@jenga-ai/agent 3.5.0 → 4.0.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 (136) hide show
  1. package/README.md +85 -78
  2. package/agents/developer.md +1 -1
  3. package/agents/scrum-master.md +20 -2
  4. package/agents/tester.md +3 -3
  5. package/hooks/on_session_end.sh +5 -5
  6. package/lib/generate-agent-context.js +2 -2
  7. package/lib/generate-copilot-hooks.js +1 -1
  8. package/lib/generate-skill-allow-list.js +79 -8
  9. package/lib/mirror.js +1 -1
  10. package/lib/postinstall-manifest.js +1 -1
  11. package/lib/skill-allow-list.json +2 -2
  12. package/mcp/help/index.js +8 -17
  13. package/mcp/help/scan.js +73 -0
  14. package/package.json +5 -1
  15. package/project/app/api/lib/resolve-project-root.js +1 -1
  16. package/project/app/api/parsers/knowledge-graph.js +100 -9
  17. package/project/app/api/routes/health.js +36 -0
  18. package/project/app/api/scripts/capture-snapshot.js +9 -6
  19. package/project/app/package.json +4 -0
  20. package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
  21. package/project/app/ui/dist/assets/index-C3oiuli_.js +104 -0
  22. package/project/app/ui/dist/index.html +2 -2
  23. package/project/app/ui/package.json +4 -0
  24. package/project/app/ui/scripts/build-snapshot-html.cjs +63 -2
  25. package/scripts/acquire-concurrency-slot.sh +35 -5
  26. package/scripts/apply-j-prefix.sh +46 -5
  27. package/scripts/build-pages-site.sh +1 -1
  28. package/scripts/check-public-playbook-steps.sh +158 -52
  29. package/scripts/check-publicignore-match.sh +2 -2
  30. package/scripts/compute-deploy-reconcile.sh +5 -5
  31. package/scripts/delete-bare-skill-dirs.sh +329 -0
  32. package/scripts/generate-legacy-shipped-paths.js +2 -2
  33. package/scripts/idea_manager.sh +273 -3
  34. package/scripts/mark-deployed.sh +2 -2
  35. package/scripts/populate-knowledge-graph.entity-resolution.test.js +254 -0
  36. package/scripts/populate-knowledge-graph.js +213 -5
  37. package/scripts/populate-knowledge-graph.staleness.test.js +130 -0
  38. package/scripts/postinstall.js +1 -1
  39. package/scripts/release-concurrency-slot.sh +34 -4
  40. package/scripts/render-ranked-list.sh +270 -0
  41. package/scripts/repoint-dead-bare-path-prose.py +81 -0
  42. package/scripts/repoint-skill-refs.sh +539 -0
  43. package/scripts/rewrite-stale-skill-preambles.py +188 -0
  44. package/scripts/strip-polyfill-frontmatter.py +166 -0
  45. package/scripts/todo_manager.sh +16 -1
  46. package/scripts/validate-typed-object.sh +750 -0
  47. package/scripts/verify-legacy-seed-reconcile.sh +10 -10
  48. package/scripts/verify-postinstall-reconcile.sh +7 -7
  49. package/scripts/write-context-digest.sh +1 -1
  50. package/skills/j-brainstorm/SKILL.md +3 -4
  51. package/skills/j-btw/SKILL.md +3 -4
  52. package/skills/j-clearify/SKILL.md +5 -6
  53. package/skills/j-close-story/SKILL.md +11 -12
  54. package/skills/j-close-story/scripts/check-privatized.sh +4 -4
  55. package/skills/j-close-story/scripts/check-story-closeable.sh +11 -4
  56. package/skills/j-commit/SKILL.md +3 -4
  57. package/skills/j-continue/SKILL.md +5 -6
  58. package/skills/j-deep-dive/SKILL.md +3 -4
  59. package/skills/j-distribute/CONFIG_SCHEMA.md +82 -5
  60. package/skills/j-distribute/SKILL.md +3 -4
  61. package/skills/j-do/SKILL.md +100 -18
  62. package/skills/j-doc/SKILL.md +3 -4
  63. package/skills/j-doc-sync/SKILL.md +4 -4
  64. package/skills/j-dooo/SKILL.md +6 -15
  65. package/skills/j-error/SKILL.md +3 -4
  66. package/skills/j-evaluate/SKILL.md +3 -4
  67. package/skills/j-examplify/SKILL.md +3 -4
  68. package/skills/j-gitignore/SKILL.md +157 -0
  69. package/skills/j-gitignore/assets/jenga-paths.txt +50 -0
  70. package/skills/j-gitignore/scripts/_catalog.sh +105 -0
  71. package/skills/j-gitignore/scripts/audit-gitignore.sh +194 -0
  72. package/skills/j-gitignore/scripts/repair-gitignore.sh +226 -0
  73. package/skills/j-gitignore/scripts/untrack-jenga-files.sh +210 -0
  74. package/skills/j-help/SKILL.md +3 -4
  75. package/skills/j-idea/SKILL.md +80 -9
  76. package/skills/j-idea/assets/idea_template.md +1 -1
  77. package/skills/j-improve/SKILL.md +4 -5
  78. package/skills/j-init/SKILL.md +23 -14
  79. package/skills/j-init/assets/scope-thresholds_template.json +5 -2
  80. package/skills/j-init/scripts/apply-scaffold-visibility.sh +9 -7
  81. package/skills/j-init/scripts/init.sh +4 -4
  82. package/skills/j-jbp/SKILL.md +3 -4
  83. package/skills/j-lgtm/SKILL.md +3 -4
  84. package/skills/j-pi-plan/SKILL.md +3 -4
  85. package/skills/j-playbook/SKILL.md +1 -1
  86. package/skills/j-proceed/SKILL.md +3 -4
  87. package/skills/j-publish/SKILL.md +4 -5
  88. package/skills/j-publish/adapters/npm-ci.md +6 -1
  89. package/skills/j-publish/adapters/npm.md +1 -1
  90. package/skills/j-publish/scripts/generate_release_notes.sh +1 -1
  91. package/skills/j-publish/scripts/npm_stage_inspect.sh +61 -0
  92. package/skills/j-publish/scripts/run_gates.sh +1 -1
  93. package/skills/j-reconcile/SKILL.md +40 -7
  94. package/skills/j-reconcile/assets/report_format.md +11 -0
  95. package/skills/j-reconcile/scripts/detect-unlinked-code.sh +13 -13
  96. package/skills/j-reconcile-origin/SKILL.md +3 -4
  97. package/skills/j-redo/SKILL.md +4 -5
  98. package/skills/j-skillify/SKILL.md +3 -4
  99. package/skills/j-spinoff/SKILL.md +4 -5
  100. package/skills/j-status/SKILL.md +18 -4
  101. package/skills/j-todo/SKILL.md +44 -5
  102. package/skills/j-todo/scripts/argument-is-not-ranked-list.sh +92 -0
  103. package/skills/j-todo/scripts/argument-is-ranked-list.sh +78 -0
  104. package/skills/j-uncharted/SKILL.md +252 -13
  105. package/skills/j-uncharted/scripts/detect-dependencies.sh +80 -22
  106. package/skills/j-uncharted/scripts/detect-tests.sh +1 -1
  107. package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
  108. package/skills/j-uncharted/scripts/elicitation-state.sh +1 -1
  109. package/skills/j-uncharted/scripts/find-scan-baseline.sh +545 -0
  110. package/skills/j-uncharted/scripts/run-engine.sh +36 -2
  111. package/skills/j-uncharted/scripts/validate-proposed-items.sh +1 -1
  112. package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
  113. package/skills/j-wtf/SKILL.md +4 -5
  114. package/skills/jenga/SKILL.md +106 -11
  115. package/skills/jenga/playbooks/board-hygiene.json +32 -0
  116. package/skills/jenga/playbooks/schema.json +73 -6
  117. package/skills/jenga/playbooks/understand-then-commit.json +19 -0
  118. package/skills/jenga/scripts/load-nl-catalog.js +5 -2
  119. package/skills/jenga/scripts/load-nl-catalog.sh +1 -1
  120. package/skills/jenga/scripts/load-playbooks.sh +290 -5
  121. package/skills/jenga/scripts/match-playbook.sh +4 -4
  122. package/skills/jenga/scripts/run-playbook-step.sh +267 -1
  123. package/templates/SCRUM_BOARD_SCHEMA.md +14 -1
  124. package/templates/SKILL_TEMPLATE.md +12 -0
  125. package/templates/permission-levels/level-1-locked.json +1 -1
  126. package/templates/permission-levels/level-2-guarded.json +1 -1
  127. package/templates/permission-levels/level-3-standard.json +1 -1
  128. package/templates/permission-levels/level-4-elevated.json +2 -2
  129. package/templates/permission-levels/level-5-unrestricted.json +2 -2
  130. package/templates/playbook-types.json +40 -6
  131. package/project/app/ui/dist/assets/index-7fj-vllY.js +0 -104
  132. package/project/app/ui/dist/assets/index-CdK3Qrep.css +0 -1
  133. package/scripts/audit-twin-divergence.sh +0 -625
  134. package/scripts/generate-j-alias.sh +0 -333
  135. package/skills/j-dev-done/SKILL.md +0 -53
  136. package/skills/j-dev-done/scripts/classify-commit-outcome.sh +0 -114
