@jenga-ai/agent 3.1.0 → 3.2.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 (44) hide show
  1. package/README.md +3 -3
  2. package/agents/developer.md +15 -15
  3. package/agents/scrum-master.md +17 -17
  4. package/agents/tester.md +25 -15
  5. package/lib/skill-allow-list.json +3 -2
  6. package/package.json +1 -1
  7. package/scripts/audit-twin-divergence.sh +625 -0
  8. package/scripts/build-pages-site.sh +268 -0
  9. package/scripts/check-public-playbook-steps.sh +136 -0
  10. package/skills/j-close-story/SKILL.md +1 -1
  11. package/skills/j-do/SKILL.md +19 -19
  12. package/skills/j-doc-sync/SKILL.md +12 -1
  13. package/skills/j-idea/SKILL.md +1 -1
  14. package/skills/j-init/SKILL.md +5 -4
  15. package/skills/j-init/assets/directory_structure.txt +1 -0
  16. package/skills/j-init/scripts/detect-existing-codebase.sh +2 -2
  17. package/skills/j-init/scripts/init.sh +13 -2
  18. package/skills/j-playbook/SKILL.md +81 -0
  19. package/skills/j-proceed/SKILL.md +1 -1
  20. package/skills/j-publish/SKILL.md +1 -1
  21. package/skills/j-publish/adapters/npm-ci.md +29 -0
  22. package/skills/j-publish/scripts/npm_ci_pipeline.sh +3 -0
  23. package/skills/j-publish/scripts/npm_pipeline.sh +18 -0
  24. package/skills/j-publish/scripts/npm_stage_pipeline.sh +81 -41
  25. package/skills/j-reconcile/SKILL.md +1 -0
  26. package/skills/j-redo/SKILL.md +1 -1
  27. package/skills/j-status/SKILL.md +12 -0
  28. package/skills/j-todo/SKILL.md +2 -2
  29. package/skills/j-uncharted/SKILL.md +8 -7
  30. package/skills/j-uncharted/scripts/elicitation-state.sh +15 -1
  31. package/skills/j-uncharted/scripts/validate-proposed-items.sh +18 -2
  32. package/skills/jenga/SKILL.md +80 -9
  33. package/skills/jenga/playbooks/idea-to-committed.json +20 -0
  34. package/skills/jenga/playbooks/schema.json +42 -0
  35. package/skills/jenga/scripts/detect-nl-intent.sh +179 -0
  36. package/skills/jenga/scripts/load-nl-catalog.js +206 -0
  37. package/skills/jenga/scripts/load-nl-catalog.sh +65 -0
  38. package/skills/jenga/scripts/load-playbooks.sh +1022 -0
  39. package/skills/jenga/scripts/match-playbook.sh +262 -0
  40. package/skills/jenga/scripts/render-playbook-confirmation.sh +517 -0
  41. package/skills/jenga/scripts/run-playbook-step.sh +766 -0
  42. package/skills/jenga-permission-level/SKILL.md +4 -4
  43. package/templates/KNOWLEDGE_GRAPH_STUB_SCHEMA_TEMPLATE.md +128 -0
  44. package/templates/playbook-types.json +8 -0
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: j.playbook
3
+ description: Invoke a specific /jenga playbook directly by ID, skipping natural-language matching entirely and going straight to chain confirmation and execution.
4
+ keywords:
5
+ - run playbook
6
+ - invoke playbook by id
7
+ - playbook direct
8
+ - execute playbook
9
+ examples:
10
+ - "j.playbook brainstorm-to-mirror"
11
+ - "run the understand-then-ship playbook"
12
+ - "invoke playbook by id"
13
+ ---
14
+
15
+ # Playbook — Direct Playbook Invocation by ID
16
+
17
+ ## Purpose
18
+
19
+ `/jenga`'s natural-language branch (`skills/jenga/SKILL.md`) proposes a playbook only when
20
+ free-text intent doesn't cleanly resolve to a single skill and `skills/jenga/scripts/match-playbook.sh`
21
+ finds a confident match. That's the right default when the user doesn't know a playbook exists or
22
+ doesn't know its exact id. `j.playbook <id>` is the other case: the user already knows exactly
23
+ which playbook they want and names it directly — this skill skips `detect-nl-intent.sh` and
24
+ `match-playbook.sh` entirely and goes straight to the same confirmation-and-execution machinery
25
+ `/jenga`'s natural-language branch already uses (`E53_S06_T03`, per story `E53_S06`'s fourth and
26
+ fifth Acceptance Criteria).
27
+
28
+ This skill never re-implements chain confirmation, sequential execution, `forward_from`/`resolve`
29
+ resolution, or composition/conditional handling — all of that is `skills/jenga/SKILL.md`'s
30
+ Natural-language branch steps 5a-5e, reused here by reference. The only genuinely new logic in
31
+ this skill is id resolution (step 1 below).
32
+
33
+ ## Instructions
34
+
35
+ 1. **Resolve the id** — invoke `skills/jenga/scripts/load-playbooks.sh lookup "<id>"`
36
+ (`E53_S06_T02`), where `<id>` is this skill's argument. Branch on the returned `status` field:
37
+
38
+ - **`"not_found"`** — no playbook with that id exists. Tell the user plainly that no such
39
+ playbook was found. As a "did you mean" nudge, you may additionally invoke
40
+ `skills/jenga/scripts/load-playbooks.sh` (no arguments, full-catalog mode) and list the
41
+ available `id`s from its output — use your judgment on whether this is helpful given the
42
+ specific id the user typed. Halt this invocation; do not proceed to step 2.
43
+ - **`"invalid"`** — a file for that id exists but failed load-time validation. Report the
44
+ `reason` field to the user **verbatim** — never a generic "not found" message, since this is
45
+ a materially different situation from `not_found` (the story's explicit distinguishing
46
+ requirement, `E53_S06_T02`). Halt this invocation; do not proceed to step 2.
47
+ - **`"valid"`** — continue to step 2, using the returned `playbook` object's `id`, `name`, and
48
+ `steps` fields. This object has the exact same field shape as one entry from
49
+ `load-playbooks.sh`'s full-catalog output — the same shape `match-playbook.sh`'s
50
+ `playbook_match` result carries `id`/`name`/`steps` in, for `/jenga`'s Natural-language
51
+ branch step 5.
52
+
53
+ 2. **Confirm and execute the chain** — follow `skills/jenga/SKILL.md`'s Natural-language branch
54
+ **step 5, sub-steps a through e, verbatim** (conditional/origin metadata resolution; render and
55
+ confirm the chain via `render-playbook-confirmation.sh`; the reply loop; sequential-runner
56
+ `init`; the execute-in-a-loop procedure covering skip evaluation, `forward_from`/`resolve`
57
+ resolution (`E53_S06_T01`), invocation, `advance`, and halt handling) exactly as written there
58
+ — do not duplicate that prose here. Use the `id`/`name`/`steps` resolved in step 1 above
59
+ wherever that section refers to `match-playbook.sh`'s `playbook_id`/`name`/`steps` output; every
60
+ other detail (including how `forward_from`, `resolve`, conditionals, and composed/nested steps
61
+ are handled) is identical, with no special-casing for this direct-invocation entry point.
62
+
63
+ The one framing difference: step 5b's confirmation prompt need not (and should not) present
64
+ this as a *proposal* the user might not have expected — they named this playbook explicitly by
65
+ id. Otherwise render, confirm, and execute exactly as that section already does.
66
+
67
+ 3. **On a `complete` or `halted` result** (per step 5e-v/5e-vi), report exactly as that section
68
+ already specifies. This skill does not continue into any further phase — there is no `/jenga`
69
+ Phase 1-4 to fall into here, since this is a direct entry point, not `/jenga` itself.
70
+
71
+ ## Edge Cases
72
+
73
+ - **The looked-up playbook contains a `resolve` step or composed/nested steps** — handled
74
+ identically to the natural-language path; no special-casing exists or is ever needed here (per
75
+ story `E53_S06`'s fifth Acceptance Criterion).
76
+ - **The user cancels at the chain confirmation step (step 5c)** — identical posture to
77
+ `/jenga`'s own playbook-confirmation cancellation: halt immediately after relaying the
78
+ cancellation acknowledgement, with no step of the chain executed.
79
+ - **`load-playbooks.sh lookup` itself fails unexpectedly (exit code 2, usage/setup error)** — this
80
+ is an environment/setup problem, not a normal `not_found`/`invalid` result; surface the script's
81
+ stderr output to the user rather than treating it as either playbook-lookup outcome.
@@ -30,7 +30,7 @@ This file is generated/synced by `scripts/generate-j-alias.sh proceed` from `ski
30
30
 
31
31
  3. **Determine the next action**:
32
32
  - If there are tasks in `Pending` or `In Progress` status that have not yet been assigned to the developer, identify them.
33
- - If outstanding tasks are ready for implementation, write a session handoff to `project/queue/handoffs/scrum-master-<session_id>-<task_id>.json` (per-session path — see `templates/SCRUM_BOARD_SCHEMA.md`'s `handoffs/` section; use the first task ID, or `batch` if several) with `"status": "planning_complete"` so that `on_session_end.sh` routes them to the developer queue.
33
+ - If outstanding tasks are ready for implementation, write a session handoff to `project/queue/handoffs/scrum-master-<session_id>-<task_id>.json` (per-session path — see `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)`'s `handoffs/` section; use the first task ID, or `batch` if several) with `"status": "planning_complete"` so that `on_session_end.sh` routes them to the developer queue.
34
34
  - If all tasks are complete, check for epic/story rollup and update board statuses accordingly.
35
35
 
36
36
  4. **Report** a clear summary to the user: what is done, what is in progress, what is next — and which agent will handle it.
@@ -261,7 +261,7 @@ Implementation: `bash skills/j-publish/scripts/generate_release_notes.sh [--targ
261
261
 
262
262
  Release-note rules:
263
263
  - The last publish tag is the highest semver tag on the current branch that also has a matching ledger entry in `project/logs/publish-history.json`.
264
- - **Default target (no `--output`):** the repo-root `CHANGELOG.md` — created from `templates/CHANGELOG_TEMPLATE.md` first if it doesn't exist yet (backward-compat for projects scaffolded before this convention). New Features/Bug Fixes/Other/Completed-task entries since the last publish tag are appended under the existing `## [Unreleased]` heading's subsections; everything else in the file (prior versioned entries, manual edits) is preserved. Entries already present are deduped (matched by commit short-sha or task id), so re-running with no new commits produces zero diff.
264
+ - **Default target (no `--output`):** the repo-root `CHANGELOG.md` — created from `$([ -f templates/CHANGELOG_TEMPLATE.md ] && echo templates/CHANGELOG_TEMPLATE.md || echo node_modules/@jenga-ai/agent/templates/CHANGELOG_TEMPLATE.md)` first if it doesn't exist yet (backward-compat for projects scaffolded before this convention). New Features/Bug Fixes/Other/Completed-task entries since the last publish tag are appended under the existing `## [Unreleased]` heading's subsections; everything else in the file (prior versioned entries, manual edits) is preserved. Entries already present are deduped (matched by commit short-sha or task id), so re-running with no new commits produces zero diff.
265
265
  - **`--output <path>`:** writes a standalone, disposable draft to that path instead (the pre-E36 behavior) — `CHANGELOG.md` is not touched in this mode.
266
266
  - If no prior ledger-backed tag exists, a `--output` draft includes `> First release — full history included`; the standing `CHANGELOG.md` merge mode has no equivalent banner (it just merges full history into `[Unreleased]` like any other run).
267
267
  - Scrum-board enrichment is best-effort only; missing or unreadable board data never fails the command.
@@ -240,6 +240,35 @@ automated and human halves of the flow:
240
240
  `bash skills/j-publish/scripts/npm_stage_inspect.sh approve <stage-id> --otp <otp>`
241
241
  from their own machine. `reject` (same script) is available to either
242
242
  side to discard a staged candidate.
243
+ - **Stage-id capture reads the CI run's own log, not a local `npm stage
244
+ list` call.** Because the actual `npm stage publish --provenance` call
245
+ runs inside the dispatched Actions run, the local `npm_stage_pipeline.sh`
246
+ process never sees npm's real stage-publish output — `STAGE_OUTPUT` for
247
+ an `npm-ci` target is only ever the fixed dispatch-summary string ("staged
248
+ via GitHub Actions workflow run: `<url>`"). Once `gh run watch` reports
249
+ the run finished successfully, phase 5 instead fetches the full run log
250
+ via `gh run view <run-id> --repo <github_repo> --log` and parses that
251
+ text for a `stage id: ...` line (or a JSON payload), using the same
252
+ parsing logic the `npm` target applies to its local output. **`npm stage
253
+ list ... --json` (the `npm` target's third fallback attempt) is never
254
+ invoked for `npm-ci`** — there is no local npm credential for this target
255
+ type, so a local `npm stage list` call would only ever fail with an
256
+ unrelated auth error.
257
+ - **Manual recovery when the log can't be parsed.** If no stage id can be
258
+ parsed from the run log, `npm_stage_pipeline.sh` exits `3`
259
+ (`EXIT_STAGE_FAILURE`) with a message stating that staging on the
260
+ registry likely **succeeded** — the workflow run itself exited 0, so the
261
+ `npm stage publish --provenance` step almost certainly ran — pointing at
262
+ `gh run view <run-id> --repo <github_repo> --log` (or the printed run
263
+ URL) to find the id by hand in the `npm stage publish` step output, and
264
+ printing an already-filled-in recovery command:
265
+ ```
266
+ bash skills/j-publish/scripts/write_ledger_entry.sh <target> npm-ci staged "" \
267
+ --version <version> --config <config> --stage-id <recovered-id> --dist-tag <tag>
268
+ ```
269
+ `<target>`, `<version>`, `<config>`, and `<tag>` are the real, already-known
270
+ values for the run that just happened — only `--stage-id` is left for the
271
+ operator to fill in by hand after reading it out of the run log.
243
272
 
244
273
  Same registry-existence precondition as a normal `npm-ci` deploy: staged
245
274
  publishing only applies to a package that has already had at least one
@@ -191,6 +191,9 @@ jobs:
191
191
  - name: Install dependencies
192
192
  run: npm ci
193
193
 
194
+ - name: Regenerate lib/legacy-shipped-paths.json (E26_S08_T03)
195
+ run: node scripts/generate-legacy-shipped-paths.js || echo "::warning::legacy-shipped-paths generation failed; publishing without an updated list"
196
+
194
197
  - name: Publish to npm
195
198
  run: npm publish --provenance --access ${NPM_ACCESS} --tag ${DIST_TAG}
196
199
 
@@ -207,6 +207,24 @@ if (( DRY_RUN )); then
207
207
  MODE_LABEL="dry-run"
208
208
  fi
209
209
 
210
+ # Regenerate lib/legacy-shipped-paths.json (E26_S08_T03) — incremental, no-network mode:
211
+ # folds this release's own skills/+agents/ tree into the running cumulative record so the
212
+ # artifact this version SHIPS already reflects what it is about to publish, ready to seed
213
+ # a pre-manifest consumer's first manifest on their NEXT upgrade. Runs on both dry-run and
214
+ # live publish (it only writes a local file, never touches the registry) so a dry-run
215
+ # rehearsal surfaces a generation failure too. Best-effort: a failure here must not block
216
+ # a publish, matching this repo's existing fail-toward-doing-nothing posture for generated
217
+ # artifacts (see lib/generate-skill-allow-list.js's equivalent best-effort call sites).
218
+ GENERATE_LEGACY_PATHS_SCRIPT="$REPO_ROOT/scripts/generate-legacy-shipped-paths.js"
219
+ if [[ -f "$GENERATE_LEGACY_PATHS_SCRIPT" ]]; then
220
+ log_info "Regenerating lib/legacy-shipped-paths.json (incremental, no network)…"
221
+ if ! node "$GENERATE_LEGACY_PATHS_SCRIPT"; then
222
+ log_warn "legacy-shipped-paths generation failed; publishing without an updated list"
223
+ fi
224
+ else
225
+ log_warn "scripts/generate-legacy-shipped-paths.js not found; skipping legacy-paths regeneration"
226
+ fi
227
+
210
228
  echo "========== NPM PUBLISH PIPELINE =========="
211
229
  printf 'Package: %s\n' "$PACKAGE_NAME"
212
230
  printf 'Version: %s\n' "$PACKAGE_VERSION"
@@ -359,6 +359,32 @@ if (( DRY_RUN )); then
359
359
  exit 0
360
360
  fi
361
361
 
362
+ # ---------------------------------------------------------------------------
363
+ # Stage-id parsing helper — shared between the `npm` target's locally
364
+ # captured STAGE_OUTPUT and the `npm-ci` target's fetched CI run log. Tries
365
+ # direct-output regex parsing first, then treats the text as JSON. Echoes
366
+ # the recovered id, or nothing if neither matches.
367
+ # ---------------------------------------------------------------------------
368
+ parse_stage_id_from_text() {
369
+ local text="$1"
370
+ local id=""
371
+
372
+ # Direct-output parsing. Tolerant of label variants npm may use
373
+ # ("stage id:", "stageId:", "stage_id="), case-insensitive.
374
+ id="$(printf '%s\n' "${text}" \
375
+ | grep -Eio '\bstage[ _-]?id["'"'"']?[[:space:]]*[:=][[:space:]]*["'"'"']?[A-Za-z0-9._-]+' \
376
+ | head -n 1 \
377
+ | grep -Eo '[A-Za-z0-9._-]+$' || true)"
378
+
379
+ # The text is itself JSON (e.g. if npm's stage publish supports --json the
380
+ # way `npm publish --json` does, or a fenced JSON blob in a CI log).
381
+ if [[ -z "${id}" ]] && printf '%s' "${text}" | jq -e . >/dev/null 2>&1; then
382
+ id="$(printf '%s' "${text}" | jq -r '.id // .stageId // .stage_id // empty' 2>/dev/null || true)"
383
+ fi
384
+
385
+ printf '%s' "${id}"
386
+ }
387
+
362
388
  # ---------------------------------------------------------------------------
363
389
  # Phase 5: capture — parse the stage id
364
390
  # ---------------------------------------------------------------------------
@@ -366,52 +392,66 @@ log_info "[capture] parsing stage id from stage output..."
366
392
 
367
393
  STAGE_ID=""
368
394
 
369
- # Attempt 1: direct-output parsing. Tolerant of label variants npm may use
370
- # ("stage id:", "stageId:", "stage_id="), case-insensitive.
371
- STAGE_ID="$(printf '%s\n' "${STAGE_OUTPUT}" \
372
- | grep -Eio '\bstage[ _-]?id["'"'"']?[[:space:]]*[:=][[:space:]]*["'"'"']?[A-Za-z0-9._-]+' \
373
- | head -n 1 \
374
- | grep -Eo '[A-Za-z0-9._-]+$' || true)"
375
-
376
- # Attempt 2: the captured output is itself JSON (e.g. if npm's stage publish
377
- # supports --json the way `npm publish --json` does).
378
- if [[ -z "${STAGE_ID}" ]] && printf '%s' "${STAGE_OUTPUT}" | jq -e . >/dev/null 2>&1; then
379
- STAGE_ID="$(printf '%s' "${STAGE_OUTPUT}" | jq -r '.id // .stageId // .stage_id // empty' 2>/dev/null || true)"
380
- fi
381
-
382
- # Attempt 3: fall back to `npm stage list <package> --json` and extract the
383
- # most recent matching entry's id. `npm stage list` rejects a version-
384
- # qualified spec ("Version specifiers are not supported for listing staged
385
- # packages") — it only accepts a bare package name — so this must pass
386
- # PACKAGE_NAME, never PACKAGE_SPEC; the version match happens client-side via
387
- # jq below instead (confirmed live on jenga-npm during v1.3.0 staging on
388
- # 2026-09-01, project/todo.md).
389
- if [[ -z "${STAGE_ID}" ]]; then
390
- log_warn "could not parse a stage id directly from stage output; falling back to 'npm stage list --json'..."
391
-
392
- LIST_STATUS=0
393
- LIST_OUTPUT="$(npm stage list "${PACKAGE_NAME}" --json 2>&1)" || LIST_STATUS=$?
394
-
395
- if [[ ${LIST_STATUS} -ne 0 ]]; then
396
- printf '%s\n' "${LIST_OUTPUT}" >&2
397
- printf 'npm stage pipeline: staged successfully but stage id capture failed (npm stage list also exited %s).\n' "${LIST_STATUS}" >&2
395
+ if [[ "${TARGET_TYPE}" == "npm-ci" ]]; then
396
+ # STAGE_OUTPUT for npm-ci is the fixed one-line dispatch-summary string set
397
+ # in Phase 4 above ("staged via GitHub Actions workflow run: <url>") —
398
+ # never npm's real output — so Attempt 1/2 can never match against it. The
399
+ # actual `npm stage publish --provenance` output (including whatever
400
+ # "stage id: ..." line npm prints) lands in the workflow run's own log
401
+ # instead. Fetch it and apply the same parsing logic used for the `npm`
402
+ # target's local STAGE_OUTPUT. `npm stage list --json` (Attempt 3) is
403
+ # never invoked for npm-ci — there is no local npm auth for this target
404
+ # type, so it would only ever produce a misleading secondary failure.
405
+ log_info "[capture] fetching CI run log for run ${RUN_ID}..."
406
+ CI_LOG_OUTPUT="$(gh run view "${RUN_ID}" --repo "${GITHUB_REPO}" --log 2>&1)" || true
407
+ STAGE_ID="$(parse_stage_id_from_text "${CI_LOG_OUTPUT}")"
408
+
409
+ if [[ -z "${STAGE_ID}" ]]; then
410
+ {
411
+ printf 'npm stage pipeline: the GitHub Actions workflow run completed successfully, so staging on the registry likely SUCCEEDED — but the stage id could not be parsed from the run log.\n'
412
+ printf 'Run "gh run view %s --repo %s --log" (or open %s) to find the "stage id: ..." line by hand in the "npm stage publish" step output, then record it manually:\n' "${RUN_ID}" "${GITHUB_REPO}" "${RUN_URL}"
413
+ printf ' bash skills/j-publish/scripts/write_ledger_entry.sh %s %s staged "" --version %s --config %s --stage-id <recovered-id> --dist-tag %s\n' "${TARGET_NAME}" "${TARGET_TYPE}" "${PACKAGE_VERSION}" "${CONFIG_PATH}" "${DIST_TAG}"
414
+ } >&2
398
415
  exit "${EXIT_STAGE_FAILURE}"
399
416
  fi
417
+ else
418
+ STAGE_ID="$(parse_stage_id_from_text "${STAGE_OUTPUT}")"
419
+
420
+ # Attempt 3: fall back to `npm stage list <package> --json` and extract the
421
+ # most recent matching entry's id. `npm stage list` rejects a version-
422
+ # qualified spec ("Version specifiers are not supported for listing staged
423
+ # packages") — it only accepts a bare package name — so this must pass
424
+ # PACKAGE_NAME, never PACKAGE_SPEC; the version match happens client-side
425
+ # via jq below instead (confirmed live on jenga-npm during v1.3.0 staging
426
+ # on 2026-09-01, project/todo.md). `npm` target only — npm-ci never
427
+ # reaches here (see the branch above).
428
+ if [[ -z "${STAGE_ID}" ]]; then
429
+ log_warn "could not parse a stage id directly from stage output; falling back to 'npm stage list --json'..."
430
+
431
+ LIST_STATUS=0
432
+ LIST_OUTPUT="$(npm stage list "${PACKAGE_NAME}" --json 2>&1)" || LIST_STATUS=$?
433
+
434
+ if [[ ${LIST_STATUS} -ne 0 ]]; then
435
+ printf '%s\n' "${LIST_OUTPUT}" >&2
436
+ printf 'npm stage pipeline: staged successfully but stage id capture failed (npm stage list also exited %s).\n' "${LIST_STATUS}" >&2
437
+ exit "${EXIT_STAGE_FAILURE}"
438
+ fi
400
439
 
401
- if printf '%s' "${LIST_OUTPUT}" | jq -e . >/dev/null 2>&1; then
402
- STAGE_ID="$(printf '%s' "${LIST_OUTPUT}" | jq -r --arg pkg "${PACKAGE_NAME}" --arg ver "${PACKAGE_VERSION}" '
403
- ( if (type == "array") then . else (.stages? // .items? // []) end ) as $entries
404
- | [ $entries[]? | select(((.name // .package // "") == $pkg) and ((.version // "") == $ver)) ]
405
- | sort_by(.stagedAt // .staged_at // .created // .createdAt // "")
406
- | last
407
- | (.id // .stageId // .stage_id // empty)
408
- ' 2>/dev/null || true)"
440
+ if printf '%s' "${LIST_OUTPUT}" | jq -e . >/dev/null 2>&1; then
441
+ STAGE_ID="$(printf '%s' "${LIST_OUTPUT}" | jq -r --arg pkg "${PACKAGE_NAME}" --arg ver "${PACKAGE_VERSION}" '
442
+ ( if (type == "array") then . else (.stages? // .items? // []) end ) as $entries
443
+ | [ $entries[]? | select(((.name // .package // "") == $pkg) and ((.version // "") == $ver)) ]
444
+ | sort_by(.stagedAt // .staged_at // .created // .createdAt // "")
445
+ | last
446
+ | (.id // .stageId // .stage_id // empty)
447
+ ' 2>/dev/null || true)"
448
+ fi
409
449
  fi
410
- fi
411
450
 
412
- if [[ -z "${STAGE_ID}" ]]; then
413
- printf 'npm stage pipeline: staged successfully but the stage id could not be captured from either the direct output or "npm stage list --json". Run "npm stage list %s --json" manually to recover it (bare package name — a version-qualified spec is rejected by npm).\n' "${PACKAGE_NAME}" >&2
414
- exit "${EXIT_STAGE_FAILURE}"
451
+ if [[ -z "${STAGE_ID}" ]]; then
452
+ printf 'npm stage pipeline: staged successfully but the stage id could not be captured from either the direct output or "npm stage list --json". Run "npm stage list %s --json" manually to recover it (bare package name — a version-qualified spec is rejected by npm).\n' "${PACKAGE_NAME}" >&2
453
+ exit "${EXIT_STAGE_FAILURE}"
454
+ fi
415
455
  fi
416
456
 
417
457
  echo ""
@@ -3,6 +3,7 @@ name: j.reconcile
3
3
  description: Polyfill alias of the reconcile skill under a collision-safe directory name. Identical behavior to /reconcile — Reconcile the scrum board with actual implementation state. Cross-checks every task's board status against git history and worktrees, merges orphaned worktree branches, demotes unimplemented "Done" items, promotes secretly-implemented items, flags code with no board provenance and offers /uncharted segment for it, and cleans stale entries from todo.md. Use when the board feels out of sync, after a big merge session, when tasks were completed outside the normal workflow, or when todo.md has grown stale. Trigger on phrases like "sync the board", "clean up the board", "reconcile", "board is out of date", "todo is stale", or "check what's really done". Use when the bare /reconcile form is shadowed by another tool's own built-in command of the same name.
4
4
  metadata:
5
5
  prefered_agent: scrum-master
6
+ output_types: text
6
7
  keywords:
7
8
  - j-reconcile
8
9
  - polyfill
@@ -45,7 +45,7 @@ If either part is missing, ask the user to provide it before proceeding.
45
45
  - Run `git --no-pager show <SHA>` to inspect the full diff.
46
46
 
47
47
  **Epic / Story number:**
48
- - Read the matching file under `$(bash scripts/board_resolver.sh)epics/` or `$(bash scripts/board_resolver.sh)stories/` to understand the scope.
48
+ - Read the matching file under `$(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo node_modules/@jenga-ai/agent/scripts/board_resolver.sh)")epics/` or `$(bash "$([ -f scripts/board_resolver.sh ] && echo scripts/board_resolver.sh || echo node_modules/@jenga-ai/agent/scripts/board_resolver.sh)")stories/` to understand the scope.
49
49
  - Use `git --no-pager log --all --oneline --grep="<epic or story title>"` to locate related commits and their diffs.
50
50
 
51
51
  Collect the list of **files originally changed** and the **original intent** of the implementation.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: j.status
3
3
  description: Polyfill alias of the status skill under a collision-safe directory name. Identical behavior to /status — Print a human-readable summary of the entire scrum board — all epics, stories, and tasks with their statuses — plus any open rapports and unprocessed queue triggers. Use when you want a quick overview of project state without reading raw files. Use when the bare /status form is shadowed by another tool's own built-in command of the same name.
4
+ output_types: text
4
5
  keywords:
5
6
  - status
6
7
  - board summary
@@ -38,3 +39,14 @@ This file is generated/synced by `scripts/generate-j-alias.sh status` from `skil
38
39
  7. **Print the summary** following the layout and icon conventions in `assets/output_format.md`.
39
40
 
40
41
  8. If no epics exist, print: `No board items found. Run /pi-plan to define epics or /todo to add items.`
42
+
43
+ ## Scope note: playbook step status (E53_S04_T03 audit)
44
+
45
+ This skill reports board-level status only (steps 2-4 above: epic/story/task `status` from
46
+ `project/board/`). It never reads or reports `/jenga` playbook run state
47
+ (`skills/jenga/scripts/run-playbook-step.sh`'s temp state file, its `step_ready`/`complete`/
48
+ `halted` reports, or per-step `passed`/`failed`/`skipped` outcomes) — a playbook run is ephemeral,
49
+ session-local execution state, not a board item, and has no representation in
50
+ `project/board/`. This is confirmed as intentional, not a gap: `skipped` is scoped strictly to
51
+ playbook-step context and is never written to a task/story's board-level `status` field (see
52
+ `templates/SCRUM_BOARD_SCHEMA.md`'s Status Values table, unmodified by `E53_S04`).
@@ -53,7 +53,7 @@ When `--trivial` is present, the mission is written as a **fully-formed task boa
53
53
 
54
54
  4. **Update project documentation** — Add the mission to the appropriate files under `project/board/epics/` and `project/board/stories/` if applicable.
55
55
  - If the mission involves implementing or modifying a skill, apply the **Skill Implementation Principle — Scripts Over Inline Logic** (see `CLAUDE.md` / `AGENTS.md`): note in the story/task's acceptance criteria that deterministic, repeatable steps must be offloaded to scripts under `skills/<name>/scripts/` (or `scripts/`) rather than encoded as inline agent instructions in `SKILL.md`.
56
- - If the user indicates the mission is high-risk, or explicitly asks to flag it, set an elevated caution tier directly on the story or task frontmatter: `crucial_level` (one of `advisory`, `gated`, `locked` — see `templates/SCRUM_BOARD_SCHEMA.md` for valid values and their meaning), `crucial_set_by: user`, and `crucial_note` capturing the user's stated reason. This user-initiated flag is written immediately — it does not require the confirm-before-write gate, which applies only to scrum-master-*proposed* caution tiers (a separate, heuristic-driven path).
56
+ - If the user indicates the mission is high-risk, or explicitly asks to flag it, set an elevated caution tier directly on the story or task frontmatter: `crucial_level` (one of `advisory`, `gated`, `locked` — see `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)` for valid values and their meaning), `crucial_set_by: user`, and `crucial_note` capturing the user's stated reason. This user-initiated flag is written immediately — it does not require the confirm-before-write gate, which applies only to scrum-master-*proposed* caution tiers (a separate, heuristic-driven path).
57
57
 
58
58
  4.5. **If `--trivial` was passed, create the task now** (skip step 5 — this step writes the todo.md entry itself):
59
59
 
@@ -81,7 +81,7 @@ When `--trivial` is present, the mission is written as a **fully-formed task boa
81
81
 
82
82
  5. **Add to `project/todo.md`** by running (skip this step if step 4.5 already ran):
83
83
  ```
