@jenga-ai/agent 3.4.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +85 -78
  2. package/agents/developer.md +1 -1
  3. package/agents/scrum-master.md +20 -2
  4. package/agents/tester.md +3 -3
  5. package/hooks/on_session_end.sh +5 -5
  6. package/lib/generate-agent-context.js +2 -2
  7. package/lib/generate-copilot-hooks.js +1 -1
  8. package/lib/generate-skill-allow-list.js +37 -3
  9. package/lib/mirror.js +1 -1
  10. package/lib/postinstall-manifest.js +1 -1
  11. package/lib/skill-allow-list.json +2 -2
  12. package/mcp/help/index.js +8 -17
  13. package/mcp/help/scan.js +73 -0
  14. package/package.json +6 -1
  15. package/project/app/api/parsers/knowledge-graph.js +100 -9
  16. package/project/app/api/routes/health.js +36 -0
  17. package/project/app/api/scripts/capture-snapshot.js +9 -6
  18. package/project/app/ui/dist/assets/{index-CdK3Qrep.css → index-BVR_7Owg.css} +1 -1
  19. package/project/app/ui/dist/assets/index-CtU2xLQm.js +104 -0
  20. package/project/app/ui/dist/index.html +2 -2
  21. package/project/app/ui/package.json +4 -0
  22. package/project/app/ui/scripts/build-snapshot-html.cjs +63 -2
  23. package/scripts/acquire-concurrency-slot.sh +1 -1
  24. package/scripts/apply-j-prefix.sh +46 -5
  25. package/scripts/audit-twin-divergence.sh +73 -5
  26. package/scripts/build-pages-site.sh +1 -1
  27. package/scripts/check-public-playbook-steps.sh +158 -52
  28. package/scripts/check-publicignore-match.sh +2 -2
  29. package/scripts/compute-deploy-reconcile.sh +5 -5
  30. package/scripts/delete-bare-skill-dirs.sh +330 -0
  31. package/scripts/generate-legacy-shipped-paths.js +2 -2
  32. package/scripts/idea_manager.sh +258 -3
  33. package/scripts/mark-deployed.sh +2 -2
  34. package/scripts/populate-knowledge-graph.entity-resolution.test.js +254 -0
  35. package/scripts/populate-knowledge-graph.js +213 -5
  36. package/scripts/populate-knowledge-graph.staleness.test.js +130 -0
  37. package/scripts/postinstall.js +1 -1
  38. package/scripts/repoint-skill-refs.sh +539 -0
  39. package/scripts/todo_manager.sh +1 -1
  40. package/scripts/verify-legacy-seed-reconcile.sh +10 -10
  41. package/scripts/verify-postinstall-reconcile.sh +7 -7
  42. package/scripts/write-context-digest.sh +1 -1
  43. package/skills/j-clearify/SKILL.md +2 -2
  44. package/skills/j-close-story/scripts/check-privatized.sh +4 -4
  45. package/skills/j-distribute/CONFIG_SCHEMA.md +82 -5
  46. package/skills/j-do/SKILL.md +101 -17
  47. package/skills/j-doc-sync/SKILL.md +1 -0
  48. package/skills/j-gitignore/SKILL.md +157 -0
  49. package/skills/j-gitignore/assets/jenga-paths.txt +50 -0
  50. package/skills/j-gitignore/scripts/_catalog.sh +105 -0
  51. package/skills/j-gitignore/scripts/audit-gitignore.sh +194 -0
  52. package/skills/j-gitignore/scripts/repair-gitignore.sh +226 -0
  53. package/skills/j-gitignore/scripts/untrack-jenga-files.sh +210 -0
  54. package/skills/j-idea/SKILL.md +78 -6
  55. package/skills/j-idea/assets/idea_template.md +1 -1
  56. package/skills/j-improve/SKILL.md +1 -1
  57. package/skills/j-init/SKILL.md +53 -14
  58. package/skills/j-init/assets/.gitignore_template +1 -2
  59. package/skills/j-init/assets/scope-thresholds_template.json +5 -2
  60. package/skills/j-init/scripts/apply-scaffold-visibility.sh +192 -0
  61. package/skills/j-init/scripts/init.sh +22 -8
  62. package/skills/j-playbook/SKILL.md +1 -1
  63. package/skills/j-publish/SKILL.md +1 -1
  64. package/skills/j-publish/adapters/npm-ci.md +6 -1
  65. package/skills/j-publish/adapters/npm.md +1 -1
  66. package/skills/j-publish/scripts/generate_release_notes.sh +1 -1
  67. package/skills/j-publish/scripts/npm_stage_inspect.sh +61 -0
  68. package/skills/j-reconcile/SKILL.md +2 -2
  69. package/skills/j-reconcile/scripts/detect-unlinked-code.sh +11 -11
  70. package/skills/j-redo/SKILL.md +1 -1
  71. package/skills/j-skillify/assets/init-new/assets/.gitignore_template +1 -2
  72. package/skills/j-spinoff/SKILL.md +1 -1
  73. package/skills/j-status/SKILL.md +15 -0
  74. package/skills/j-todo/SKILL.md +3 -1
  75. package/skills/j-uncharted/SKILL.md +55 -8
  76. package/skills/j-uncharted/assets/NODE_QUESTION_TEMPLATE.md +69 -0
  77. package/skills/j-uncharted/scripts/detect-dependencies.sh +1 -1
  78. package/skills/j-uncharted/scripts/detect-tests.sh +1 -1
  79. package/skills/j-uncharted/scripts/elicitation-state.sh +46 -8
  80. package/skills/j-uncharted/scripts/validate-proposed-items.sh +1 -1
  81. package/skills/j-wtf/SKILL.md +1 -1
  82. package/skills/jenga/SKILL.md +43 -9
  83. package/skills/jenga/playbooks/board-hygiene.json +32 -0
  84. package/skills/jenga/playbooks/schema.json +73 -6
  85. package/skills/jenga/playbooks/understand-then-commit.json +19 -0
  86. package/skills/jenga/scripts/load-nl-catalog.sh +1 -1
  87. package/skills/jenga/scripts/load-playbooks.sh +23 -11
  88. package/skills/jenga/scripts/match-playbook.sh +1 -1
  89. package/templates/SCRUM_BOARD_SCHEMA.md +14 -1
  90. package/templates/SKILL_TEMPLATE.md +12 -0
  91. package/templates/permission-levels/level-4-elevated.json +1 -1
  92. package/templates/permission-levels/level-5-unrestricted.json +1 -1
  93. package/templates/playbook-types.json +34 -6
  94. package/project/app/ui/dist/assets/index-7fj-vllY.js +0 -104
  95. package/scripts/generate-j-alias.sh +0 -333
  96. package/skills/j-dev-done/SKILL.md +0 -53
  97. package/skills/j-dev-done/scripts/classify-commit-outcome.sh +0 -114
