@jenga-ai/agent 1.0.1 → 1.1.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.
Files changed (117) hide show
  1. package/README.md +10 -7
  2. package/agents/developer.md +82 -2
  3. package/agents/scrum-master.md +215 -21
  4. package/agents/tester.md +90 -8
  5. package/hooks/on_session_end.sh +171 -20
  6. package/mcp/router/embedder.js +1 -1
  7. package/mcp/training_runner/index.js +239 -0
  8. package/mcp/training_runner/package-lock.json +1065 -0
  9. package/mcp/training_runner/package.json +15 -0
  10. package/package.json +14 -16
  11. package/scripts/check-permission-level.sh +107 -0
  12. package/scripts/check-publicignore-match.sh +122 -0
  13. package/scripts/check-worktree-liveness.sh +193 -0
  14. package/scripts/generate-rapport-manifest.sh +43 -0
  15. package/scripts/idea_manager.sh +47 -0
  16. package/scripts/install-worktree-commit-guard.sh +134 -0
  17. package/scripts/jenga-permission-level-switch.sh +109 -0
  18. package/scripts/smoke-harness.sh +139 -0
  19. package/scripts/validate-board.sh +62 -0
  20. package/scripts/with-lock.sh +158 -0
  21. package/scripts/worktree-remove-guard.sh +204 -0
  22. package/skills/clearify/SKILL.md +52 -0
  23. package/skills/close-story/SKILL.md +203 -0
  24. package/skills/close-story/scripts/check-story-closeable.sh +195 -0
  25. package/skills/close-story/scripts/compute-scope-divergence.sh +128 -0
  26. package/skills/close-story/scripts/extract-diff-stats.sh +48 -0
  27. package/skills/close-story/scripts/extract-task-diff-stats.sh +97 -0
  28. package/skills/close-story/scripts/update-task-frontmatter.sh +103 -0
  29. package/skills/commit/SKILL.md +30 -3
  30. package/skills/distribute/CONFIG_SCHEMA.md +148 -0
  31. package/skills/distribute/SKILL.md +173 -0
  32. package/skills/distribute/scripts/check-version.sh +74 -0
  33. package/skills/distribute/scripts/commit-version-bump.sh +108 -0
  34. package/skills/distribute/scripts/distribute-changes.sh +381 -0
  35. package/skills/do/SKILL.md +352 -1
  36. package/skills/do/assets/intent-vs-diff-prompt.md +69 -0
  37. package/skills/doc/assets/path-objectives.yaml +13 -0
  38. package/skills/doc-sync/SKILL.md +16 -0
  39. package/skills/doc-sync/assets/doc_targets.md +11 -0
  40. package/skills/idea/SKILL.md +56 -0
  41. package/skills/idea/assets/idea_handoff_template.md +26 -0
  42. package/skills/idea/assets/idea_template.md +3 -0
  43. package/skills/init/SKILL.md +101 -7
  44. package/skills/init/assets/directory_structure.txt +1 -0
  45. package/skills/init/assets/strategy_stub_template.md +38 -0
  46. package/skills/init/assets/workflow_template.json +1 -1
  47. package/skills/init/scripts/apply-project-visibility.sh +176 -0
  48. package/skills/init/scripts/detect-existing-codebase.sh +166 -0
  49. package/skills/init/scripts/init.sh +35 -1
  50. package/skills/jenga/SKILL.md +206 -14
  51. package/skills/jenga/scripts/board-scan.sh +238 -0
  52. package/skills/jenga/scripts/cascade-resolve.sh +297 -0
  53. package/skills/jenga/scripts/render-confirmation.sh +679 -0
  54. package/skills/jenga/scripts/render-picker.sh +439 -0
  55. package/skills/jenga/scripts/resolve-id.sh +367 -0
  56. package/skills/jenga-permission-level/SKILL.md +81 -0
  57. package/skills/proceed/SKILL.md +1 -1
  58. package/skills/publish/SKILL.md +8 -5
  59. package/skills/publish/assets/ci-contract.md +2 -2
  60. package/skills/publish/assets/ownership-matrix.md +1 -1
  61. package/skills/publish/scripts/finalize_changelog.sh +115 -0
  62. package/skills/publish/scripts/generate_release_notes.sh +475 -28
  63. package/skills/publish/scripts/npm_ci_pipeline.sh +44 -6
  64. package/skills/publish/scripts/publish_deploy.sh +38 -8
  65. package/skills/publish/scripts/run_gates.sh +2 -2
  66. package/skills/reconcile/SKILL.md +117 -5
  67. package/skills/reconcile/scripts/detect-unlinked-code.sh +741 -0
  68. package/skills/skillify/assets/init-new/assets/directory_structure.txt +5 -1
  69. package/skills/spinoff/SKILL.md +12 -7
  70. package/skills/todo/SKILL.md +2 -0
  71. package/skills/uncharted/SKILL.md +711 -0
  72. package/skills/uncharted/assets/SEGMENT_PROPOSAL_TEMPLATE.md +129 -0
  73. package/skills/uncharted/assets/UNDERSTANDING_DOC_TEMPLATE.md +160 -0
  74. package/skills/uncharted/scripts/apply-subsystem-cap.sh +573 -0
  75. package/skills/uncharted/scripts/detect-dependencies.sh +732 -0
  76. package/skills/uncharted/scripts/detect-tests.sh +553 -0
  77. package/skills/uncharted/scripts/discover-subsystems.sh +1029 -0
  78. package/skills/uncharted/scripts/enumerate-target.sh +470 -0
  79. package/skills/uncharted/scripts/import-source.sh +517 -0
  80. package/skills/uncharted/scripts/inspect-provenance.sh +573 -0
  81. package/skills/uncharted/scripts/resolve-segment-target.sh +640 -0
  82. package/skills/uncharted/scripts/run-engine.sh +655 -0
  83. package/skills/uncharted/scripts/validate-proposed-items.sh +125 -0
  84. package/skills/uncharted/scripts/write-backfilled-epics.sh +498 -0
  85. package/skills/wtf/SKILL.md +20 -0
  86. package/templates/CHANGELOG_TEMPLATE.md +13 -0
  87. package/templates/PROBLEM_RAPPORT_TEMPLATE.md +4 -1
  88. package/templates/SCRUM_BOARD_SCHEMA.md +206 -10
  89. package/templates/permission-levels/README.md +73 -0
  90. package/templates/permission-levels/level-1-locked.json +71 -0
  91. package/templates/permission-levels/level-2-guarded.json +64 -0
  92. package/templates/permission-levels/level-3-standard.json +62 -0
  93. package/templates/permission-levels/level-4-elevated.json +60 -0
  94. package/templates/permission-levels/level-5-unrestricted.json +58 -0
  95. package/skills/convert/SKILL.md +0 -124
  96. package/skills/convert/convert_cli.py +0 -235
  97. package/skills/convert/tests/sample.csv +0 -4
  98. package/skills/convert/tests/sample.json +0 -5
  99. package/skills/convert/tests/sample.jsonl +0 -3
  100. package/skills/convert/tests/sample.yaml +0 -18
  101. package/skills/convert/tests/sample_obj.csv +0 -2
  102. package/skills/convert/tests/sample_obj.json +0 -9
  103. package/skills/mirror-public/SKILL.md +0 -237
  104. package/skills/mirror-public/assets/config.json +0 -5
  105. package/skills/mirror-public/scripts/mirror.sh +0 -374
  106. package/skills/self-sync/SKILL.md +0 -73
  107. package/skills/self-sync/scripts/run.js +0 -136
  108. package/skills/train/SKILL.md +0 -116
  109. package/skills/train/assets/dashboard-templates/classifiers.html +0 -106
  110. package/skills/train/assets/dashboard-templates/nlp.html +0 -102
  111. package/skills/train/assets/dashboard-templates/transformers.html +0 -98
  112. package/skills/train/assets/results-parsers/__init__.py +0 -9
  113. package/skills/train/assets/results-parsers/classifiers.py +0 -84
  114. package/skills/train/assets/results-parsers/nlp.py +0 -88
  115. package/skills/train/assets/results-parsers/reporter.py +0 -154
  116. package/skills/train/assets/results-parsers/transformers.py +0 -120
  117. package/skills/train/train_cli.py +0 -786