84
- bash scripts/todo_manager.sh add '<mission title>: <Epic no.>_<Story no.>'
84
+ bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" add '<mission title>: <Epic no.>_<Story no.>'
85
85
  ```
86
86
  The epic and story reference is only required if the mission is assigned to one.
87
87
 
@@ -3,6 +3,7 @@ name: j.uncharted
3
3
  description: Polyfill alias of the uncharted skill under a collision-safe directory name. Identical behavior to /uncharted — Investigate code that has no Jenga board provenance — a foreign file, an external source being pulled in, or an entire pre-existing codebase — and give it a consistent understanding document plus proper board representation. Use when the bare /uncharted form is shadowed by another tool's own built-in command of the same name.
4
4
  metadata:
5
5
  prefered_agent: scrum-master
6
+ output_types: text
6
7
  keywords:
7
8
  - "uncharted"
8
9
  - "foreign code"
@@ -117,7 +118,7 @@ Every mode produces exactly one document per target, rendered from `skills/j-unc
117
118
 
118
119
  **Where it goes:** `project/rapports/analysis/`, named `uncharted-<mode>-<slug>-<YYYYMMDDTHHMMSSZ>.md`. Nothing is ever overwritten — a name collision gets a `-2`, `-3`… suffix, so repeated runs against one target leave a diffable history.