package/README.md CHANGED
@@ -31,6 +31,34 @@ Jenga AI solves each of these with structure: persistent engineering context mai
31
31
 
32
32
  ---
33
33
 
34
+ ## How Jenga AI's Agentic Workflow Works
35
+
36
+ ```
37
+ j.init → j.pi-plan → j.todo → j.do
38
+ │
39
+ Developer agent
40
+ (isolated worktree, commits)
41
+ │
42
+ Tester agent
43
+ (validates, updates board status)
44
+ │
45
+ SessionEnd hook
46
+ (writes triggers to queue)
47
+ │
48
+ Scrum Master (next session)
49
+ (processes queue, rollups, unblocks)
50
+ ```
51
+
52
+ | Mechanism | Location | Purpose |
53
+ |---|---|---|
54
+ | Scrum board | `project/board/` | Epics, stories, tasks with structured frontmatter |
55
+ | Trigger queue | `project/queue/scrum_triggers.jsonl` | Async handoff to Scrum Master |
56
+ | Event log | `project/logs/events.json` | Append-only audit trail |
57
+ | Rapport system | `project/rapports/` | Problem/analysis reports by Developer and Tester |
58
+ | File locking | `<file>.lock` adjacent to board files | Concurrency control for parallel agents |
59
+
60
+ ---
61
+
34
62
  ## What You Get
35
63
 
36
64
  - **Three specialised agents** — Scrum Master, Developer, Tester — each with a distinct role and no self-graded work
@@ -60,83 +88,6 @@ The Tester doesn't read your code and form an opinion about it. It executes the
60
88
 
61
89
  ---
62
90
 
63
- ## Examples
64
-
65
- ### A Real Session, On This Repo
66
-
67
- Not a demo — this is what actually produced the section you're reading.
68
-
69
- The conversation starts with a request to align this README's language and content with a positioning plan drafted for this epic (E41):
70
-
71
- ```
72
- j.improve E41: I want to make some adjustments to the README in both
73
- language as well as content to better match [the E41 positioning plan]
74
- [...]
75
- ```
76
-
77
- That plan already existed, so the copy gets edited directly — no need to re-run a fresh analysis pass just to re-derive it. A few edits later, ten more issues surface, one with an `j.brainstorm` request attached:
78
-
79
- ```
80
- Then do a j.todo on this, as there are more places that should be
81
- edited based on that scrutiny rapport:
82
- * Remove warning banner about Copilot conflict
83
- [...]
84
- * Fewer, better and more relatable examples, we should
85
- j.brainstorm this so I can send examples from real world usecases
86
- [...]
87
- * Should we really display the entire Skill list here?
88
- [...]
89
- ```
90
-
91
- `j.todo` classifies the ten items into two stories: nine mechanical README fixes (`E41_S10`), and the Examples section you're reading right now (`E41_S11`) — carved out and held back specifically because of that `j.brainstorm` request, since a real example had to come from the user, not be invented.
92
-
93
- Once `E41_S10` is on the board, it's picked to run first. The Scrum Master reads the story and decomposes it into seven tasks, executed through `j.do`. Five are small, single-file text fixes deemed `--trivial` — they run inline, smoke-tested and committed directly, no worktree needed. Two need an editorial call — one of them being which skills are "foundational" enough to stay inline — those escalate to a real git worktree with a Developer and a Tester. The Tester independently re-verifies the diff, catches one file the Developer missed, fixes it, and queues the story for rollup. The Scrum Master processes that queue next: `E41_S10` → `Passed`. Epic `E41` stays `In Progress` — three other stories are still open.
94
-
95
- Then, circling back to the deferred request:
96
-
97
- ```
98
- Jenga AI: [...] Remaining open thread from this session: E41_S11
99
- (Examples rework) is on the board but not started — it's gated on
100
- a j.brainstorm session where you supply real-world use cases. Want
101
- to do that now, or leave it queued?
102
-
103
- You: do it now
104
- ```
105
-
106
- `j.brainstorm` opens for the gated story — the session you're reading right now, working out what this example should even say.
107
-
108
- Nobody hand-wrote this task breakdown. The board did it, the routing rules decided what needed a real worktree versus what could run inline, and the Tester — not the agent that made the edits — decided when each piece was actually done. The board entries are real: `E41_S10` and `E41_S11`.
109
-
110
- ---
111
-
112
- ## How Jenga AI's Agentic Workflow Works
113
-
114
- ```
115
- j.init → j.pi-plan → j.todo → j.do
116
- │
117
- Developer agent
118
- (isolated worktree, commits)
119
- │
120
- Tester agent
121
- (validates, updates board status)
122
- │
123
- SessionEnd hook
124
- (writes triggers to queue)
125
- │
126
- Scrum Master (next session)
127
- (processes queue, rollups, unblocks)
128
- ```
129
-
130
- | Mechanism | Location | Purpose |
131
- |---|---|---|
132
- | Scrum board | `project/board/` | Epics, stories, tasks with structured frontmatter |
133
- | Trigger queue | `project/queue/scrum_triggers.jsonl` | Async handoff to Scrum Master |
134
- | Event log | `project/logs/events.json` | Append-only audit trail |
135
- | Rapport system | `project/rapports/` | Problem/analysis reports by Developer and Tester |
136
- | File locking | `<file>.lock` adjacent to board files | Concurrency control for parallel agents |
137
-
138
- ---
139
-
140
91
  ## Agents
141
92
 
142
93
  | Agent | Role | Owns |
@@ -161,6 +112,13 @@ Each agent is defined in `.agents/agents/`. They communicate exclusively through
161
112
  npm install -g @jenga-ai/agent