@@ -26,7 +26,7 @@ project/
26
26
  developer_triggers.jsonl Trigger queue for developer agent
27
27
  tester_triggers.jsonl Trigger queue for tester agent
28
28
  project_summary_updates.jsonl Proposed PROJECT_SUMMARY.md edits
29
- .session_handoff.json Transient inter-session handoff (written by agent, consumed by on_session_end.sh)
29
+ handoffs/ Per-session transient handoffs (written by agent, consumed by on_session_end.sh) — see `handoffs/` below
30
30
  rapports/
31
31
  problems/ Problem rapports from developer and tester
32
32
  analysis/ Analysis rapports from tester
@@ -65,6 +65,8 @@ All status fields must use one of the following exact strings:
65
65
  | `Failed` | Tests did not pass |
66
66
  | `Rejected` | Deliberately rejected — not a test failure |
67
67
  | `Blocked` | Cannot proceed; human intervention required |
68
+ | `Backlog` | Epic-level only; queued but not yet prioritized for work |
69
+ | `Done` | Epic-level only; all child stories/tasks closed out |
68
70
 
69
71
  Only the **tester agent** may write status values to story and task files. Only the **scrum master** may write status values to epic files and may update story status as part of rollup.
70
72
 
@@ -86,6 +88,8 @@ dates_previously_completed: # comma-separated list, e.g. 2026-01-15, 2026-03-22
86
88
  reopened_on: # comma-separated list, e.g. 2026-02-01, 2026-04-10
87
89
  reopened_reason: # comma-separated list, e.g. "Scope expanded", "Bug found post-release"
88
90
  docs: [] # optional list of repo-relative documentation paths, e.g. ["README.md", "docs/API.md"]
91
+ epic_scope_approval: false # set to true by the human operator only when any task in this epic has execution_scope: epic
92
+ provenance: # optional; only valid value is `backfilled` (epic reverse-engineered from pre-existing code by `/uncharted onboard`). Omit for normally-authored epics.
89
93
  stories:
90
94
  - E##_S##
91
95
  - E##_S##
@@ -101,6 +105,8 @@ stories:
101
105
  - <Concrete, testable criterion>
102
106
  ```
103
107
 
108
+ > **`epic_scope_approval`** — This field is **set by the human operator only**. Neither the scrum-master nor the developer agent may set it to `true`. It must be `true` before any task with `execution_scope: epic` can be executed. Its absence is equivalent to `false`.
109
+
104
110
  ### Story — `E##_S##_<slug>.md`
105
111
 
106
112
  ```markdown
@@ -116,6 +122,11 @@ dates_previously_completed: # comma-separated list, e.g. 2026-01-15, 2026-03-22
116
122
  reopened_on: # comma-separated list, e.g. 2026-02-01, 2026-04-10
117
123
  reopened_reason: # comma-separated list, e.g. "Scope expanded", "Bug found post-release"
118
124
  docs: [] # optional list of repo-relative documentation paths, e.g. ["README.md", "docs/API.md"]
125
+ crucial_level: # optional; advisory | gated | locked; absence means no elevated caution
126
+ crucial_set_by: # required when crucial_level is set; user | scrum-master | <agent>-escalation
127
+ crucial_note: # required when crucial_level is set; free-text justification
128
+ crucial_declined: # optional; true only; absence means no declined proposal is on record
129
+ crucial_declined_note: # required when crucial_declined: true; free-text: heuristic matched, proposed tier, date declined
119
130
  tasks:
120
131
  - E##_S##_T##
121
132
  - E##_S##_T##
@@ -150,6 +161,17 @@ reopened_on: # comma-separated list, e.g. 2026-02-01, 2026-04-10
150
161
  reopened_reason: # comma-separated list, e.g. "Scope expanded", "Bug found post-release"
151
162
  assigned_to: developer | tester | scrum-master
152
163
  docs: [] # optional list of repo-relative documentation paths, e.g. ["README.md", "docs/API.md"]
164
+ execution_scope: task # task | story | epic | inline; omit for legacy tasks (defaults to task)
165
+ needs_docs: true # boolean; omit for legacy tasks (defaults to true)
166
+ scope_rationale: "" # required when execution_scope is set; must contain a numeric/file-count claim
167
+ jenga_assigned: true # boolean; true = machine-assigned, false = human override
168
+ override_justification: "" # required when jenga_assigned: false
169
+ epic_scope_approval: false # required (as true) when execution_scope: epic; set by human operator only
170
+ crucial_level: # optional; advisory | gated | locked; absence means no elevated caution
171
+ crucial_set_by: # required when crucial_level is set; user | scrum-master | <agent>-escalation
172
+ crucial_note: # required when crucial_level is set; free-text justification
173
+ crucial_declined: # optional; true only; absence means no declined proposal is on record
174
+ crucial_declined_note: # required when crucial_declined: true; free-text: heuristic matched, proposed tier, date declined
153
175
  ---
154
176
 
155
177
  # Task: <Title>
@@ -164,6 +186,23 @@ docs: [] # optional list of repo-relative documentation path
164
186
  - [ ] <Verifiable criterion>
165
187
  ```