119
120
 
120
- This reuses the **existing `analysis` rapport type** already defined in `templates/SCRUM_BOARD_SCHEMA.md`. `/uncharted` introduces **no new rapport type** — if a change to the rapport taxonomy ever seems necessary, that is a schema change to be raised with the scrum-master, not something this skill invents.
121
+ This reuses the **existing `analysis` rapport type** already defined in `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)`. `/uncharted` introduces **no new rapport type** — if a change to the rapport taxonomy ever seems necessary, that is a schema change to be raised with the scrum-master, not something this skill invents.
121
122
 
122
123
  **Fixed structure** — seven headings, never added to, removed, reordered, or renamed, because downstream steps read the document by heading:
123
124
 
@@ -157,7 +158,7 @@ A document whose judgement sections are generic enough to apply to any codebase
157
158
 
158
159
  Since **E20_S08_T03**, `onboard`'s default behavior and `segment --mode investigate` both run a human-in-the-loop **conversational elicitation** instead of (or, for `onboard`, in addition to keeping available) a one-shot deterministic pass. This section defines the mechanics shared by both; each mode's own subsection below only describes what is specific to it.
159
160
 
160
- **What conversational elicitation produces, and how that differs from the Understanding Document above:** the **primary** output is coarse-tier graph nodes/edges — written directly to `project/knowledge-graph/graph.json`, conforming to the stub schema at `project/knowledge-graph/STUB_SCHEMA.md` (E20_S08_T01; this is a throwaway pilot schema, swapped wholesale once E20_S01's real schema lands — do not extend it expecting stability). Every node this flow writes carries `source: "human"`, since it comes from a person confirming or correcting a proposed understanding, not from mechanical extraction. Board representation is a `[ARCH]`-tagged epic, story, or task at whichever level fits the investigated scope (see `templates/SCRUM_BOARD_SCHEMA.md`'s `[ARCH]` — Durable Architectural Inventory convention) — **not** the delivery-shaped epic/story/task proposal `segment --mode delivery` and legacy `onboard` produce, and not the fixed 7-heading Understanding Document either. A written summary is produced only when warranted, filed as the resulting board item's ordinary `-summary.md` — there is no new artifact type or separate "Understanding Document" for conversational output.
161
+ **What conversational elicitation produces, and how that differs from the Understanding Document above:** the **primary** output is coarse-tier graph nodes/edges — written directly to `project/knowledge-graph/graph.json`, conforming to the stub schema at `project/knowledge-graph/STUB_SCHEMA.md` (E20_S08_T01; this is a throwaway pilot schema, swapped wholesale once E20_S01's real schema lands — do not extend it expecting stability). Every node this flow writes carries `source: "human"`, since it comes from a person confirming or correcting a proposed understanding, not from mechanical extraction. Board representation is a `[ARCH]`-tagged epic, story, or task at whichever level fits the investigated scope (see `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)`'s `[ARCH]` — Durable Architectural Inventory convention) — **not** the delivery-shaped epic/story/task proposal `segment --mode delivery` and legacy `onboard` produce, and not the fixed 7-heading Understanding Document either. A written summary is produced only when warranted, filed as the resulting board item's ordinary `-summary.md` — there is no new artifact type or separate "Understanding Document" for conversational output.
161
162
 