162
113
  ```
163
114
 
115
+ > **pnpm or `--ignore-scripts`?** Jenga's install relies on a `postinstall` lifecycle script to
116
+ > mirror `skills/`/`agents/` into your project — if your package manager blocks lifecycle
117
+ > scripts by default (pnpm v8+) or you install with `--ignore-scripts`/`ignore-scripts=true`,
118
+ > the install will silently produce none of the framework files with no error shown. See
119
+ > [`docs/distribution.md`](docs/distribution.md#prerequisite--lifecycle-scripts-must-be-allowed-to-run-e26_s09)
120
+ > for the exact npm/pnpm opt-in step.
121
+
164
122
  Or clone directly:
165
123
 
166
124
  1. **Clone or copy this repo** into your project's root.
@@ -251,7 +209,7 @@ j.brainstorm → j.todo → j.do → j.commit
251
209
 
252
210
  Calling `j.playbook` with no id prints a table of every available playbook (id, name, and steps) instead of resolving one.
253
211
 
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.
212
+ Nothing executes until you confirm the chain, and any step can be unchecked first. `understand-then-commit` prepends `j.uncharted` investigation for unfamiliar code before running that same pipeline — useful when the change touches code you don't fully understand yet. No built-in playbook publishes or mirrors on your behalf: build chains terminate at `j.commit`, and publishing stays an explicit, separately invoked act.
255
213
 
256
214
  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
215
 
@@ -259,6 +217,55 @@ Want your own recurring chain? `j.playbook-new` walks you through authoring one
259
217
 
260
218
  ---
261
219
 
220
+ ## Example
221
+
222
+ ### A Real Session, On This Repo
223
+
224
+ Not a demo — this is what actually produced the section you're reading.
225
+
226
+ The conversation starts with a request to align this README's language and content with a positioning plan drafted for this epic (E41):
227
+
228
+ ```
229
+ j.improve E41: I want to make some adjustments to the README in both
230
+ language as well as content to better match [the E41 positioning plan]
231
+ [...]
232
+ ```
233
+
234
+ That plan already existed, so the copy gets edited directly — no need to re-run a fresh analysis pass just to re-derive it. A few edits later, ten more issues surface, one with an `j.brainstorm` request attached:
235
+
236
+ ```
237
+ Then do a j.todo on this, as there are more places that should be
238
+ edited based on that scrutiny rapport:
239
+ * Remove warning banner about Copilot conflict
240
+ [...]
241
+ * Fewer, better and more relatable examples, we should
242
+ j.brainstorm this so I can send examples from real world usecases
243
+ [...]
244
+ * Should we really display the entire Skill list here?
245
+ [...]
246
+ ```
247
+
248
+ `j.todo` classifies the ten items into two stories: nine mechanical README fixes (`E41_S10`), and the Examples section you're reading right now (`E41_S11`) — carved out and held back specifically because of that `j.brainstorm` request, since a real example had to come from the user, not be invented.
249
+
250
+ Once `E41_S10` is on the board, it's picked to run first. The Scrum Master reads the story and decomposes it into seven tasks, executed through `j.do`. Five are small, single-file text fixes deemed `--trivial` — they run inline, smoke-tested and committed directly, no worktree needed. Two need an editorial call — one of them being which skills are "foundational" enough to stay inline — those escalate to a real git worktree with a Developer and a Tester. The Tester independently re-verifies the diff, catches one file the Developer missed, fixes it, and queues the story for rollup. The Scrum Master processes that queue next: `E41_S10` → `Passed`. Epic `E41` stays `In Progress` — three other stories are still open.
251
+
252
+ Then, circling back to the deferred request:
253
+
254
+ ```
255
+ Jenga AI: [...] Remaining open thread from this session: E41_S11
256
+ (Examples rework) is on the board but not started — it's gated on
257
+ a j.brainstorm session where you supply real-world use cases. Want
258
+ to do that now, or leave it queued?
259
+
260
+ You: do it now
261
+ ```
262
+
263
+ `j.brainstorm` opens for the gated story — the session you're reading right now, working out what this example should even say.
264
+
265
+ Nobody hand-wrote this task breakdown. The board did it, the routing rules decided what needed a real worktree versus what could run inline, and the Tester — not the agent that made the edits — decided when each piece was actually done. The board entries are real: `E41_S10` and `E41_S11`.
266
+
267
+ ---
268
+
262
269
  ## When to Use Jenga AI
263
270
 
264
271
  **Use it when:**
@@ -215,7 +215,7 @@ This list is fixed and verbatim across both this file and `agents/tester.md` —
215
215
 
216
216
  **Effect — forced inline scope.** `execution_scope` is force-set to `inline` for any `locked` task, overriding whatever scope `j.jenga`'s Execution Scope Assignment heuristics would otherwise assign — or auto-correcting a wrong value in place, with a logged `override_justification` note explaining the correction. The concrete mechanism is `skills/jenga/SKILL.md` Phase 0.5's **Rule 4 — `crucial_level: locked` forces `execution_scope: inline`** (added by E39_S03_T03).
217
217
 
218
- **Effect — dispatch-time rejection of backgrounding.** A `locked` task can never be routed to a background subagent, a worktree-isolated session, or a bundled `j.jenga` story-batch execution, regardless of what its `execution_scope` value currently reads. This is enforced at two separate points, both added by E39_S03_T04: `skills/jenga/SKILL.md` Phase 3.5 step 5's **Guard: locked-task disqualifier (defense-in-depth)**, which disqualifies any story containing a `locked` task from the bundle path before dispatch, and `skills/do/SKILL.md` Section 4.2's **Locked-task dispatch guard (defense-in-depth)**, which forces the inline execution path (no worktree, no developer subagent) at the point of dispatch even if `execution_scope` somehow still reads something other than `inline`.
218
+ **Effect — dispatch-time rejection of backgrounding.** A `locked` task can never be routed to a background subagent, a worktree-isolated session, or a bundled `j.jenga` story-batch execution, regardless of what its `execution_scope` value currently reads. This is enforced at two separate points, both added by E39_S03_T04: `skills/jenga/SKILL.md` Phase 3.5 step 5's **Guard: locked-task disqualifier (defense-in-depth)**, which disqualifies any story containing a `locked` task from the bundle path before dispatch, and `skills/j-do/SKILL.md` Section 4.2's **Locked-task dispatch guard (defense-in-depth)**, which forces the inline execution path (no worktree, no developer subagent) at the point of dispatch even if `execution_scope` somehow still reads something other than `inline`.
219
219
 
220
220
  **No agent-discretion obligation.** Unlike `advisory` (a reporting-cadence habit you must remember to keep up) and `gated` (a confirmation you must actively pause and perform), `locked` requires no judgment call from you at all. It is fully enforced by pre-flight validation (Rule 4) and dispatch-time guards (the Phase 3.5 and `j.do` guards above) before you ever begin work on the task — there is no step in this tier that depends on you noticing or remembering anything. Your only obligation is to recognize that a `locked` task will always run in the current foreground session, and to never manually route around that guarantee — for example, do not spin up your own background subagent or a separate worktree-isolated session to "help" with a `locked` task, even if it seems more efficient. If a locked task ever reaches you already running in a background or worktree-isolated context, treat that as a guard failure worth flagging (see Rapport System), not something to quietly work through.
221
221
 
@@ -74,10 +74,28 @@ This is a self-contained procedure, not a session-start-only step. It may be inv
74
74
  4. **Reject branch** — if the reason is generic or non-concrete, do not write any of `crucial_level` / `crucial_set_by` / `crucial_note` to the target item. Instead, decline the escalation using the same `.IGNORE.md` convention already documented in `agents/tester.md`'s "IGNORE.md — skipping resolved rapports" section: rename the rapport file to `<name>.IGNORE.md` and append an Ignore Log entry stating the escalation was declined for lacking a concrete reason. This keeps the declined rapport from being silently re-surfaced as a fresh `rapport_review` trigger on a future `on_session_end.sh` scan, since that scan's new-rapport detection skips `*.IGNORE.md` files.
75
75
  5. **Report back** — in both branches, name the target item and the decision made (accepted at tier X with `crucial_set_by`/`crucial_note` set, or declined for lacking a concrete reason) as part of the existing "Report to the user" step below (Session Start — Queue Processing, item 3); no separate reporting step is needed.
76
76
  6. **This is the only path** by which a mid-task agent request results in a `crucial_level` board write. Developer and tester never write `crucial_level`, `crucial_set_by`, or `crucial_note` directly to a board file themselves under any circumstance — they may only *request* the change via a `crucial_escalation` rapport, and the actual frontmatter write happens here, exclusively by scrum-master, closing the loop described in E39's Purpose section ("the actual frontmatter write still goes through scrum-master, never the subagent itself").
77
+ - **Rapport-sourced idea/topic detection (E35_S03_T02) — runs for every rapport processed under `rapport_review`, regardless of `Type`, in addition to (never instead of) the backlog-or-`Failed` handling above and the `crucial_escalation` special case above.** For each rapport, apply one further LLM judgment: does this rapport surface a distinct idea or topic not already captured in an existing epic/story/task or `project/ideas.md` entry? This mirrors the "Crucial Level Heuristic Proposal" propose-then-confirm shape (evaluation → proposal → same-session confirmation → write, or decline → record and suppress re-proposal) documented later in this file — read that section for the pattern this step reuses.
78
+ 1. **Skip check — already-handled rapports.** Before evaluating a rapport for this step, check both of the following. If either is true, skip idea-detection for this rapport entirely and move on to the next rapport (this does not affect the rapport's normal backlog-or-`Failed` or `crucial_escalation` handling, which always runs regardless):
79
+ - `project/ideas.md` already contains a line tagged `<!-- rapport: <this rapport's file path> -->` — a rapport-sourced idea from this rapport was already confirmed and captured in a prior session, per the source-rapport link convention `skills/j-idea/SKILL.md` documents (E35_S03_T01).
80
+ - The rapport file itself already contains an `## Idea Detection Log` section (written by the decline branch below) — a proposal for this rapport was already made and declined in a prior session.
81
+ 2. **Evaluate** — read the rapport's Summary, Context, and Problem Description sections and judge whether they surface a distinct idea or topic genuinely not already tracked anywhere on the board (`project/board/epics/`, `project/board/stories/`, `project/board/tasks/`) or in `project/ideas.md`. If nothing distinct surfaces, do nothing further for this rapport under this step.
82
+ 3. **Propose** — if a distinct idea is found, state the proposed idea text to the user in-session and ask for confirmation. Do not add anything to `project/ideas.md` without it. Apply the same same-session-only discipline as the Confirm-Before-Write Gate below: if the session ends before the user confirms, the proposal is dropped — not persisted, not carried forward — and the rapport is simply re-evaluated fresh (subject to the skip check above) on a future `rapport_review` pass.
83
+ 4. **On confirm** — append the idea to `project/ideas.md` using the exact same mechanism `j.idea` uses: `bash "$([ -f scripts/idea_manager.sh ] && echo scripts/idea_manager.sh || echo node_modules/@jenga-ai/agent/scripts/idea_manager.sh)" add '<idea text> <!-- rapport: <path to this rapport> -->'` — untagged (no `PROMOTED`/`REJECTED` at capture time), with the source-rapport link recorded via the `<!-- rapport: <path> -->` inline-comment convention already defined in `skills/j-idea/SKILL.md`'s "Source-Rapport Link Convention" subsection. Scrum-master does **not** author a board file directly from this step — the idea stays in `project/ideas.md` until separately promoted, exactly like any other `j.idea` entry (promotion is re-running `j.brainstorm` on it, per `skills/j-idea/SKILL.md`).
84
+ 5. **On decline** — do not add anything to `project/ideas.md`. Instead, append the following section to the rapport file itself (a new, distinctly-named section — do **not** reuse the rapport template's existing `## Ignore Log` section, which is reserved for fully resolving/superseding the whole rapport via the `.IGNORE.md` rename convention; a declined idea proposal does not mean the rapport's underlying problem is resolved, so its normal backlog-or-`Failed`/`crucial_escalation` handling must be unaffected):
85
+ ```
86
+ ## Idea Detection Log
87
+ _Populated by scrum-master when a rapport-derived idea proposal is declined by the user._
88
+
89
+ **Declined by:** User
90
+ **Date:** YYYY-MM-DD (UTC)
91
+ **Proposed idea:** <one-line idea text that was proposed>
92
+ ```
93
+ This is the same non-re-proposal guarantee as `crucial_declined` (see "Decline Handling" under "Crucial Level Heuristic Proposal" below), adapted for a rapport file, which carries no frontmatter of its own to hold a boolean flag: the presence of this section is exactly what the skip check in step 1 above looks for, so a declined proposal is never re-surfaced on a future `rapport_review` pass over the same rapport. A decline here is scoped to this one rapport only — it never suppresses idea-detection evaluation on any other rapport, including similar ones.
94
+ 6. **Report back** — name any rapports where an idea was proposed and the outcome (captured to `project/ideas.md`, or declined) as part of the existing "Report to the user" step below (Session Start — Queue Processing, item 3); no separate reporting step is needed.
77
95
  - `status_review`: Review the scrum board for any tasks or stories whose status should be updated based on recent activity.