166
188
 
189
+ > **Backward compatibility:** Tasks that omit all six new fields (`execution_scope`, `needs_docs`, `scope_rationale`, `jenga_assigned`, `override_justification`, `epic_scope_approval`) are treated as `execution_scope: task` / `needs_docs: true` and are processed without error. Agents must not reject board files that lack these fields.
190
+
191
+ ### Runtime-written task fields
192
+
193
+ These four fields are **not authored by hand**. They are appended to a task's frontmatter after execution and are absent from any task that has not yet run. They are listed here so that tooling — in particular `scripts/validate-board.sh` — recognises them as valid rather than unknown.
194
+
195
+ | Field | Written by | Meaning |
196
+ |---|---|---|
197
+ | `actual_files_changed` | `/close-story` | Count of files changed by the task, extracted from its EST-tagged commits |
198
+ | `actual_lines_delta` | `/close-story` | Net line delta for the task, from the same extraction |
199
+ | `scope_divergence_flag` | `/close-story` | Set when actual diff stats exceed the thresholds that justified the assigned `execution_scope` |
200
+ | `divergence_flag` | `/do` | Set to `true` by the intent-vs-diff check when a `needs_docs: false` task touched unregistered files |
201
+
202
+ All four are advisory and non-blocking — they record evidence for later review and never change a task's Passed/Failed outcome.
203
+
204
+ > `task_changed_files` is **not** a frontmatter field despite the similar name. It lives in the bundle manifest at `project/queue/bundle-<E##_S##>.json`, keyed by task ID.
205
+
167
206
  ---
168
207
 
169
208
  ## Story Format Standards
@@ -172,6 +211,14 @@ docs: [] # optional list of repo-relative documentation path
172
211
 
173
212
  **`dates_previously_completed`, `reopened_on`, `reopened_reason`** — These fields are **only populated when a previously completed item is being reopened and modified**. Leave them blank on first-run items. Each value is a comma-separated list to support multiple reopen cycles.
174
213
 
214
+ ## Planning Fields
215
+
216
+ **`priority`** (stories, optional) — Relative planning priority, e.g. `P0`, `P2`, or a range such as `P0-P1`. Advisory only; no agent behaviour keys off it.
217
+
218
+ **`depends_on`** (stories and tasks, optional) — Board IDs this item depends on, e.g. `E19_S02`. Advisory only.
219
+
220
+ > **Deprecated spellings.** `epic:`, `story:`, `date_added:`, `dependencies:`, and `reopens:` were used on older board files and are **not** valid. Use `epic_id`, `story_id`, `date_created`, `depends_on`, and the reopen tracking fields respectively. All existing files were normalised on 2026-08-19; `scripts/validate-board.sh` rejects the old spellings so the drift cannot reappear.
221
+
175
222
  ## Documentation Provenance Field
176
223
 
177
224
  **`docs`** — Optional YAML list of documentation files affected by the board item. Use repo-relative paths such as `README.md` or `docs/API.md`.
@@ -181,8 +228,118 @@ docs: [] # optional list of repo-relative documentation path
181
228
  - Optionality: existing board files remain valid when `docs` is omitted.
182
229
  - Format: use a YAML list. Inline (`docs: ["README.md"]`) and expanded list styles are both valid.
183
230
 