162
163
  ### Human-Oracle-Availability Limitation
163
164
 
@@ -252,7 +253,7 @@ A whole-codebase `onboard` conversation, or an investigation of a large director
252
253
 
253
254
  Idempotent — safe to call again on a resumed `<elicitation-id>` without resetting progress. Choose `<elicitation-id>` so it is stable and re-derivable across sessions (e.g. `onboard-<root-slug>-<date>`, or `segment-investigate-<target-slug>`), since a resuming session must be able to reconstruct it to call `init` again.
254
255
  - **`checkpoint` after every converged node and after the Directory Triage confirmation gate** — never only at the end. This is what makes a mid-run pause lossless: `checkpoint --id <id> --json <file>` merges arbitrary progress data (triage results, draft nodes not yet converged, anything else worth surviving a pause) into the state file.
255
- - **`pause` when a session must end before the elicitation has converged.** Immediately after calling `elicitation-state.sh pause --id <elicitation-id>`, write the scrum-master's own `SessionEnd` handoff (per `templates/SCRUM_BOARD_SCHEMA.md`'s `handoffs/` convention) with `status: "elicitation_paused"` and both `elicitation_id` and `state_file` set — `hooks/on_session_end.sh` routes that into an `elicitation_resume` trigger on `scrum_triggers.jsonl`, which the next scrum-master session's Drain Scrum Triggers Queue procedure picks up (`agents/scrum-master.md`).
256
+ - **`pause` when a session must end before the elicitation has converged.** Immediately after calling `elicitation-state.sh pause --id <elicitation-id>`, write the scrum-master's own `SessionEnd` handoff (per `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)`'s `handoffs/` convention) with `status: "elicitation_paused"` and both `elicitation_id` and `state_file` set — `hooks/on_session_end.sh` routes that into an `elicitation_resume` trigger on `scrum_triggers.jsonl`, which the next scrum-master session's Drain Scrum Triggers Queue procedure picks up (`agents/scrum-master.md`).
256
257
  - **On resume**, read `state_file` directly — every converged node, every flagged node, and the checkpoint data (including the confirmed directory-triage lists) are already there. Do not re-run Directory Triage or re-ask about an already-converged node; resume the Convergence Loop only for nodes still `pending` or explicitly deferred.