78
96
  - `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
- - `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.
97
+ - `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/j-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/j-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.
98
+ - `capacity_starvation`: `skills/j-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.
81
99
  - After processing all triggers, **clear the file** by writing an empty file — do not leave processed triggers.
82
100
 
83
101
  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/agents/tester.md CHANGED
@@ -167,7 +167,7 @@ Always include the sender object in the response.
167
167
 
168
168
  **Trigger.** During steps 4-6 of "Invoked for test implementation and/or execution" above, you find a defect. Before defaulting to step 9's rapport path, classify it: is this **mechanical** (safe to fix in place) or does it **need a rapport** (a design decision or real risk is involved)? This classification is what determines which of the two remediation paths below you take — it is not optional bookkeeping, and it happens at the moment the defect is found, not retroactively.
169
169
 
170
- **Motivating case.** `project/rapports/analysis/E42_S04-execution-overhead-postmortem.md` Finding 3: the tester found `skills/dev-done/scripts/classify-commit-outcome.sh` committed without its executable bit (`chmod +x`) plus a dead, unreachable error branch. Both were one-line-class fixes, but the only path available at the time was the full formal one — a `test_failure` rapport, a separate developer fix commit, a re-verification pass, and a second complete `j.self-sync` mirror run. That produced 3 of the task's 6 total commits for defects that needed no design judgment at all. This subsection exists so that pattern doesn't repeat.
170
+ **Motivating case.** `project/rapports/analysis/E42_S04-execution-overhead-postmortem.md` Finding 3: the tester found `skills/j-dev-done/scripts/classify-commit-outcome.sh` committed without its executable bit (`chmod +x`) plus a dead, unreachable error branch. Both were one-line-class fixes, but the only path available at the time was the full formal one — a `test_failure` rapport, a separate developer fix commit, a re-verification pass, and a second complete `j.self-sync` mirror run. That produced 3 of the task's 6 total commits for defects that needed no design judgment at all. This subsection exists so that pattern doesn't repeat.
171
171
 