231
+ ## Epic Provenance Field
232
+
233
+ **`provenance`** (epics only, optional) — Records how the epic came to exist: whether it was
234
+ planned through the normal Jenga workflow or reverse-engineered from code that already existed.
235
+
236
+ - **Valid values:** `backfilled` — the only recognised value.
237
+ - **`provenance: backfilled`** means the epic was generated by `/uncharted onboard` from a
238
+ pre-existing codebase, rather than authored through the normal planning flow (`/pi-plan`,
239
+ `/brainstorm`, `/todo`, or the Scrum Master breaking down a user request).
240
+ - **Absence means normally-authored.** An epic with no `provenance` field was authored through
241
+ the standard planning flow. There is no explicit value for this — omission *is* the signal.
242
+ - **Optional and backward compatible.** Every existing epic omits `provenance` and remains valid.
243
+ `scripts/validate-board.sh` accepts the key when present and never requires it.
244
+ - **Scope:** epics only. Stories and tasks do not carry `provenance`; a backfilled epic's children
245
+ are identified by their parent, not by a marker of their own.
246
+
247
+ ### What `backfilled` implies
248
+
249
+ A backfilled epic describes **code that already exists**. Its Purpose section documents a
250
+ subsystem that was discovered, not a capability to be built. Consequently its stories and tasks
251
+ describe **understanding and integration work** — documenting behaviour, adding missing tests,
252
+ identifying risk areas, bringing the subsystem under board provenance — and **not original
253
+ construction**. Agents and humans reading the board should not treat a backfilled epic's
254
+ Definition of Done as a build plan, and should not assume that an incomplete-looking backfilled
255
+ epic represents unbuilt functionality.
256
+
257
+ ## Execution Scope Fields (Task)
258
+
259
+ These six fields control the execution footprint of a task within the `/jenga` and `/do` workflows. They are **optional** — omitting all six is valid and equivalent to `execution_scope: task` / `needs_docs: true`.
260
+
261
+ **`execution_scope`**
262
+ - Valid values: `task` | `story` | `epic` | `inline`
263
+ - When required: optional; omit for legacy tasks (runtime default: `task`)
264
+ - Description: defines how broadly this task's implementation touches the codebase.
265
+ - `task` — standard single-task scope (default)
266
+ - `story` — task may touch files across multiple tasks in the same story
267
+ - `epic` — task may touch files across stories; requires `epic_scope_approval: true` on the parent epic
268
+ - `inline` — trivial change (e.g. config tweak, comment, schema doc); no execution plan or summary document is needed
269
+
270
+ **`needs_docs`**
271
+ - Valid values: `true` | `false`
272
+ - When required: optional; omit for legacy tasks (runtime default: `true`)
273
+ - Description: when `false`, the developer agent skips writing an execution plan and execution summary for this task. Automatically implied as `false` when `execution_scope: inline`.
274
+
275
+ **`scope_rationale`**
276
+ - Valid values: any non-empty string; must include a measurable claim (e.g. a file count or line count)
277
+ - When required: required when `execution_scope` is explicitly set to any value
278
+ - Description: a brief justification explaining why the chosen scope is appropriate. Validators reject a blank value when `execution_scope` is present.
279
+
280
+ **`jenga_assigned`**
281
+ - Valid values: `true` | `false`
282
+ - When required: optional; omit for legacy tasks (runtime default: `true`)
283
+ - Description: indicates whether the execution scope was assigned by the `/jenga` orchestrator (`true`) or overridden by a human (`false`). When `false`, `override_justification` is required.
284
+
285
+ **`override_justification`**
286
+ - Valid values: any non-empty string
287
+ - When required: required when `jenga_assigned: false`
288
+ - Description: explains why the human operator overrode the machine-assigned scope. Must be non-empty when present.
289
+
290
+ **`epic_scope_approval`**
291
+ - Valid values: `true` | `false`
292
+ - When required: required on the **epic** frontmatter (as `true`) before any task with `execution_scope: epic` may be executed; the task frontmatter carries this field for reference only
293
+ - Description: the authoritative value is always read from the **epic** frontmatter, not the task. This field is **set by the human operator only** — neither the scrum-master nor the developer agent may set it to `true`. Its absence on the epic is equivalent to `false`.
294
+
184
295
  These rules apply to every story file written or amended by the **Scrum Master**. They are enforced at write time by `scripts/validate-story-format.sh`.
185
296
 