257
258
  - **`complete` when every candidate has converged, been deferred, or been explicitly accepted past the cap.** The state file is left on disk afterward as an audit trail — nothing currently prunes a completed elicitation's state file.
258
259
 
@@ -341,7 +342,7 @@ A new epic is the last option, not the default, and needs a stated reason. Recor
341
342
 
342
343
  **Step 5 — Draft the proposal** using `skills/j-uncharted/assets/SEGMENT_PROPOSAL_TEMPLATE.md`. Its six headings are fixed; fill every one of them. Two things it will not let you skip:
343
344
 
344
- - **Every proposed task carries `execution_scope` and a non-empty `scope_rationale`** containing a file-count or line-count claim, assigned against `project/configs/scope-thresholds.json` rather than by feel — `inline` at or under 1 file / 20 lines, `story` when tasks share files across the story (up to 5 files), `task` otherwise. Never self-assign `execution_scope: epic`: it requires `epic_scope_approval: true`, which only a human sets. Propose `story` and say the task looks bigger instead. Field semantics live in `templates/SCRUM_BOARD_SCHEMA.md` — read them there, do not paraphrase them here.
345
+ - **Every proposed task carries `execution_scope` and a non-empty `scope_rationale`** containing a file-count or line-count claim, assigned against `project/configs/scope-thresholds.json` rather than by feel — `inline` at or under 1 file / 20 lines, `story` when tasks share files across the story (up to 5 files), `task` otherwise. Never self-assign `execution_scope: epic`: it requires `epic_scope_approval: true`, which only a human sets. Propose `story` and say the task looks bigger instead. Field semantics live in `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)` — read them there, do not paraphrase them here.
345
346
  - **The document's `Open Questions` carry over verbatim**, not re-derived, each with who can answer it and whether it blocks confirmation. A blocking question is resolved *before* the proposal is accepted, because the breakdown depends on its answer.