172
172
  **Qualifying examples (mechanical — fix in place).**
173
173
  - A missing executable bit or other permission-bit error (e.g. a script committed without `chmod +x`) — the exact E42_S04 Finding 3 case.
@@ -260,7 +260,7 @@ Append this as a new array entry — never overwrite existing log content. This
260
260
 
261
261
  **Effect — forced inline scope.** `execution_scope` is force-set to `inline` for any `locked` task, overriding whatever scope `j.jenga`'s Execution Scope Assignment heuristics would otherwise assign — or auto-correcting a wrong value in place, with a logged `override_justification` note. The concrete mechanism is `skills/jenga/SKILL.md` Phase 0.5's **Rule 4 — `crucial_level: locked` forces `execution_scope: inline`** (added by E39_S03_T03). As tester, verify this field is actually `inline` on any `locked` item you're validating — a value that slipped through would itself be a defect worth flagging.
262
262
 
263
- **Effect — dispatch-time rejection of backgrounding.** A `locked` task can never be routed to a background subagent, a worktree-isolated session, or a bundled `j.jenga` story-batch execution, regardless of its `execution_scope` value. This is enforced at two points, both added by E39_S03_T04: `skills/jenga/SKILL.md` Phase 3.5 step 5's **Guard: locked-task disqualifier (defense-in-depth)** and `skills/do/SKILL.md` Section 4.2's **Locked-task dispatch guard (defense-in-depth)**. This matters directly to you as tester: you must never yourself dispatch, recommend, or improvise a background subagent, a separate worktree-isolated session, or a bundled batch run in order to verify a `locked` item faster or in parallel with other work — verification of a `locked` item happens in the same foreground session the guards already pinned it to, same as implementation.
263
+ **Effect — dispatch-time rejection of backgrounding.** A `locked` task can never be routed to a background subagent, a worktree-isolated session, or a bundled `j.jenga` story-batch execution, regardless of its `execution_scope` value. This is enforced at two points, both added by E39_S03_T04: `skills/jenga/SKILL.md` Phase 3.5 step 5's **Guard: locked-task disqualifier (defense-in-depth)** and `skills/j-do/SKILL.md` Section 4.2's **Locked-task dispatch guard (defense-in-depth)**. This matters directly to you as tester: you must never yourself dispatch, recommend, or improvise a background subagent, a separate worktree-isolated session, or a bundled batch run in order to verify a `locked` item faster or in parallel with other work — verification of a `locked` item happens in the same foreground session the guards already pinned it to, same as implementation.
264
264
 
265
265
  **No agent-discretion obligation.** Unlike `advisory` (a reporting-cadence habit) and `gated` (a confirmation you must actively pause and perform), `locked` requires no judgment call from you. It is fully enforced by pre-flight validation (Rule 4) and dispatch-time guards (the Phase 3.5 and `j.do` guards above) before the developer ever begins work — none of this depends on you noticing or remembering anything mid-verification. Your only obligation is to recognize that a `locked` task always runs (and was always verified) in the current foreground session, and to never suggest or perform a workaround that would route around that guarantee. If you find evidence during verification that a `locked` item was actually run in a backgrounded or worktree-isolated context, treat that as a guard failure worth flagging (see Rapport System), not something to silently pass.
266
266
 
@@ -381,7 +381,7 @@ After every status update to a task or story, check whether a parent rollup is w
381
381
 
382
382
  2. The scrum master processes rollup triggers from the queue at its next session start and updates story and epic statuses accordingly.
383
383
 