297
+ ## Crucial Flag Fields (Story, Task)
298
+
299
+ These three fields let a story or task declare an elevated caution tier — a signal that the item carries more risk than usual and should be handled with extra care during execution and review. They are **optional** — omitting all three is valid and means no elevated caution applies.
300
+
301
+ **`crucial_level`**
302
+ - Valid values: `advisory` | `gated` | `locked`
303
+ - When required: optional; absence means no elevated caution (same "absence is the signal" convention used by `provenance` and the execution-scope fields)
304
+ - Description: declares the item's caution tier.
305
+ - `advisory` — proceed as normal but flag the elevated risk for reviewers
306
+ - `gated` — requires explicit confirmation before execution proceeds
307
+ - `locked` — execution is blocked until the tier is downgraded or cleared by an authorised party
308
+
309
+ **`crucial_set_by`**
310
+ - Valid values: `user` | `scrum-master` | `<agent>-escalation` (e.g. `developer-escalation`, `tester-escalation`)
311
+ - When required: required when `crucial_level` is set
312
+ - Description: records who or what set the caution tier — a human operator, the scrum-master during planning, or an agent escalating mid-execution.
313
+
314
+ **`crucial_note`**
315
+ - Valid values: any non-empty string
316
+ - When required: required whenever `crucial_level` is set (same required-when-present pattern as `scope_rationale`)
317
+ - Description: a free-text justification explaining why the item was assigned this caution tier. Validators reject a blank value when `crucial_level` is present.
318
+
319
+ **Scope: stories and tasks only.** Epics do not carry these fields — an epic's risk gating is already handled by `epic_scope_approval` (see Execution Scope Fields above). A story or task's `crucial_level` is independent of any execution-scope value on the same item.
320
+
321
+ **Backward compatibility.** Existing board files that omit all three fields (`crucial_level`, `crucial_set_by`, `crucial_note`) remain valid, exactly like the `docs` field and the execution-scope fields. Agents must not reject board files that lack them.
322
+
323
+ ## Declined Crucial Proposal Fields (Story, Task)
324
+
325
+ These two fields record that a scrum-master heuristic proposed a `crucial_level` for this specific item (per the "Crucial Level Heuristic Proposal" step in `agents/scrum-master.md`) and the user explicitly declined it in-session. This is the **authoritative decline-tracking mechanism** — `agents/scrum-master.md`'s breakdown step checks it before evaluating the heuristic list again, so a declined proposal is not silently re-surfaced on a later breakdown pass over the same item. They follow the same "optional field, absence is the signal, backward compatible" convention already used by `docs`, `provenance`, and the execution-scope fields, and the same required-when-present pairing already used by `crucial_level`/`crucial_note`.
326
+
327
+ **`crucial_declined`**
328
+ - Valid values: `true` (the only recognised value)
329
+ - When required: optional; absence means no declined proposal is on record for this item — same "absence is the signal" convention used by `provenance`. There is no `false` state to write: an item either has a recorded decline or it doesn't.
330
+ - Description: a boolean marker, deliberately separate from free text, so the scrum-master breakdown step can check a single unambiguous field before re-proposing rather than parsing prose for intent.
331
+
332
+ **`crucial_declined_note`**
333
+ - Valid values: any non-empty string
334
+ - When required: required when `crucial_declined: true` (same required-when-present pattern as `crucial_note`)
335
+ - Description: free-text record of which heuristic(s) matched (from the fixed list in `agents/scrum-master.md`'s "Crucial Level Heuristic Proposal" section), the `crucial_level` tier that was proposed, and the date the user declined it — e.g. `"Declined 2026-08-27: matched 'schema/frontmatter contracts' heuristic, proposed advisory tier; user declined without further reason."` Validators reject a blank value when `crucial_declined` is present.
336
+
337
+ **Scope: stories and tasks only** — same scope as the Crucial Flag Fields above.
338
+
339
+ **Relationship to `crucial_level`.** `crucial_declined` and `crucial_level` are mutually exclusive in the normal flow: if the user *confirms* a proposal, the item gets `crucial_level`/`crucial_set_by: scrum-master`/`crucial_note` and no decline is recorded; if the user *declines*, the item gets `crucial_declined: true`/`crucial_declined_note` and none of the three `crucial_level` fields are set. A human operator may still set `crucial_level` directly on an item that also carries a recorded decline (e.g. deciding later, independent of the scrum-master's proposal, that the item should be flagged) — the fields are not validated as exclusive, only documented as normally following one path or the other.
340
+
341
+ **Backward compatibility.** Existing board files that omit both fields remain valid — no item has ever had a decline recorded before this convention existed, so every pre-existing file is trivially compliant.
342
+
186
343
  ### Acceptance Criteria (`## Acceptance Criteria`)
187
344
 
188
345
  - **Required** — the section must exist in every story file.
@@ -199,7 +356,7 @@ These rules apply to every story file written or amended by the **Scrum Master**
199
356
 
200
357
  Run `scripts/validate-story-format.sh <path-to-story-file>` to verify a story file meets these requirements. The script exits 0 on success and non-zero with a descriptive error message on failure.
201
358
 
202
- Run `scripts/validate-board.sh <path-to-board-file>` to validate epic, story, or task frontmatter keys. The helper accepts optional `docs` annotations and remains backward compatible with existing board files that omit `docs`.
359
+ Run `scripts/validate-board.sh <path-to-board-file>` to validate epic, story, or task frontmatter keys. The helper accepts optional `docs` and (on epics) `provenance` annotations, and remains backward compatible with existing board files that omit them.
203
360
 
204
361
  ---
205
362
 
@@ -215,12 +372,28 @@ Run `scripts/validate-board.sh <path-to-board-file>` to validate epic, story, or
215
372
 
216
373
  ## File Locking (Concurrency Control)
217
374
 
218
- Before writing to any board file, agents must:
375
+ Board file writes (task/story/epic frontmatter updates) are enforced through `scripts/with-lock.sh`, not through agents manually implementing a check-wait-retry convention. The earlier prose-only protocol ("check for a `<filename>.lock` file, wait, retry, then create/delete it yourself") was purely advisory — nothing stopped two writers from both deciding the lock was free at the same instant. `scripts/with-lock.sh` closes that gap with a real mutual-exclusion primitive.
376
+
377
+ **Usage:**
378
+
379
+ ```bash
380
+ scripts/with-lock.sh <target-file> -- <command> [args...]
381
+ ```
382
+
383
+ The script acquires an exclusive lock keyed to `<target-file>`, runs `<command> [args...]` only once the lock is held, and always releases the lock afterward — on success, on failure, and on `INT`/`TERM` signals. Agents (scrum-master, tester) MUST wrap every board file write through this script rather than reading/writing a `.lock` file by hand.
384
+
385
+ **Lock primitive.** The script uses `mkdir "<target-file>.lock.d"` as the exclusivity check, not `flock`. `flock` is a Linux-only (`util-linux`) utility and is not available by default on macOS/BSD — this repo runs on Darwin, so a `flock`-only implementation would silently not work for every contributor on macOS. `mkdir` is atomic on every POSIX platform this project targets: when multiple processes race to create the same directory, exactly one succeeds and every other caller fails immediately (`EEXIST`) — there is no window where two callers can both believe they hold the lock.
386
+
387
+ **Waiting and failure behavior.** If the lock is already held, the script polls (default every 0.2s, `WITH_LOCK_POLL_SECONDS`) until either the lock is released or a timeout elapses (default 30s, `WITH_LOCK_TIMEOUT_SECONDS`). If the timeout elapses first, the script exits `2` **without ever running the wrapped command** — a fail-safe abort, never a silent overwrite. A lock still held after `WITH_LOCK_STALE_SECONDS` (default 60s) is treated as abandoned (e.g. a crashed holder) and reclaimed; reclamation only clears the lock directory, it does not itself grant the lock, so simultaneous reclaim attempts by multiple waiters still cannot let more than one of them proceed — the next `mkdir` in each waiter's loop remains the sole arbiter.
388
+
389
+ **Example — a status write wrapped in the lock:**
390
+
391
+ ```bash
392
+ scripts/with-lock.sh project/board/tasks/E01_S02_T03_example.md -- \
393
+ bash -c 'update_task_status project/board/tasks/E01_S02_T03_example.md Passed'
394
+ ```
219
395
 
220
- 1. Check for a `<filename>.lock` file adjacent to the target file.
221
- 2. If the lock file exists and its modification time is less than 60 seconds ago — wait up to 10 seconds and retry once. If still locked, abort and write a problem rapport.
222
- 3. If no lock exists (or it is stale, older than 60 seconds) — create the lock file, perform the write, then delete the lock file.
223
- 4. Always delete the lock file in success and failure paths. Use a `trap` or equivalent cleanup.
396
+ If a caller cannot acquire the lock within the timeout, treat it the same as the old protocol's "still locked after retry" case: abort the write and write a problem rapport rather than bypassing the script.
224
397
 
225
398
  ---
226
399
 
@@ -233,6 +406,17 @@ Before writing to any board file, agents must:
233
406
  | `security_concern` | developer | `project/rapports/problems/` |
234
407
  | `test_failure` | tester | `project/rapports/problems/` |
235
408
  | `analysis` | tester | `project/rapports/analysis/` |
409
+ | `crucial_escalation` | developer, tester | `project/rapports/problems/` |
410
+
411
+ ### `crucial_escalation` — concrete-reason requirement
412
+
413
+ A `crucial_escalation` rapport is filed mid-task when developer or tester discovers something that changes an item's risk profile and warrants raising its `crucial_level` (see E39 — Crucial Flag). It is routed through the same rapport/trigger queue as every other rapport type (`on_session_end.sh` → `scrum_triggers.jsonl`), since no synchronous interrupt exists for a backgrounded subagent.
414
+
415
+ The rapport's reason **must include at least one concrete, checkable fact** — a specific file/path, an exact error message, a reproduction count, or a quantifiable impact (e.g. "affects 12 downstream tasks", "data loss observed on 2 of 2 reproduction attempts"). A subjective statement alone (e.g. "this seems risky", "this feels important") is **not** acceptable.
416
+
417
+ This is the same numeric-claim bar already established for `scope_rationale` in `agents/scrum-master.md` (see its "scope_rationale — Mandatory Population Rules" section) — reused here deliberately so the concrete-reason standard is consistent across the codebase rather than a new one-off rule invented for this rapport type. Enforcing this rule (rejecting generic text) is scrum-master's responsibility when it reviews the rapport, not something checked at write time.
418
+
419
+ A `crucial_escalation` rapport must also name the target item's ID (`E##`, `E##_S##`, or `E##_S##_T##`) whose `crucial_level` is being escalated. No new field is introduced for this — it uses the rapport's existing "Related Epic" / "Related Story" / "Related Task" header fields (see `templates/PROBLEM_RAPPORT_TEMPLATE.md`).
236
420
 
237
421
  ---
238
422
 
@@ -259,9 +443,21 @@ Before writing to any board file, agents must:
259
443
  |-------------------|---------------------|-------------------------------------------------|
260
444
  | `test_assignment` | on_session_end.sh (from developer handoff) | Implementation complete; run tests against worktree |
261
445
 
262
- ### `.session_handoff.json` — transient, consumed by on_session_end.sh
446
+ ### `handoffs/` — per-session transient handoffs, consumed by on_session_end.sh
447
+
448
+ **Directory:** `project/queue/handoffs/`
449
+
450
+ Replaces the earlier single-slot `project/queue/.session_handoff.json` file (E37_S01_T01). The single fixed path allowed two sessions ending close together — an in-session tester invocation, or concurrent `/jenga` dispatch running multiple developer/tester pairs at once — to both write the same file before either write was consumed; the second write silently clobbered the first, and the loser's handoff (and, in the worst case, the task it represented) was lost with no error. Live incidents are recorded in `PROJECT_SUMMARY.md`'s E37 section.
451
+
452
+ **Filename convention:** `project/queue/handoffs/<agent>-<session_id>-<task_id>.json`
453
+
454
+ - `<agent>` — `scrum-master`, `developer`, or `tester` (matches the handoff body's own `agent` field).
455
+ - `<session_id>` — the writing session's id, verbatim. This alone already makes the path collision-free, since each session has a unique id and writes its terminal handoff exactly once (the "last action of its session").
456
+ - `<task_id>` — the primary task ID (`E##_S##_T##`) this handoff concerns, included for human-readability/debugging. For the scrum-master's batched `task_ids` array, use the first entry, or the literal string `batch` if the array is empty.
457
+
458
+ Each file is written by an agent as the **last action** of its session, and is single-use: consumed and deleted by `on_session_end.sh` (per-file, immediately after routing — see `hooks/on_session_end.sh` section 4) once that session's `SessionEnd` hook fires. A file must not persist once it has been consumed. `project/queue/handoffs/*.json` is git-ignored (E37_S01_T02) precisely to enforce this structurally — the directory itself is kept via `.gitkeep`, but individual handoff files must never become durable git artifacts, since a committed one can no longer be told apart from a live pending signal by inspection alone.
263
459
 
264
- Written by an agent as the **last action** of its session. Consumed and deleted by `on_session_end.sh`. The file must not persist across sessions.
460
+ **Staleness guard (E37_S01_T02):** before routing a `developer` or `tester` handoff, `on_session_end.sh` checks whether the file's `task_id` is already in a terminal board status (`Passed`, `Passed with remarks`, `Rejected`, `Done`, or `Blocked`). If so, the file is deleted without routing — it is treated as a stale leftover (e.g. one that predates this consumer logic, or one that slipped past the git-ignore rule above) rather than a live signal for already-completed work. This closed a real regression found during testing: `project/queue/handoffs/` had accumulated ~25 committed files for already-`Passed` work before this consumer logic existed to clean them up, and a bare generic glob would have resurrected all of them as fresh `test_assignment`/`story_rollup` triggers on the first `SessionEnd` run after this feature landed. The check is not applied to `scrum-master`'s batched `task_ids` handoffs (a `planning_complete` handoff's tasks are freshly created and cannot already be terminal in practice).
265
461
 
266
462
  | Field | Required by | Notes |
267
463
  |---------------|----------------------|------------------------------------------|
@@ -288,7 +484,7 @@ Located at `project/configs/workflow.json`. Scaffolded by `/init` and owned by t
288
484
 
289
485
  ```json
290
486
  {
291
- "statuses": ["Pending", "In Progress", "Passed", "Passed with remarks", "Failed", "Rejected", "Blocked"],
487
+ "statuses": ["Pending", "In Progress", "Passed", "Passed with remarks", "Failed", "Rejected", "Blocked", "Backlog", "Done"],
292
488
  "rapport_types": ["conflict", "implementation_blocker", "security_concern", "test_failure", "analysis"],
293
489
  "paths": {
294
490
  "board": "project/board",
@@ -0,0 +1,73 @@
1
+ # Permission Level Templates
2
+
3
+ > **Distribution note:** This directory is mirrored automatically — no additional wiring is needed. `/self-sync` (`skills/self-sync/scripts/run.js`, `COPY_SET`) and `/distribute` (`distribute.config.json`, `distribute.include`) both already list `templates` as a top-level entry, and both copy that directory recursively, so `templates/permission-levels/` (including this README and all five level JSON files) rides along automatically with every sync/distribute run.
4
+
5
+ This directory holds the five canonical `settings.json` permission templates that back the `/jenga-permission-level` session-switchable permission system: **Locked (1)**, **Guarded (2)**, **Standard (3)**, **Elevated (4)**, and **Unrestricted (5)**.
6
+
7
+ Each level is a full `settings.json` file — `level-1-locked.json` through `level-5-unrestricted.json`. Two fields vary between them: `permissions.deny` and `autoMode.allow`. This table exists so a maintainer can see, at a glance, what each level allows and denies without diffing all 5 JSON files by hand.
8
+
9
+ ## Level Matrix
10
+
11
+ | Level | Name | `permissions.deny` | Rationale |
12
+ |---|---|---|---|
13
+ | 1 | Locked | `rm *`<br>`rmdir *`<br>`dd *`<br>`mkfs *`<br>`shred *`<br>`truncate *`<br>`git push *`<br>`git reset --hard *`<br>`git clean *`<br>`chmod *`<br>`chown *`<br>`sudo *`<br>`su *`<br>`git commit *`<br>`git add *`<br>`git stash *`<br>`npm install *`<br>`npm publish *`<br>`git branch -D *`<br>`git tag *` | Maximum safety. Blocks all destructive file/git ops plus every git *write* op (commit, add, stash, branch delete, tag) and package publish/install — suitable for read-only exploration or highly sensitive sessions. |
14
+ | 2 | Guarded (**permanent default**) | `rm *`<br>`rmdir *`<br>`dd *`<br>`mkfs *`<br>`shred *`<br>`truncate *`<br>`git push *`<br>`git reset --hard *`<br>`git clean *`<br>`chmod *`<br>`chown *`<br>`sudo *`<br>`su *` | Safe default for normal agentic work. Blocks destructive file ops, destructive/history-rewriting git ops, and permission/ownership changes, but allows normal git commit/add/stash and package installs. |
15
+ | 3 | Standard | `dd *`<br>`mkfs *`<br>`shred *`<br>`truncate *`<br>`git push *`<br>`git reset --hard *`<br>`git clean *`<br>`chmod *`<br>`chown *`<br>`sudo *`<br>`su *` | Adds `rm *` / `rmdir *` back (Guarded minus those two) for normal file cleanup during active development, while still blocking pushes and history-rewriting git ops. |
16
+ | 4 | Elevated | `dd *`<br>`mkfs *`<br>`shred *`<br>`truncate *`<br>`chmod *`<br>`chown *`<br>`sudo *`<br>`su *` | Adds `git push *` / `git reset --hard *` / `git clean *` back (Standard minus those three) for active development workflows that need to push and reset branches. |
17
+ | 5 | Unrestricted | `dd *`<br>`mkfs *`<br>`shred *`<br>`truncate *`<br>`sudo *`<br>`su *` | Adds `chmod *` / `chown *` back (Elevated minus those two). Only the non-negotiable always-blocked set (see below) remains denied. |
18
+
19
+ ## `autoMode.allow` by Level
20
+
21
+ `autoMode.allow` lists commands that are auto-approved **without a prompt** when the session is running in auto mode. It is a *friction* dial, not a *capability* dial — `permissions.deny` always wins over it — with one important exception described below.
22
+
23
+ | Level | `autoMode.allow` |
24
+ |---|---|
25
+ | 1 · Locked | `$defaults` |
26
+ | 2 · Guarded | `$defaults` |
27
+ | 3 · Standard | `$defaults` |
28
+ | 4 · Elevated | `$defaults`<br>`Bash(bash skills/mirror-public/scripts/mirror.sh*)` |
29
+ | 5 · Unrestricted | `$defaults`<br>`Bash(bash skills/mirror-public/scripts/mirror.sh*)` |
30
+
31
+ ### Why mirror.sh is gated to level 4+
32
+
33
+ `permissions.deny` matches the command string the agent invokes — it does **not** see what that command spawns in a subprocess. `skills/mirror-public/scripts/mirror.sh` internally runs `git reset --hard origin/<branch>` and `git push origin <branch>` against the public repo. Auto-approving the script by name therefore grants an unprompted push of repository content to a **public** repo that the deny list cannot intercept. (The push itself is a plain fast-forward `git push` — the script's `--force` flag only overrides a pre-flight safety abort, it does not force-push. The risk here is disclosure, not history loss.)
34
+
35
+ Levels 1–3 deny `git push *` and `git reset --hard *`. Auto-approving `mirror.sh` at those levels would silently defeat that intent. Level 4 (Elevated) is the first tier where `git push *` and `git reset --hard *` are permitted directly, so it is the first tier where auto-approving a script that performs them is consistent.
36
+
37
+ **Rule for adding future entries:** before adding a command to `autoMode.allow` at level *n*, check what it executes internally. If it performs an operation that `permissions.deny` blocks at level *n*, it belongs at a higher level instead.
38
+
39
+ ### Every template must carry an `autoMode` key
40
+
41
+ Even the levels that only grant `$defaults` declare the block explicitly. A level switch is a **whole-file overwrite** of `.claude/settings.json` and `.agents/settings.json` — any top-level key present in the destination but absent from the template is silently dropped. A template that omitted `autoMode` would wipe the destination's block on every switch.
42
+
43
+ ## Always Blocked
44
+
45
+ The following commands are **never removed from `permissions.deny` at any level, including level 5 (Unrestricted)**:
46
+
47
+ - `dd *`
48
+ - `mkfs *`
49
+ - `shred *`
50
+ - `truncate *`
51
+ - `sudo *`
52
+ - `su *`
53
+
54
+ These are disk-destructive or privilege-escalation commands with no legitimate use case in an agentic coding session. No permission level — not even Unrestricted — removes them.
55
+
56
+ ## Fields That Never Vary
57
+
58
+ Across all 5 templates:
59
+
60
+ - `defaultMode` stays `"acceptEdits"` at every level.
61
+ - `permissions.allow` stays `["Bash(*)"]` at every level.
62
+ - `env` and `hooks` (`WorktreeCreate`, `WorktreeRemove`, `SessionEnd`) are identical across all 5 files.
63
+
64
+ **Only `permissions.deny` and `autoMode.allow` vary between levels.** Every template is otherwise byte-identical to root `settings.json`, and root `settings.json` matches `level-2-guarded.json` exactly — Guarded being the permanent default.
65
+
66
+ > **Superseded (2026-08-23):** epic E33 originally settled on "`autoMode` is never used at any level" and "only `permissions.deny` varies". That decision was revisited after it emerged that auto-approving `mirror.sh` by script name bypasses the `git push *` deny at levels 1–3 (see the section above). `autoMode.allow` is now a second, deliberately tiered axis.
67
+
68
+ ## Guarded (Level 2) Is the Permanent Default
69
+
70
+ There is no separate "default level" config number. Level 2 (Guarded) is conceptually always the project's permanent default.
71
+
72
+ - To change the **permanent, project-wide default**, a maintainer edits `templates/permission-levels/level-2-guarded.json` directly.
73
+ - Running `/jenga-permission-level <n>` only changes the **current session's** active level (by copying the matching template into `.claude/settings.json` and `.agents/settings.json` and recording the level in `.jenga-permission-level.json`) — it does not modify the permanent default, and the scrum-master resets a session back to Guarded at the start of every session it starts.
@@ -0,0 +1,71 @@
1
+ {
2
+ "defaultMode": "acceptEdits",
3
+ "env": {
4
+ "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
5
+ },
6
+ "autoMode": {
7
+ "allow": [
8
+ "$defaults"
9
+ ]
10
+ },
11
+ "permissions": {
12
+ "allow": [
13
+ "Bash(*)"
14
+ ],
15
+ "deny": [
16
+ "Bash(rm *)",
17
+ "Bash(rmdir *)",
18
+ "Bash(dd *)",
19
+ "Bash(mkfs *)",
20
+ "Bash(shred *)",
21
+ "Bash(truncate *)",
22
+ "Bash(git push *)",
23
+ "Bash(git reset --hard *)",
24
+ "Bash(git clean *)",
25
+ "Bash(chmod *)",
26
+ "Bash(chown *)",
27
+ "Bash(sudo *)",
28
+ "Bash(su *)",
29
+ "Bash(git commit *)",
30
+ "Bash(git add *)",
31
+ "Bash(git stash *)",
32
+ "Bash(npm install *)",
33
+ "Bash(npm publish *)",
34
+ "Bash(git branch -D *)",
35
+ "Bash(git tag *)"
36
+ ]
37
+ },
38
+ "hooks": {
39
+ "WorktreeCreate": [
40
+ {
41
+ "hooks": [
42
+ {
43
+ "type": "command",
44
+ "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\necho \"$DIR\"\n"
45
+ }
46
+ ]
47
+ }
48
+ ],
49
+ "WorktreeRemove": [
50
+ {
51
+ "hooks": [
52
+ {
53
+ "type": "command",
54
+ "command": "\"$(git rev-parse --show-toplevel)/scripts/worktree-remove-guard.sh\"\n"
55
+ }
56
+ ]
57
+ }
58
+ ],
59
+ "SessionEnd": [
60
+ {
61
+ "hooks": [
62
+ {
63
+ "type": "command",
64
+ "async": true,
65
+ "command": ". \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\" && \"$JENGA_PROJECT_DIR\"/.claude/hooks/on_session_end.sh"
66
+ }
67
+ ]
68
+ }
69
+ ]
70
+ }
71
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "defaultMode": "acceptEdits",
3
+ "env": {
4
+ "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
5
+ },
6
+ "autoMode": {
7
+ "allow": [
8
+ "$defaults"
9
+ ]
10
+ },
11
+ "permissions": {
12
+ "allow": [
13
+ "Bash(*)"
14
+ ],
15
+ "deny": [
16
+ "Bash(rm *)",
17
+ "Bash(rmdir *)",
18
+ "Bash(dd *)",
19
+ "Bash(mkfs *)",
20
+ "Bash(shred *)",
21
+ "Bash(truncate *)",
22
+ "Bash(git push *)",
23
+ "Bash(git reset --hard *)",
24
+ "Bash(git clean *)",
25
+ "Bash(chmod *)",
26
+ "Bash(chown *)",
27
+ "Bash(sudo *)",
28
+ "Bash(su *)"
29
+ ]
30
+ },
31
+ "hooks": {
32
+ "WorktreeCreate": [
33
+ {
34
+ "hooks": [
35
+ {
36
+ "type": "command",
37
+ "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\necho \"$DIR\"\n"
38
+ }
39
+ ]
40
+ }
41
+ ],
42
+ "WorktreeRemove": [
43
+ {
44
+ "hooks": [
45
+ {
46
+ "type": "command",
47
+ "command": "\"$(git rev-parse --show-toplevel)/scripts/worktree-remove-guard.sh\"\n"
48
+ }
49
+ ]
50
+ }
51
+ ],
52
+ "SessionEnd": [
53
+ {
54
+ "hooks": [
55
+ {
56
+ "type": "command",
57
+ "async": true,
58
+ "command": ". \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\" && \"$JENGA_PROJECT_DIR\"/.claude/hooks/on_session_end.sh"
59
+ }
60
+ ]
61
+ }
62
+ ]
63
+ }
64
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "defaultMode": "acceptEdits",
3
+ "env": {
4
+ "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
5
+ },
6
+ "autoMode": {
7
+ "allow": [
8
+ "$defaults"
9
+ ]
10
+ },
11
+ "permissions": {
12
+ "allow": [
13
+ "Bash(*)"
14
+ ],
15
+ "deny": [
16
+ "Bash(dd *)",
17
+ "Bash(mkfs *)",
18
+ "Bash(shred *)",
19
+ "Bash(truncate *)",
20
+ "Bash(git push *)",
21
+ "Bash(git reset --hard *)",
22
+ "Bash(git clean *)",
23
+ "Bash(chmod *)",
24
+ "Bash(chown *)",
25
+ "Bash(sudo *)",
26
+ "Bash(su *)"
27
+ ]
28
+ },
29
+ "hooks": {
30
+ "WorktreeCreate": [
31
+ {
32
+ "hooks": [
33
+ {
34
+ "type": "command",
35
+ "command": "NAME=$(jq -r '.name')\n. \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\"\nDIR=\"$JENGA_PROJECT_DIR/.claude/worktrees/$NAME\"\ngit worktree add \"$DIR\" -b \"$NAME\" 2>&1\necho \"$DIR\"\n"
36
+ }
37
+ ]
38
+ }
39
+ ],
40
+ "WorktreeRemove": [
41
+ {
42
+ "hooks": [
43
+ {
44
+ "type": "command",
45
+ "command": "\"$(git rev-parse --show-toplevel)/scripts/worktree-remove-guard.sh\"\n"
46
+ }
47
+ ]
48
+ }
49
+ ],
50
+ "SessionEnd": [
51
+ {
52
+ "hooks": [
53
+ {
54
+ "type": "command",
55
+ "async": true,
56
+ "command": ". \"$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh\" && \"$JENGA_PROJECT_DIR\"/.claude/hooks/on_session_end.sh"
57
+ }
58
+ ]
59
+ }
60
+ ]
61
+ }
62
+ }