346
347
  - **Propose everything the board file will contain**, not just titles. Each story's Acceptance Criteria and Definition of Done are proposed at the gate, and a new epic's Purpose and Definition of Done with it — `scripts/validate-story-format.sh` requires those sections, so anything missing here is text `E40_S02_T03` would have to invent after the user has already said yes.
347
348
 
@@ -367,7 +368,7 @@ Options 2 and 3 loop back to Step 5 (or to Step 4 for a re-parent) and re-presen
367
368
 
368
369
  The proposal is the input and the **only** source of content. Everything the board files contain — the epic's Purpose and Definition of Done, each story's `As a …, I want …, so that …` line, Acceptance Criteria, and Definition of Done, each task's `execution_scope` and `scope_rationale` — was proposed at the gate and is transcribed **verbatim**. Nothing is composed, expanded, or improved at write time. If a field looks wrong now, that is a revision (option 2), not an edit in passing: text the user did not read has no business on the board, which is the whole point of the gate.
369
370
 
370
- Shape, field names, and file naming come from `templates/SCRUM_BOARD_SCHEMA.md`. Read them there — do not paraphrase them here. Five things it will not let you skip:
371
+ Shape, field names, and file naming come from `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)`. Read them there — do not paraphrase them here. Five things it will not let you skip:
371
372
 