384
- **Playbook step status is out of scope for this rollup (E53_S04_T03 audit).** `/jenga` playbook
384
+ **Playbook step status is out of scope for this rollup (E53_S04_T03 audit).** `j.jenga` playbook
385
385
  runs (`skills/jenga/scripts/run-playbook-step.sh`) track their own separate step-level status
386
386
  vocabulary (`step_ready`/`complete`/`halted`, and per-step `passed`/`failed`/`skipped`) in an
387
387
  ephemeral, session-local temp state file — never in `project/board/`. This rollup logic never
@@ -128,8 +128,8 @@ if [ -d "$RAPPORT_DIR" ]; then
128
128
  # Uses a portable `while read` loop rather than mapfile/readarray: macOS
129
129
  # ships bash 3.2 (no mapfile support), and this hook must run there —
130
130
  # matches the convention already established in scripts/smoke-harness.sh,
131
- # skills/publish/scripts/generate_release_notes.sh, and
132
- # skills/publish/scripts/finalize_changelog.sh. Fixed incidentally here
131
+ # skills/j-publish/scripts/generate_release_notes.sh, and
132
+ # skills/j-publish/scripts/finalize_changelog.sh. Fixed incidentally here
133
133
  # because this task's acceptance criteria require the hook to actually
134
134
  # execute end-to-end (mapfile silently failed on stock macOS bash,
135
135
  # leaving CURRENT_FILES empty and masking real detection results).
@@ -312,9 +312,9 @@ for HANDOFF_FILE in "$HANDOFF_DIR"/*.json; do
312
312
  # A conversational architecture elicitation session (/uncharted
313
313
  # onboard's default flow, or segment --mode investigate — E20_S08_T03)
314
314
  # ended mid-run without converging. The session driving it is
315
- # responsible for calling skills/uncharted/scripts/elicitation-state.sh
315
+ # responsible for calling skills/j-uncharted/scripts/elicitation-state.sh
316
316
  # pause and then writing this handoff with status "elicitation_paused"
317
- # as its last action (see skills/uncharted/SKILL.md's Multi-Session
317
+ # as its last action (see skills/j-uncharted/SKILL.md's Multi-Session
318
318
  # Persistence subsection). This routes that pause into a resume
319
319
  # signal for the next scrum-master session, per the existing
320
320
  # SessionEnd/queue pattern rather than a new persistence mechanism
@@ -425,4 +425,4 @@ bash "$PROJECT_DIR/scripts/todo_cleanup.sh"
425
425
  # digest's consumer is a later session that may not have started yet when
426
426
  # THIS session ends — see scripts/sweep-stale-context-digests.sh's header for
427
427
  # the full rationale. Runs unconditionally, same as todo cleanup above.
428
- bash "$PROJECT_DIR/scripts/sweep-stale-context-digests.sh"
428
+ bash "$PROJECT_DIR/scripts/sweep-stale-context-digests.sh"
@@ -5,7 +5,7 @@
5
5
  * Single source of truth for scaffolding the two root-level agent-context
6
6
  * files from templates/agent-context.md.tpl (E41_S04_T02). Used by:
7
7
  * - lib/commands/init.js (published `jenga init` CLI)
8
- * - skills/init/scripts/init.sh (this repo's own board-scaffolding flow,
8
+ * - skills/j-init/scripts/init.sh (this repo's own board-scaffolding flow,
9
9
  * invoked via `node` — see the CLI guard
10
10
  * at the bottom of this file)
11
11
  *
@@ -234,7 +234,7 @@ export function generateAgentContext(projectRoot = process.cwd(), packageRoot =
234
234
  }
235
235
 
236
236
  // CLI guard — allows `node lib/generate-agent-context.js [projectRoot]`,
237
- // used by skills/init/scripts/init.sh (this repo's own board-scaffolding
237
+ // used by skills/j-init/scripts/init.sh (this repo's own board-scaffolding
238
238
  // flow, where node is guaranteed available). The published npm CLI path
239
239
  // (lib/commands/init.js) imports generateAgentContext() directly instead.
240
240
  //
@@ -30,7 +30,7 @@
30
30
  * is no legitimate reason for a consumer to hand-edit it, so a full deterministic overwrite on
31
31
  * every run is safe and simpler than a marker-merge.
32
32
  *
33
- * No `skills/self-sync/scripts/run.js` wiring is needed either: self-sync mirrors root-level
33
+ * No `skills/j-self-sync/scripts/run.js` wiring is needed either: self-sync mirrors root-level
34
34
  * source directories, and this file has none to mirror — it is generated directly, exactly like
35
35
  * `.github/copilot-instructions.md` is (also outside self-sync's `COPY_SET`/`GITHUB_COPY_SET`).
36
36
  *
@@ -19,10 +19,26 @@
19
19
  * Auto-regeneration at `/self-sync`/postinstall time is wired up separately by the follow-up
20
20
  * task E50_S02_T02 — not in scope here.
21
21
  *
22
+ * ## The three-way mapping (E50_S12_T01)
23
+ *
24
+ * This generator's correctness rests on a relationship that used to be left implicit in
25
+ * `extractName`'s regex. Stated explicitly, per `docs/skill-authoring.md`'s "The Canonical Naming
26
+ * Contract" table (settled 2026-09-09, transcribed from E50_S10):
27
+ *
28
+ * skills/j-<name>/ (directory) <-> name: j.<name> (frontmatter) <-> <name> (allow-listed
29
+ * identifier, after extractName's /^j[.:]/i strip)
30
+ *
31
+ * Three permanent exceptions skip the `j-` prefix on all three legs of that mapping and keep their
32
+ * bare form everywhere: `jenga`, `jenga-permission-level` (both deliberately excluded from twin
33
+ * generation, E50_S06), and `index` (not a skill — no SKILL.md, not part of routing). Every other
34
+ * skill under `skills/` is expected to satisfy the mapping above once E50_S15 lands (see that
35
+ * story's frontmatter-rewrite scope) — see the note below on the transitional state this generator
36
+ * currently has to tolerate.
37
+ *
22
38
  * Consumers:
23
39
  * - CLI guard at the bottom of this file (`node lib/generate-skill-allow-list.js`) — run once
24
40
  * against this repo's own skills/ to produce the committed lib/skill-allow-list.json, and
25
- * intended to be callable from skills/self-sync/scripts/run.js and scripts/postinstall.js in
41
+ * intended to be callable from skills/j-self-sync/scripts/run.js and scripts/postinstall.js in
26
42
  * the follow-up task.
27
43
  * - getSkillAllowListIdentifiers() — an in-memory-only helper for callers that want the
28
44
  * identifier list without touching disk, e.g. the MCP router work in E50_S02_T03.
@@ -48,6 +64,24 @@ const DEFAULT_PACKAGE_ROOT = join(__dirname, "..");
48
64
  * is needed here (unlike mcp/router/skill-index.js's fuller frontmatter parser, which also
49
65
  * handles array fields like `keywords`/`examples`) — a direct regex against the frontmatter
50
66
  * block is sufficient and avoids re-implementing that broader parser for a single field.
67
+ *
68
+ * This is the function that produces one leg of the three-way mapping described in this file's
69
+ * header (`directory <-> frontmatter name: <-> allow-listed identifier`): given a SKILL.md's raw
70
+ * frontmatter `name:` value, it returns the bare identifier that goes into the allow-list. For a
71
+ * normal skill (post-E50_S15) that value is `j.<name>` and this strips to `<name>`, matching the
72
+ * `skills/j-<name>/` directory it was read from with the `j-` prefix removed. For the three
73
+ * permanent exceptions (`jenga`, `jenga-permission-level`, `index`) the frontmatter carries no
74
+ * prefix at all and this is a no-op, matching their bare directory name directly.
75
+ *
76
+ * Transitional note (pre-E50_S15): as of E50_S12, most `skills/j-<name>/SKILL.md` twins still
77
+ * carry the doubled `name: j.j-<name>` form (E50_S15's frontmatter rewrite to `j.<name>` has not
78
+ * landed yet — that rewrite and the bare-directory deletion are scoped together and must land in
79
+ * the same change, per E50_S15's story file). Stripping a single leading `j[.:]` off a doubled
80
+ * value yields `j-<name>`, not `<name>` — a real, known, and separately-owned gap in the
81
+ * three-way mapping above, not a bug in this function's own contract. See
82
+ * `project/documentation/plans/E50_S12-plan.md` for why this task's regression coverage targets a
83
+ * synthetic twin-only tree (where the mapping already holds) rather than asserting a bijection
84
+ * against this repo's own transitional `skills/` tree.
51
85
  */