@@ -0,0 +1,329 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/delete-bare-skill-dirs.sh — delete the bare-name skill directories and
3
+ # rewrite their surviving j-<name> twins' frontmatter (E50_S15).
4
+ #
5
+ # Purpose
6
+ # -------
7
+ # Performs E50_S15's own irreversible step: for every skills/<name>/ that still has
8
+ # its original, pre-`j-` "bare" directory form alongside its canonical
9
+ # skills/j-<name>/ twin, (1) delete the bare directory outright and (2) rewrite the
10
+ # twin's SKILL.md frontmatter `name:` field from the transitional `j.j-<name>` form
11
+ # to the settled `j.<name>` form (E50_S10's contract). Both steps land together for
12
+ # every entry in one script run: renaming the twin first (while the bare directory
13
+ # still exists) would leave two directories both claiming `j.<name>`; deleting the
14
+ # bare directory first would leave no directory claiming it until the rename
15
+ # catches up. Neither intermediate state is ever produced by --apply, because this
16
+ # script performs every rewrite before it performs any delete (see step 4 below),
17
+ # and only after that first pass completes clean does it touch a filesystem entry.
18
+ #
19
+ # This script does NOT invoke `git commit`. E50_S15_T04 (the sole task allowed to
20
+ # run --apply) is responsible for staging and committing the resulting working-tree
21
+ # state as a single atomic commit immediately afterward — mirroring
22
+ # scripts/repoint-skill-refs.sh and scripts/apply-j-prefix.sh, neither of which
23
+ # commits on the caller's behalf either. Keeping VCS operations at the call site
24
+ # (not buried inside this script) keeps the script's own contract to "leave the
25
+ # working tree in the correct final state or abort with zero writes" — it does not
26
+ # also have to reason about repo-wide staging concerns (what else might already be
27
+ # staged, whether a lock file needs to be excluded, etc).
28
+ #
29
+ # Modes
30
+ # -----
31
+ # --dry-run Report the full computed delete set and, for each entry, the exact
32
+ # frontmatter line change planned. Nothing is written.
33
+ # --apply Perform the deletion + rename in place. Requires the live count to
34
+ # still match EXPECTED_DELETE_COUNT (see below) or the run aborts
35
+ # with zero filesystem writes.
36
+ #
37
+ # Exactly one of --dry-run/--apply is required. Either mode always runs the delta
38
+ # check and the count cross-check FIRST (see below) before touching or reporting on
39
+ # any file.
40
+ #
41
+ # Usage
42
+ # -----
43
+ # scripts/delete-bare-skill-dirs.sh --dry-run
44
+ # scripts/delete-bare-skill-dirs.sh --apply
45
+ #
46
+ # Delete-set computation — never hardcoded
47
+ # ------------------------------------------
48
+ # Computed fresh from disk on every invocation: every directory directly under
49
+ # skills/ that (a) is not itself "j-"-prefixed, (b) is not one of the three
50
+ # permanent exceptions (jenga, jenga-permission-level, index — E50_S06/E50), and
51
+ # (c) has a matching skills/j-<name>/ twin on disk. E50_S15_T01 already showed the
52
+ # live tree can drift from the story's authoring-time snapshot (6 orphan
53
+ # j-<name>-only directories were added after the story was written) — a hardcoded
54
+ # name list would silently miss a future drift the exact same way, so the set is
55
+ # always recomputed, never literal.
56
+ #
57
+ # Count cross-check
58
+ # ------------------
59
+ # EXPECTED_DELETE_COUNT below is the one hardcoded number in this script — not a
60
+ # name list, a single integer recorded by E50_S15_T01's live pre-flight recompute
61
+ # (2026-09-19: 40). If the live computed delete-set size does not equal this
62
+ # number, the script aborts with a non-zero exit and performs zero filesystem
63
+ # writes, in either mode. This catches the case where skills/ drifted again
64
+ # between T01's recompute and this script actually running --apply.
65
+ #
66
+ # Bare-vs-twin delta check
67
+ # --------------------------
68
+ # Before computing the delete set, every bare (non-"j-"-prefixed, non-exception)
69
+ # directory under skills/ MUST have a skills/j-<name>/ twin on disk. Any unpaired
70
+ # bare directory aborts the run (naming it) before any write — mirrors
71
+ # scripts/repoint-skill-refs.sh's identical guard.
72
+ #
73
+ # Frontmatter rewrite
74
+ # ---------------------
75
+ # For every delete-set entry <name>, skills/j-<name>/SKILL.md's frontmatter `name:`
76
+ # field is rewritten from `j.j-<name>` to `j.<name>`. A twin whose frontmatter does
77
+ # not already read exactly `j.j-<name>` is treated as an error (aborts before any
78
+ # write) rather than silently skipped or forced — the whole point of this
79
+ # cross-check is that any twin already in an unexpected state must be looked at by
80
+ # a human, not silently overwritten or silently left behind.
81
+ #
82
+ # Never touches the generated mirrors
83
+ # --------------------------------------
84
+ # This script only ever reads/writes under skills/. It has no code path that
85
+ # touches .claude/skills/ or .agents/skills/ — those are regenerated by
86
+ # /self-sync (E50_S15_T05), never hand-edited here, per CLAUDE.md's standing
87
+ # read-only-mirror rule.
88
+
89
+ set -euo pipefail
90
+
91
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
92
+ # This task's AC requires a clean default-level shellcheck run; SC1091 is an
93
+ # info-level "did not follow sourced file" finding that would otherwise fail that
94
+ # bar even though the sourced file does exist (see -x). Same fix already applied to
95
+ # scripts/apply-j-prefix.sh and scripts/build-pages-site.sh.
96
+ # shellcheck source=lib/resolve-project-dir.sh disable=SC1091
97
+ source "$SCRIPT_DIR/../lib/resolve-project-dir.sh"
98
+
99
+ PROJECT_DIR="$JENGA_PROJECT_DIR"
100
+ SKILLS_DIR="$PROJECT_DIR/skills"
101
+
102
+ # ---- Single named source: directory exception list (mirrors repoint-skill-refs.sh) ----
103
+ DELETE_BARE_SKILL_DIRS_EXCEPTIONS=(jenga jenga-permission-level index)
104
+
105
+ is_exception() {
106
+ local name="$1" ex
107
+ for ex in "${DELETE_BARE_SKILL_DIRS_EXCEPTIONS[@]}"; do
108
+ if [ "$name" = "$ex" ]; then
109
+ return 0
110
+ fi
111
+ done
112
+ return 1
113
+ }
114
+
115
+ # ---- Single named source: expected delete-set count (E50_S15_T01, 2026-09-19) ----
116
+ EXPECTED_DELETE_COUNT=40
117
+
118
+ usage() {
119
+ cat >&2 <<'EOF'
120
+ Usage: delete-bare-skill-dirs.sh (--dry-run|--apply)
121
+
122
+ --dry-run Report the computed delete set and planned frontmatter rewrites.
123
+ Writes nothing.
124
+ --apply Delete every bare directory in the delete set and rewrite its
125
+ twin's frontmatter name: line, in one pass. Aborts with zero
126
+ writes if the live delete-set count does not match the recorded
127
+ expected count.
128
+ EOF
129
+ }
130
+
131
+ MODE=""
132
+
133
+ while [ "$#" -gt 0 ]; do
134
+ case "$1" in
135
+ --dry-run)
136
+ MODE="dry-run"
137
+ shift
138
+ ;;
139
+ --apply)
140
+ MODE="apply"
141
+ shift
142
+ ;;
143
+ -h|--help)
144
+ usage
145
+ exit 0
146
+ ;;
147
+ *)
148
+ echo "delete-bare-skill-dirs.sh: error: unknown argument '$1'" >&2
149
+ usage
150
+ exit 1
151
+ ;;
152
+ esac
153
+ done
154
+
155
+ if [ -z "$MODE" ]; then
156
+ echo "delete-bare-skill-dirs.sh: error: exactly one of --dry-run or --apply is required" >&2
157
+ usage
158
+ exit 1
159
+ fi
160
+
161
+ # ============================================================================
162
+ # 1. Live bare-vs-twin delta check — ALWAYS runs first, before any file I/O.
163
+ # ============================================================================
164
+ BARE_DIRS=()
165
+ TWIN_NAMES=()
166
+ UNPAIRED=()
167
+
168
+ if [ -d "$SKILLS_DIR" ]; then
169
+ for d in "$SKILLS_DIR"/*/; do
170
+ [ -d "$d" ] || continue
171
+ name="$(basename "$d")"
172
+ case "$name" in
173
+ j-*)
174
+ TWIN_NAMES+=("${name#j-}")
175
+ continue
176
+ ;;
177
+ esac
178
+ BARE_DIRS+=("$name")
179
+ done
180
+ fi
181
+
182
+ is_twin_name() {
183
+ local target="$1" n
184
+ for n in "${TWIN_NAMES[@]-}"; do
185
+ [ -n "${n:-}" ] || continue
186
+ if [ "$n" = "$target" ]; then
187
+ return 0
188
+ fi
189
+ done
190
+ return 1
191
+ }
192
+
193
+ for name in "${BARE_DIRS[@]-}"; do
194
+ [ -n "${name:-}" ] || continue
195
+ if is_exception "$name"; then
196
+ continue
197
+ fi
198
+ if ! is_twin_name "$name"; then
199
+ UNPAIRED+=("$name")
200
+ fi
201
+ done
202
+
203
+ echo "== delete-bare-skill-dirs.sh: bare-vs-twin delta =="
204
+ echo " bare directories under skills/ (incl. exceptions): ${#BARE_DIRS[@]}"
205
+ echo " twin directories (skills/j-<name>/): $(printf '%s\n' "${TWIN_NAMES[@]-}" | grep -c . || true)"
206
+ if [ "${#UNPAIRED[@]}" -gt 0 ]; then
207
+ echo " UNPAIRED bare directories (no twin, not on the exception list):"
208
+ for u in "${UNPAIRED[@]}"; do
209
+ echo " - $u"
210
+ done
211
+ echo "delete-bare-skill-dirs.sh: error: the directory(ies) named above have no skills/j-<name>/ twin and are not on the exception list. Nothing was written. Disposition each one before running this script." >&2
212
+ exit 1
213
+ fi
214
+ echo " delta check clean — every bare directory is either excepted or paired with a twin."
215
+ echo
216
+
217
+ # ============================================================================
218
+ # 2. Compute the delete set: every bare, non-excepted directory with a twin.
219
+ # ============================================================================
220
+ DELETE_SET=()
221
+ for name in "${BARE_DIRS[@]-}"; do
222
+ [ -n "${name:-}" ] || continue
223
+ if is_exception "$name"; then
224
+ continue
225
+ fi
226
+ DELETE_SET+=("$name")
227
+ done
228
+
229
+ # Sort for deterministic, reviewable output.
230
+ if [ "${#DELETE_SET[@]}" -gt 0 ]; then
231
+ SORTED_DELETE_SET=()
232
+ while IFS= read -r n; do
233
+ [ -n "$n" ] || continue
234
+ SORTED_DELETE_SET+=("$n")
235
+ done < <(printf '%s\n' "${DELETE_SET[@]}" | sort)
236
+ DELETE_SET=("${SORTED_DELETE_SET[@]}")
237
+ fi
238
+
239
+ echo "== delete-bare-skill-dirs.sh: computed delete set =="
240
+ echo " count: ${#DELETE_SET[@]} (expected: $EXPECTED_DELETE_COUNT)"
241
+ if [ "${#DELETE_SET[@]}" -ne "$EXPECTED_DELETE_COUNT" ]; then
242
+ echo "delete-bare-skill-dirs.sh: error: live delete-set count (${#DELETE_SET[@]}) does not match the recorded expected count ($EXPECTED_DELETE_COUNT, from E50_S15_T01's 2026-09-19 pre-flight recompute). Nothing was written. skills/ has drifted since that recompute -- re-run E50_S15_T01's checks before proceeding." >&2
243
+ exit 1
244
+ fi
245
+ for n in "${DELETE_SET[@]}"; do
246
+ echo " - $n"
247
+ done
248
+ echo " count check clean."
249
+ echo
250
+
251
+ # ============================================================================
252
+ # 3. Pre-validate every twin's current frontmatter name: line before writing
253
+ # anything, in either mode -- an unexpected frontmatter state aborts the
254
+ # whole run rather than being silently skipped or forced.
255
+ # ============================================================================
256
+ PLANNED=() # "name|old_line|new_line" per entry, in DELETE_SET order
257
+
258
+ for name in "${DELETE_SET[@]}"; do
259
+ twin_skill_md="$SKILLS_DIR/j-$name/SKILL.md"
260
+ if [ ! -f "$twin_skill_md" ]; then
261
+ echo "delete-bare-skill-dirs.sh: error: expected twin file not found: $twin_skill_md. Nothing was written." >&2
262
+ exit 1
263
+ fi
264
+ current_line="$(grep -m1 -E '^name:[[:space:]]' "$twin_skill_md" || true)"
265
+ expected_old="name: j.j-${name}"
266
+ new_line="name: j.${name}"
267
+ if [ "$current_line" != "$expected_old" ]; then
268
+ echo "delete-bare-skill-dirs.sh: error: $twin_skill_md's frontmatter name: line reads '$current_line', expected exactly '$expected_old'. Nothing was written. This twin is in an unexpected state -- resolve it manually before re-running." >&2
269
+ exit 1
270
+ fi
271
+ PLANNED+=("${name}|${expected_old}|${new_line}")
272
+ done
273
+
274
+ echo "== delete-bare-skill-dirs.sh: planned frontmatter rewrites (${#PLANNED[@]}) =="
275
+ for entry in "${PLANNED[@]}"; do
276
+ IFS='|' read -r name old_line new_line <<< "$entry"
277
+ echo " skills/j-${name}/SKILL.md: '${old_line}' -> '${new_line}'"
278
+ done
279
+ echo
280
+
281
+ if [ "$MODE" = "dry-run" ]; then
282
+ echo "== delete-bare-skill-dirs.sh: dry-run complete — ${#DELETE_SET[@]} director(ies) would be deleted, ${#PLANNED[@]} frontmatter line(s) would be rewritten =="
283
+ echo " Bare directories that would be deleted:"
284
+ for name in "${DELETE_SET[@]}"; do
285
+ echo " - skills/${name}/"
286
+ done
287
+ echo " No files were written. Run with --apply to perform this (E50_S15_T04 only)."
288
+ exit 0
289
+ fi
290
+
291
+ # ============================================================================
292
+ # 4. --apply: rewrite every twin's frontmatter FIRST (full pass), then delete
293
+ # every bare directory (full pass). Doing all rewrites before any delete
294
+ # means a mid-run failure during the rewrite pass leaves every bare
295
+ # directory still present (recoverable: nothing deleted yet), and a
296
+ # mid-run failure during the delete pass leaves every twin already
297
+ # correctly renamed (recoverable: re-run recomputes a smaller, still-valid
298
+ # delete set next time, since a twin with no more `j.j-` prefix simply
299
+ # won't re-match the pre-validation above for whichever entries already
300
+ # completed). Neither pass order can leave a "two directories claim
301
+ # j.<name>" state or a "no directory claims it" state uncommitted, because
302
+ # a bare directory is never deleted before its own twin's rename has
303
+ # already succeeded.
304
+ # ============================================================================
305
+ echo "== delete-bare-skill-dirs.sh: applying frontmatter rewrites =="
306
+ for entry in "${PLANNED[@]}"; do
307
+ IFS='|' read -r name old_line new_line <<< "$entry"
308
+ twin_skill_md="$SKILLS_DIR/j-${name}/SKILL.md"
309
+ tmp_file="$(mktemp "${twin_skill_md}.rename.XXXXXX")"
310
+ cp -p "$twin_skill_md" "$tmp_file"
311
+ awk -v old="$old_line" -v new="$new_line" '
312
+ $0 == old && !done { print new; done=1; next }
313
+ { print }
314
+ ' "$twin_skill_md" > "$tmp_file"
315
+ mv "$tmp_file" "$twin_skill_md"
316
+ echo " rewrote skills/j-${name}/SKILL.md"
317
+ done
318
+ echo
319
+
320
+ echo "== delete-bare-skill-dirs.sh: deleting bare directories =="
321
+ for name in "${DELETE_SET[@]}"; do
322
+ rm -rf "${SKILLS_DIR:?}/${name:?}"
323
+ echo " deleted skills/${name}/"
324
+ done
325
+ echo
326
+
327
+ echo "== delete-bare-skill-dirs.sh: apply complete — ${#DELETE_SET[@]} director(ies) deleted, ${#PLANNED[@]} frontmatter line(s) rewritten =="
328
+ echo " Working tree now needs a single commit covering both changes (this script does not commit)."
329
+ exit 0
@@ -35,7 +35,7 @@
35
35
  * and unions it with the paths CURRENTLY on disk under this repo's own `skills/`
36
36
  * and `agents/` directories — i.e. "what this release is about to ship" folds into
37
37
  * the running cumulative record. This is the mode wired into the publish pipeline
38
- * (`skills/publish/scripts/npm_pipeline.sh` / `npm_ci_pipeline.sh`, via the
38
+ * (`skills/j-publish/scripts/npm_pipeline.sh` / `npm_ci_pipeline.sh`, via the
39
39
  * `generate:legacy-paths` npm script) so every future publish keeps the list
40
40
  * current with zero network dependency and zero publish-time registry flakiness.
41
41
  *
@@ -57,7 +57,7 @@
57
57
  * "generated_at": "<ISO 8601>",
58
58
  * "package": "@jenga-ai/agent",
59
59
  * "source": "bootstrap-from-registry+incremental" | "incremental",
60
- * "paths": ["agents/developer.md", "skills/do/SKILL.md", ...]
60
+ * "paths": ["agents/developer.md", "skills/j-do/SKILL.md", ...]
61
61
  * }
62
62
  *
63
63
  * `paths` are relative to a mirror root, POSIX-separated, deduped and sorted — matching the same
@@ -3,15 +3,50 @@
3
3
  # Run from the repository root.
4
4
 
5
5
  IDEA_FILE="project/ideas.md"
6
- TEMPLATE="skills/idea/assets/idea_template.md"
6
+ PLANS_DIR="project/documentation/plans"
7
+
8
+ # PACKAGE_ROOT locates the package-shipped idea template (skills/j-idea/assets/).
9
+ # This script always lives at scripts/, one level below the package root, in
10
+ # both layouts:
11
+ # - this monorepo checkout
12
+ # - a consumer install, inside node_modules/@jenga-ai/agent/
13
+ # BASH_SOURCE-derived resolution is exact in both cases (same reasoning as
14
+ # scripts/jenga-permission-level-switch.sh's PACKAGE_ROOT derivation), with a
15
+ # defensive node_modules-relative fallback matching the PKG_ROOT convention in
16
+ # skills/init/scripts/init.sh, in case BASH_SOURCE resolution is ever
17
+ # unavailable (e.g. the script is sourced rather than executed).
18
+ PACKAGE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
19
+ TEMPLATE="$PACKAGE_ROOT/skills/j-idea/assets/idea_template.md"
20
+ if [ ! -f "$TEMPLATE" ]; then
21
+ TEMPLATE="node_modules/@jenga-ai/agent/skills/j-idea/assets/idea_template.md"
22
+ fi
7
23
 
8
24
  usage() {
9
25
  cat >&2 <<EOF
10
26
  Usage: $0 <subcommand> [args]
11
27
 
12
28
  Subcommands:
13
- add "<entry>" Append entry to project/ideas.md (auto-creates from template if missing)
14
- list Print all non-comment, non-blank entries (silent if file missing/empty)
29
+ add "<entry>" Append entry to project/ideas.md (auto-creates from
30
+ template if missing)
31
+ list Print all non-comment, non-blank entries (silent if
32
+ file missing/empty)
33
+ tag "<idea-text>" <TAG> Tag an existing idea line. <TAG> is either
34
+ "PROMOTED:E##_S##[_T##]" or "REJECTED". Errors if the
35
+ line is not found, is ambiguous, or is already tagged.
36
+ get-id "<idea-text>" Print the embedded board ID from a PROMOTED line
37
+ (errors if the line has no PROMOTED tag).
38
+ get-rapport "<idea-text>" Print the source-rapport path recorded on the line via
39
+ a "<!-- rapport: <path> -->" comment. Prints nothing
40
+ (exit 0) if the line has no such comment.
41
+ resolve "<idea-text>" <promoted|rejected> [board_id]
42
+ The full outcome-record mechanism: tags the line, then
43
+ either updates the idea's source rapport (decision,
44
+ date, resulting board ID, [SPIKE]-prefixed title) or,
45
+ if the idea has no source rapport, creates
46
+ project/documentation/plans/<slug>_PROMOTED.md or
47
+ _REJECTED.md with the same content. board_id is
48
+ required for "promoted" and must be omitted for
49
+ "rejected".
15
50
  EOF
16
51
  exit 1
17
52
  }
@@ -23,6 +58,129 @@ real_entries() {
23
58
  | grep -v '^\s*<!--'
24
59
  }
25
60
 
61
+ # find_idea_line <idea-text>
62
+ # Prints "LINE_NUM:CONTENT" for the single line in $IDEA_FILE matching <idea-text> literally.
63
+ # Errors (exit 1) if the file is missing, no line matches, or more than one line matches.
64
+ find_idea_line() {
65
+ local needle="$1"
66
+ local matches
67
+ if [ ! -f "$IDEA_FILE" ]; then
68
+ echo "Error: $IDEA_FILE does not exist" >&2
69
+ return 1
70
+ fi
71
+ matches=$(grep -Fn -- "$needle" "$IDEA_FILE")
72
+ if [ -z "$matches" ]; then
73
+ echo "Error: no line in $IDEA_FILE matches: $needle" >&2
74
+ return 1
75
+ fi
76
+ if [ "$(printf '%s\n' "$matches" | wc -l | tr -d ' ')" -gt 1 ]; then
77
+ echo "Error: idea text matches multiple lines (ambiguous), refine it: $needle" >&2
78
+ return 1
79
+ fi
80
+ printf '%s\n' "$matches"
81
+ }
82
+
83
+ # tag_line <line-num> <tag-text>
84
+ # Appends " <!-- <tag-text> -->" to the given line number of $IDEA_FILE, in place.
85
+ # Uses awk string concatenation (not sed substitution) so arbitrary idea text/paths
86
+ # containing &, /, or other sed-special characters are never re-interpreted.
87
+ tag_line() {
88
+ local linenum="$1"
89
+ local tagtext="$2"
90
+ local tmpfile
91
+ tmpfile="${IDEA_FILE}.tmp.$$"
92
+ awk -v n="$linenum" -v suffix=" <!-- ${tagtext} -->" \
93
+ 'NR==n { $0 = $0 suffix } { print }' "$IDEA_FILE" > "$tmpfile" && mv "$tmpfile" "$IDEA_FILE"
94
+ }
95
+
96
+ # slugify <text> — lowercase, non-alphanumeric runs collapsed to a single '-', trimmed, capped
97
+ slugify() {
98
+ printf '%s' "$1" \
99
+ | tr '[:upper:]' '[:lower:]' \
100
+ | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' \
101
+ | cut -c1-60
102
+ }
103
+
104
+ # extract_rapport_link <line-content> — prints the rapport: path, or nothing if absent
105
+ extract_rapport_link() {
106
+ printf '%s' "$1" | grep -oE '<!-- rapport: [^>]*-->' | sed -E 's/<!-- rapport: *([^ ]+) *-->/\1/'
107
+ }
108
+
109
+ # extract_promoted_id <line-content> — prints the embedded board ID, or nothing if absent
110
+ extract_promoted_id() {
111
+ printf '%s' "$1" | grep -oE '<!-- PROMOTED: [^>]*-->' | sed -E 's/<!-- PROMOTED: *([^ ]+) *-->/\1/'
112
+ }
113
+
114
+ # already_tagged <line-content> — returns 0 (true) if the line already has a terminal tag
115
+ already_tagged() {
116
+ case "$1" in
117
+ *"<!-- PROMOTED"*|*"<!-- REJECTED"*) return 0 ;;
118
+ *) return 1 ;;
119
+ esac
120
+ }
121
+
122
+ # validate_board_id <id> — loose shape check: E<digits> optionally _S<digits> optionally _T<digits>
123
+ validate_board_id() {
124
+ case "$1" in
125
+ E[0-9]*) return 0 ;;
126
+ *) return 1 ;;
127
+ esac
128
+ }
129
+
130
+ # update_rapport_outcome <rapport-path> <decision-label> <date> <board-id> <idea-text>
131
+ # Prefixes the rapport's "# Rapport: ..." title with "[SPIKE] " if not already present, then
132
+ # appends an "## Idea Outcome Record" section with the decision.
133
+ update_rapport_outcome() {
134
+ local rapport_path="$1" decision_label="$2" today="$3" board_id="$4" idea_text="$5"
135
+ local tmpfile
136
+ tmpfile="${rapport_path}.tmp.$$"
137
+
138
+ awk '
139
+ /^# Rapport:/ && $0 !~ /\[SPIKE\]/ {
140
+ sub(/^# Rapport: /, "# Rapport: [SPIKE] ")
141
+ }
142
+ { print }
143
+ ' "$rapport_path" > "$tmpfile" && mv "$tmpfile" "$rapport_path"
144
+
145
+ {
146
+ echo ""
147
+ echo "---"
148
+ echo ""
149
+ echo "## Idea Outcome Record"
150
+ echo ""
151
+ echo "**Decision:** ${decision_label}"
152
+ echo "**Date:** ${today} (UTC)"
153
+ if [ "$decision_label" = "Promoted" ]; then
154
+ echo "**Resulting Board ID:** ${board_id}"
155
+ fi
156
+ echo "**Idea:** ${idea_text}"
157
+ } >> "$rapport_path"
158
+ }
159
+
160
+ # write_fallback_outcome <idea-text> <decision-label> <date> <board-id> <file-suffix>
161
+ # Creates project/documentation/plans/<slug><file-suffix> and prints its path.
162
+ write_fallback_outcome() {
163
+ local idea_text="$1" decision_label="$2" today="$3" board_id="$4" suffix="$5"
164
+ local slug outfile
165
+ slug=$(slugify "$idea_text")
166
+ mkdir -p "$PLANS_DIR"
167
+ outfile="${PLANS_DIR}/${slug}${suffix}"
168
+ {
169
+ echo "# Idea Outcome: ${idea_text}"
170
+ echo ""
171
+ echo "**Decision:** ${decision_label}"
172
+ echo "**Date:** ${today} (UTC)"
173
+ if [ "$decision_label" = "Promoted" ]; then
174
+ echo "**Resulting Board ID:** ${board_id}"
175
+ fi
176
+ echo ""
177
+ echo "## Idea"
178
+ echo ""
179
+ echo "${idea_text}"
180
+ } > "$outfile"
181
+ printf '%s' "$outfile"
182
+ }
183
+
26
184
  case "${1:-}" in
27
185
  add)
28
186
  [ -z "${2:-}" ] && { echo "Error: add requires an entry argument" >&2; exit 1; }
@@ -41,6 +199,118 @@ case "${1:-}" in
41
199
  exit 0
42
200
  ;;
43
201
 
202
+ tag)
203
+ [ -z "${2:-}" ] && { echo "Error: tag requires an idea-text argument" >&2; exit 1; }
204
+ [ -z "${3:-}" ] && { echo "Error: tag requires a TAG argument (PROMOTED:E##_S##[_T##] or REJECTED)" >&2; exit 1; }
205
+ MATCH=$(find_idea_line "$2") || exit 1
206
+ LINENUM=$(printf '%s' "$MATCH" | cut -d: -f1)
207
+ CONTENT=$(printf '%s' "$MATCH" | cut -d: -f2-)
208
+ if already_tagged "$CONTENT"; then
209
+ echo "Error: idea line is already tagged; refusing to double-tag: $CONTENT" >&2
210
+ exit 1
211
+ fi
212
+ RAW_TAG="$3"
213
+ case "$RAW_TAG" in
214
+ PROMOTED:*)
215
+ BOARD_ID="${RAW_TAG#PROMOTED:}"
216
+ if ! validate_board_id "$BOARD_ID"; then
217
+ echo "Error: invalid board ID format in PROMOTED tag: $BOARD_ID (expected E##_S##[_T##] or E##)" >&2
218
+ exit 1
219
+ fi
220
+ TAG_TEXT="PROMOTED: $BOARD_ID"
221
+ ;;
222
+ REJECTED)
223
+ TAG_TEXT="REJECTED"
224
+ ;;
225
+ *)
226
+ echo "Error: TAG must be PROMOTED:E##_S##[_T##] or REJECTED, got: $RAW_TAG" >&2
227
+ exit 1
228
+ ;;
229
+ esac
230
+ tag_line "$LINENUM" "$TAG_TEXT"
231
+ echo "Tagged line $LINENUM: <!-- $TAG_TEXT -->"
232
+ ;;
233
+
234
+ get-id)
235
+ [ -z "${2:-}" ] && { echo "Error: get-id requires an idea-text argument" >&2; exit 1; }
236
+ MATCH=$(find_idea_line "$2") || exit 1
237
+ CONTENT=$(printf '%s' "$MATCH" | cut -d: -f2-)
238
+ ID=$(extract_promoted_id "$CONTENT")
239
+ if [ -z "$ID" ]; then
240
+ echo "Error: no PROMOTED tag found on matching line" >&2
241
+ exit 1
242
+ fi
243
+ echo "$ID"
244
+ ;;
245
+
246
+ get-rapport)
247
+ [ -z "${2:-}" ] && { echo "Error: get-rapport requires an idea-text argument" >&2; exit 1; }
248
+ MATCH=$(find_idea_line "$2") || exit 1
249
+ CONTENT=$(printf '%s' "$MATCH" | cut -d: -f2-)
250
+ extract_rapport_link "$CONTENT"
251
+ exit 0
252
+ ;;
253
+
254
+ resolve)
255
+ [ -z "${2:-}" ] && { echo "Error: resolve requires an idea-text argument" >&2; exit 1; }
256
+ [ -z "${3:-}" ] && { echo "Error: resolve requires a decision argument: promoted|rejected" >&2; exit 1; }
257
+ IDEA_TEXT="$2"
258
+ DECISION="$3"
259
+ BOARD_ID="${4:-}"
260
+
261
+ case "$DECISION" in
262
+ promoted|rejected) : ;;
263
+ *) echo "Error: decision must be 'promoted' or 'rejected', got: $DECISION" >&2; exit 1 ;;
264
+ esac
265
+
266
+ if [ "$DECISION" = "promoted" ]; then
267
+ [ -z "$BOARD_ID" ] && { echo "Error: promoted decision requires a board_id argument (E##_S##[_T##] or E##)" >&2; exit 1; }
268
+ if ! validate_board_id "$BOARD_ID"; then
269
+ echo "Error: invalid board ID format: $BOARD_ID" >&2
270
+ exit 1
271
+ fi
272
+ else
273
+ [ -n "$BOARD_ID" ] && { echo "Error: rejected decision does not take a board_id argument" >&2; exit 1; }
274
+ fi
275
+
276
+ MATCH=$(find_idea_line "$IDEA_TEXT") || exit 1
277
+ LINENUM=$(printf '%s' "$MATCH" | cut -d: -f1)
278
+ CONTENT=$(printf '%s' "$MATCH" | cut -d: -f2-)
279
+
280
+ if already_tagged "$CONTENT"; then
281
+ echo "Error: idea line is already tagged; refusing to re-resolve: $CONTENT" >&2
282
+ exit 1
283
+ fi
284
+
285
+ RAPPORT_PATH=$(extract_rapport_link "$CONTENT")
286
+
287
+ if [ "$DECISION" = "promoted" ]; then
288
+ TAG_TEXT="PROMOTED: $BOARD_ID"
289
+ DECISION_LABEL="Promoted"
290
+ FILE_SUFFIX="_PROMOTED.md"
291
+ else
292
+ TAG_TEXT="REJECTED"
293
+ DECISION_LABEL="Rejected"
294
+ FILE_SUFFIX="_REJECTED.md"
295
+ fi
296
+
297
+ TODAY=$(date -u +%Y-%m-%d)
298
+
299
+ tag_line "$LINENUM" "$TAG_TEXT"
300
+
301
+ if [ -n "$RAPPORT_PATH" ]; then
302
+ if [ ! -f "$RAPPORT_PATH" ]; then
303
+ echo "Error: source rapport not found at $RAPPORT_PATH; idea line was tagged but outcome record was not written" >&2
304
+ exit 1
305
+ fi
306
+ update_rapport_outcome "$RAPPORT_PATH" "$DECISION_LABEL" "$TODAY" "$BOARD_ID" "$IDEA_TEXT"
307
+ echo "Idea line $LINENUM tagged <!-- $TAG_TEXT -->; rapport updated: $RAPPORT_PATH"
308
+ else
309
+ OUTFILE=$(write_fallback_outcome "$IDEA_TEXT" "$DECISION_LABEL" "$TODAY" "$BOARD_ID" "$FILE_SUFFIX")
310
+ echo "Idea line $LINENUM tagged <!-- $TAG_TEXT -->; outcome record created: $OUTFILE"
311
+ fi
312
+ ;;
313
+
44
314
  *)
45
315
  usage
46
316
  ;;
@@ -13,7 +13,7 @@
13
13
  # 2. Derives each eligible ticket's matched EST-tagged commit SHAs (and,
14
14
  # for parity/logging, their unioned touched-file list) using the
15
15
  # identical anchored-grep + union technique
16
- # skills/self-sync/scripts/mark-merged.sh already implements.
16
+ # skills/j-self-sync/scripts/mark-merged.sh already implements.
17
17
  # 3. Checks ANCESTRY, not diff-membership: a ticket is "covered" by a tag
18
18
  # only when EVERY one of its matched commits is an ancestor of that
19
19
  # tag's resolved_private_sha (git merge-base --is-ancestor). A tag
@@ -183,7 +183,7 @@ REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)
183
183
 
184
184
  COMPUTE_SCRIPT="$SCRIPT_DIR/compute-deploy-reconcile.sh"
185
185
  WITH_LOCK_SCRIPT="$REPO_ROOT/scripts/with-lock.sh"
186
- UPDATE_FRONTMATTER_SCRIPT="$REPO_ROOT/skills/close-story/scripts/update-task-frontmatter.sh"
186
+ UPDATE_FRONTMATTER_SCRIPT="$REPO_ROOT/skills/j-close-story/scripts/update-task-frontmatter.sh"
187
187
  MARKER_FILE="$REPO_ROOT/project/data/deploy-reconcile-marker.json"
188
188
 
189
189
  [ -f "$WITH_LOCK_SCRIPT" ] || die "expected script not found: $WITH_LOCK_SCRIPT"