372
373
  - **Parent before child.** Epic (if new), then stories, then tasks. The schema's Linking Convention makes the epic's `stories[]` and each story's `tasks[]` the authoritative index, so a child written before its parent leaves an index nobody has updated.
373
374
  - **IDs continue the sequence they belong to.** `E##` is the next free epic number board-wide, but `S##` is the next free story number **within its parent epic** and `T##` the next free task number **within its parent story** — that is what the `E##_S##_T##` shape means. Story numbers restarting per epic is normal and expected, not a collision. Nothing catches an error here: `validate-board.sh` checks an ID's *shape*, never its uniqueness or its sequence, so a well-formed wrong number passes every gate in Step 7 and quietly reuses an ID that already meant something else.
@@ -387,7 +388,7 @@ It runs `scripts/validate-board.sh` over every file and `scripts/validate-story-
387
388
 
388
389
  On success, tell the user exactly which files were created, with their IDs.
389
390
 
390
- **Step 8 — Hand off to the standard path.** Queue each new task with the canonical todo owner — `scripts/todo_manager.sh add "<entry>"`, one call per task, referencing the task ID — and the work then proceeds through the ordinary `/todo` → `/do` → developer → tester path, with no special casing anywhere along it. Point the tasks' Description at the understanding document from Step 2; it is the context the developer picking one up would otherwise lack.
391
+ **Step 8 — Hand off to the standard path.** Queue each new task with the canonical todo owner — `` bash "$([ -f scripts/todo_manager.sh ] && echo scripts/todo_manager.sh || echo node_modules/@jenga-ai/agent/scripts/todo_manager.sh)" add "<entry>" ``, one call per task, referencing the task ID — and the work then proceeds through the ordinary `/todo` → `/do` → developer → tester path, with no special casing anywhere along it. Point the tasks' Description at the understanding document from Step 2; it is the context the developer picking one up would otherwise lack.
391
392
 
392
393
  **`/uncharted` writes board files and stops there.** It does not adapt the segment to project conventions, edit or move the code it just analysed, open a worktree, or write an execution plan. That is ordinary developer work, driven by ordinary task files, and it is the developer agent's job — the same as for a task that came from `/brainstorm` or `/pi-plan`. A segment that has reached the board is no longer a special case, and this skill growing its own integration path would be a second, divergent execution route for work the existing one already handles.
393
394
 
@@ -712,7 +713,7 @@ re-running `onboard` with a higher `--cap`.
712
713
  Live as of `E40_S04_T04`. `skills/j-uncharted/scripts/write-backfilled-epics.sh` is the only thing
713
714
  that actually writes backfilled epics to the board — it consumes `apply-subsystem-cap.sh`'s
714
715
  `kept` array and renders one epic file per entry, following the Epic format in
715
- `templates/SCRUM_BOARD_SCHEMA.md` exactly (`provenance: backfilled`, `status: Pending`, an empty
716
+ `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)` exactly (`provenance: backfilled`, `status: Pending`, an empty
716
717
  `stories` array, a Purpose section built from the discovery evidence, and a Definition of Done
717
718
  framed around understanding and integration, never original construction):
718
719
 
@@ -151,7 +151,21 @@ if [ -z "$REPO_ROOT" ]; then
151
151
  exit 2
152
152
  fi
153
153
 
154
- WITH_LOCK="$REPO_ROOT/scripts/with-lock.sh"
154
+ # ─── Resolve with-lock.sh's package root ──────────────────────────────────
155
+ # postinstall.js mirrors only skills/ and agents/ into a consumer's .claude/
156
+ # and .agents/ — scripts/ (which owns with-lock.sh) is never copied there, so
157
+ # this script — itself shipped under skills/j-uncharted/scripts/ and mirrored
158
+ # alongside it — cannot assume "$REPO_ROOT/scripts/with-lock.sh" exists.
159
+ # Mirrors skills/init/scripts/init.sh's PKG_ROOT fallback: prefer a monorepo
160
+ # checkout's sibling scripts/ dir, else fall back to the installed npm
161
+ # package under node_modules/@jenga-ai/agent.
162
+ if [ -f "$SCRIPT_DIR/../../../scripts/with-lock.sh" ]; then
163
+ WITH_LOCK="$SCRIPT_DIR/../../../scripts/with-lock.sh"
164
+ elif [ -f "$REPO_ROOT/node_modules/@jenga-ai/agent/scripts/with-lock.sh" ]; then
165
+ WITH_LOCK="$REPO_ROOT/node_modules/@jenga-ai/agent/scripts/with-lock.sh"
166
+ else
167
+ WITH_LOCK="$REPO_ROOT/scripts/with-lock.sh"
168
+ fi
155
169
  STATE_DIR="$REPO_ROOT/project/queue/elicitation-state"
156
170
  DEFAULT_CAP=5
157
171
 
@@ -48,8 +48,24 @@ SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
48
48
  REPO_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)
49
49
  [ -n "$REPO_ROOT" ] || REPO_ROOT=$(cd -- "$SCRIPT_DIR/../../.." && pwd -P)
50
50
 
51
- BOARD_VALIDATOR="$REPO_ROOT/scripts/validate-board.sh"
52
- STORY_VALIDATOR="$REPO_ROOT/scripts/validate-story-format.sh"
51
+ # ─── Resolve the validators' package root ─────────────────────────────────
52
+ # postinstall.js mirrors only skills/ and agents/ into a consumer's .claude/
53
+ # and .agents/ — scripts/ (which owns both validators) is never copied there,
54
+ # so this script — itself shipped under skills/j-uncharted/scripts/ and mirrored
55
+ # alongside it — cannot assume "$REPO_ROOT/scripts/..." exists. Mirrors
56
+ # skills/init/scripts/init.sh's PKG_ROOT fallback (same pattern already
57
+ # applied to this skill's elicitation-state.sh WITH_LOCK resolution): prefer
58
+ # a monorepo checkout's sibling scripts/ dir, else fall back to the installed
59
+ # npm package under node_modules/@jenga-ai/agent.
60
+ if [ -f "$SCRIPT_DIR/../../../scripts/validate-board.sh" ]; then
61
+ VALIDATOR_ROOT="$SCRIPT_DIR/../../../scripts"
62
+ elif [ -f "$REPO_ROOT/node_modules/@jenga-ai/agent/scripts/validate-board.sh" ]; then
63
+ VALIDATOR_ROOT="$REPO_ROOT/node_modules/@jenga-ai/agent/scripts"
64
+ else
65
+ VALIDATOR_ROOT="$REPO_ROOT/scripts"
66
+ fi
67
+ BOARD_VALIDATOR="$VALIDATOR_ROOT/validate-board.sh"
68
+ STORY_VALIDATOR="$VALIDATOR_ROOT/validate-story-format.sh"
53
69
 
54
70
  usage() {
55
71
  echo "Usage: $(basename "$0") <board-file> [more-files...]" >&2