52
86
  function extractName(content) {
53
87
  const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -116,7 +150,7 @@ export function getSkillAllowListIdentifiers(skillsDir = DEFAULT_SKILLS_DIR) {
116
150
 
117
151
  /**
118
152
  * Read the committed skill-allow-list.json artifact (produced by generateSkillAllowList /
119
- * regenerated by postinstall.js and skills/self-sync/scripts/run.js) and return its `skills`
153
+ * regenerated by postinstall.js and skills/j-self-sync/scripts/run.js) and return its `skills`
120
154
  * array. Resolution order mirrors resolveTemplatePath's packageRoot-then-projectRoot candidate
121
155
  * order in lib/generate-agent-context.js and lib/generate-copilot-instructions.js: the artifact
122
156
  * normally lives at `<packageRoot>/lib/skill-allow-list.json` (the installed jenga-agent
@@ -180,7 +214,7 @@ export function generateSkillAllowList(skillsDir = DEFAULT_SKILLS_DIR, outputPat
180
214
  }
181
215
 
182
216
  // CLI guard — allows `node lib/generate-skill-allow-list.js [skillsDir] [outputPath]`, intended
183
- // to be callable from skills/self-sync/scripts/run.js and scripts/postinstall.js in the
217
+ // to be callable from skills/j-self-sync/scripts/run.js and scripts/postinstall.js in the
184
218
  // follow-up auto-regeneration task (E50_S02_T02).
185
219
  //
186
220
  // process.argv[1] is compared via realpath, not as a raw string — see the identical comment
package/lib/mirror.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * (files or directories) from a source root into a destination root. Used
6
6
  * by:
7
7
  * - scripts/postinstall.js (consumer install; reconcileDeletes = false)
8
- * - skills/self-sync/... (in-repo dev mirror; reconcileDeletes = true) [wired in T02]
8
+ * - skills/j-self-sync/... (in-repo dev mirror; reconcileDeletes = true) [wired in T02]
9
9
  *
10
10
  * Design constraints:
11
11
  * - ESM, Node built-ins only (node:fs / node:fs/promises / node:path).
@@ -42,7 +42,7 @@
42
42
  * "package_version": "3.0.1",
43
43
  * "generated_at": "2026-09-07T00:00:00.000Z",
44
44
  * "dest_root": ".agents",
45
- * "paths": ["agents/developer.md", "skills/do/SKILL.md"]
45
+ * "paths": ["agents/developer.md", "skills/j-do/SKILL.md"]
46
46
  * }
47
47
  *
48
48
  * - `paths` are relative to the destination root, POSIX-separated, deduped and
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated_at": "2026-09-14T18:42:41.263Z",
2
+ "generated_at": "2026-09-20T06:12:06.824Z",
3
3
  "skill_count": 41,
4
4
  "skills": [
5
5
  "brainstorm",
@@ -12,7 +12,6 @@
12
12
  "dashboard",
13
13
  "dashboard-share",
14
14
  "deep-dive",
15
- "dev-done",
16
15
  "distribute",
17
16
  "do",
18
17
  "doc",
@@ -21,6 +20,7 @@
21
20
  "error",
22
21
  "evaluate",
23
22
  "examplify",
23
+ "gitignore",
24
24
  "help",
25
25
  "idea",
26
26
  "improve",
package/mcp/help/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
- import { existsSync, readdirSync, statSync } from "fs";
6
- import { join, resolve } from "path";
5
+ import { resolve } from "path";
6
+ import { resolveSkillsDir, candidateSkillsDirs, listSkillFolders } from "./scan.js";
7
7
 
8
8
  const server = new McpServer({
9
9
  name: "help",
@@ -23,15 +23,13 @@ server.tool(
23
23
  },
24
24
  async ({ path: inputPath }) => {
25
25
  const root = inputPath ? resolve(inputPath) : process.cwd();
26
- // The jenga-agent postinstall mirrors skills/ into both .claude/ (Claude Code)
27
- // and .agents/ (Copilot / custom agents). Prefer .claude/; fall back to .agents/.
28
- const candidates = [
29
- join(root, ".claude", "skills"),
30
- join(root, ".agents", "skills"),
31
- ];
32
- const skillsDir = candidates.find(existsSync);
26
+ // E50_S12_T06: the actual scan logic lives in ./scan.js (resolveSkillsDir /
27
+ // listSkillFolders) so it is importable and unit-testable without starting this
28
+ // MCP server. See that module's header for why. No behavior change here.
29
+ const skillsDir = resolveSkillsDir(root);
33
30
 
34
31
  if (!skillsDir) {
32
+ const candidates = candidateSkillsDirs(root);
35
33
  return {
36
34
  content: [
37
35
  {
@@ -42,14 +40,7 @@ server.tool(
42
40
  };
43
41
  }
44
42
 
45
- const entries = readdirSync(skillsDir);
46
- const folders = entries.filter((entry) => {
47
- try {
48
- return statSync(join(skillsDir, entry)).isDirectory();
49
- } catch {
50
- return false;
51
- }
52
- });
43
+ const folders = listSkillFolders(skillsDir);
53
44
 
54
45
  if (folders.length === 0) {
55
46
  return {
@@ -0,0 +1,73 @@
1
+ /**
2
+ * mcp/help/scan.js — pure directory-scan logic behind the `help` MCP tool (E50_S12_T06).
3
+ *
4
+ * Split out of index.js so it is importable and unit-testable without starting an MCP
5
+ * server. index.js instantiates an `McpServer` and ends with a top-level
6
+ * `await server.connect(transport)` that blocks on stdin — importing that file as a
7
+ * whole would hang any test that tried it, the exact untestability
8
+ * mcp/router/allow-list-guard.js's header describes for mcp/router/index.js, and the
9
+ * same reason mcp/router's Stage 1 decision was extracted into its own module by
10
+ * E50_S07_T02. This file mirrors that precedent: plain Node built-ins only, no MCP SDK
11
+ * import, no side effects at module load time.
12
+ *
13
+ * Confirmed by direct reading (E50_S12_T06): this is a pure `readdirSync`/`statSync`
14
+ * scan. It never parses a SKILL.md's frontmatter, never strips a `j.`/`j:` prefix, and
15
+ * never compares against the allow-list — it returns whatever directory names exist
16
+ * under `.claude/skills`/`.agents/skills` verbatim. A `j-<name>` twin directory and a
17
+ * bare `<name>` directory are both just directory names to this code; neither is
18
+ * treated differently in any way, so nothing here needs to change for a twin-only tree.
19
+ */
20
+
21
+ import { existsSync, readdirSync, statSync } from "fs";
22
+ import { join } from "path";
23
+
24
+ /**
25
+ * Resolves which of the two generated skill mirrors exists under `root`. The
26
+ * jenga-agent postinstall mirrors skills/ into both .claude/ (Claude Code) and
27
+ * .agents/ (Copilot / custom agents); this prefers .claude/, falling back to .agents/.
28
+ *
29
+ * @param {string} root - project root to scan (already resolved to an absolute path)
30
+ * @returns {string|null} the first candidate skills directory that exists, or null
31
+ */
32
+ export function resolveSkillsDir(root) {
33
+ const candidates = [
34
+ join(root, ".claude", "skills"),
35
+ join(root, ".agents", "skills"),
36
+ ];
37
+ return candidates.find(existsSync) ?? null;
38
+ }
39
+
40
+ /**
41
+ * The two candidate mirror paths under `root`, in preference order — exposed
42
+ * separately from resolveSkillsDir() so a caller can report "looked in: ..." even when
43
+ * neither candidate exists (mirrors index.js's `help` tool's own "No skills found"
44
+ * message).
45
+ *
46
+ * @param {string} root
47
+ * @returns {string[]}
48
+ */
49
+ export function candidateSkillsDirs(root) {
50
+ return [
51
+ join(root, ".claude", "skills"),
52
+ join(root, ".agents", "skills"),
53
+ ];
54
+ }
55
+
56
+ /**
57
+ * Lists the immediate subdirectory names of `skillsDir` — the exact set the `help` tool
58
+ * reports. See this module's header for why this is safe to call against a twin-only
59
+ * tree with no code change: it is a verbatim directory listing, format-agnostic.
60
+ *
61
+ * @param {string} skillsDir - an existing directory (typically resolveSkillsDir()'s result)
62
+ * @returns {string[]} immediate subdirectory names, in readdirSync's natural order
63
+ */
64
+ export function listSkillFolders(skillsDir) {
65
+ const entries = readdirSync(skillsDir);
66
+ return entries.filter((entry) => {
67
+ try {
68
+ return statSync(join(skillsDir, entry)).isDirectory();
69
+ } catch {
70
+ return false;
71
+ }
72
+ });
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jenga-ai/agent",
3
- "version": "3.4.0",
3
+ "version": "3.6.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": {
@@ -12,12 +12,17 @@
12
12
  "engines": {
13
13
  "node": ">=14.13.1"
14
14
  },
15
+ "jenga": {
16
+ "requiresLifecycleScripts": true,
17
+ "note": "postinstall (scripts/postinstall.js) mirrors skills/agents/hooks into the consumer project and is load-bearing for the whole framework install. On package managers that block lifecycle scripts by default (pnpm v10+, npm 11.16+ via allowScripts), an explicit consumer-side opt-in is required — see docs/distribution.md."
18
+ },
15
19
  "scripts": {
16
20
  "postinstall": "node scripts/postinstall.js",
17
21
  "prepack": "npm run ui:build --prefix project/app --",
18
22
  "generate:legacy-paths": "node scripts/generate-legacy-shipped-paths.js",
19
23
  "graph:populate": "node scripts/populate-knowledge-graph.js",
20
24
  "test": "bats tests/*.bats",
25
+ "gate:twin-parity": "bash scripts/audit-twin-divergence.sh . --min-pairs 40",
21
26
  "validate:npm-metadata": "bash scripts/validate_npm_metadata.sh",
22
27
  "ui:dev": "npm run ui:dev --prefix project/app --",
23
28
  "ui:build": "npm run ui:build --prefix project/app --",