@jenga-ai/agent 3.1.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +52 -12
  2. package/agents/developer.md +31 -16
  3. package/agents/scrum-master.md +18 -17
  4. package/agents/tester.md +25 -15
  5. package/bin/jenga.js +10 -0
  6. package/lib/commands/dashboard.js +92 -0
  7. package/lib/skill-allow-list.json +7 -2
  8. package/package.json +21 -2
  9. package/project/app/api/lib/resolve-project-root.js +120 -0
  10. package/project/app/api/package.json +16 -0
  11. package/project/app/api/parsers/architecture.js +72 -0
  12. package/project/app/api/parsers/board.js +141 -0
  13. package/project/app/api/parsers/documentation.js +125 -0
  14. package/project/app/api/parsers/git-log.js +52 -0
  15. package/project/app/api/parsers/ideas.js +62 -0
  16. package/project/app/api/parsers/knowledge-graph.js +73 -0
  17. package/project/app/api/parsers/lib/markdown-dir-reader.js +163 -0
  18. package/project/app/api/parsers/rapports.js +148 -0
  19. package/project/app/api/parsers/todo.js +179 -0
  20. package/project/app/api/response.js +47 -0
  21. package/project/app/api/routes/architecture.js +23 -0
  22. package/project/app/api/routes/board.js +46 -0
  23. package/project/app/api/routes/documentation.js +24 -0
  24. package/project/app/api/routes/health.js +25 -0
  25. package/project/app/api/routes/history.js +55 -0
  26. package/project/app/api/routes/rapports.js +24 -0
  27. package/project/app/api/scripts/capture-snapshot.js +294 -0
  28. package/project/app/api/server.js +112 -0
  29. package/project/app/api/types.js +40 -0
  30. package/project/app/package.json +21 -0
  31. package/project/app/ui/dist/assets/index-7fj-vllY.js +104 -0
  32. package/project/app/ui/dist/assets/index-CdK3Qrep.css +1 -0
  33. package/project/app/ui/dist/index.html +13 -0
  34. package/project/app/ui/package.json +23 -0
  35. package/project/app/ui/scripts/build-snapshot-html.cjs +214 -0
  36. package/project/app/ui/scripts/dashboard-open.cjs +88 -0
  37. package/project/app/ui/scripts/dashboard-start.cjs +87 -0
  38. package/scripts/acquire-concurrency-slot.sh +220 -0
  39. package/scripts/audit-twin-divergence.sh +625 -0
  40. package/scripts/check-public-playbook-steps.sh +136 -0
  41. package/scripts/compute-deploy-reconcile.sh +439 -0
  42. package/scripts/jenga-permission-level-switch.sh +19 -3
  43. package/scripts/mark-deployed.sh +532 -0
  44. package/scripts/populate-knowledge-graph.js +429 -0
  45. package/scripts/release-concurrency-slot.sh +129 -0
  46. package/scripts/validate-board.sh +60 -2
  47. package/scripts/verify-consumer-install.sh +470 -0
  48. package/skills/j-close-story/SKILL.md +1 -1
  49. package/skills/j-cloud-connect/SKILL.md +95 -0
  50. package/skills/j-cloud-connect/scripts/configure-backend.sh +267 -0
  51. package/skills/j-cloud-connect/scripts/install-rclone.sh +153 -0
  52. package/skills/j-dashboard/SKILL.md +144 -0
  53. package/skills/j-dashboard/scripts/launch.sh +121 -0
  54. package/skills/j-dashboard/scripts/resolve-app-dir.sh +164 -0
  55. package/skills/j-dashboard/scripts/snapshot.sh +267 -0
  56. package/skills/j-dashboard-share/SKILL.md +96 -0
  57. package/skills/j-dashboard-share/scripts/upload-snapshot.sh +173 -0
  58. package/skills/j-do/SKILL.md +19 -19
  59. package/skills/j-doc-sync/SKILL.md +12 -1
  60. package/skills/j-idea/SKILL.md +1 -1
  61. package/skills/j-init/SKILL.md +5 -4
  62. package/skills/j-init/assets/directory_structure.txt +1 -0
  63. package/skills/j-init/scripts/detect-existing-codebase.sh +2 -2
  64. package/skills/j-init/scripts/init.sh +13 -2
  65. package/skills/j-playbook/SKILL.md +93 -0
  66. package/skills/j-playbook-new/SKILL.md +155 -0
  67. package/skills/j-playbook-new/scripts/playbook-new.sh +332 -0
  68. package/skills/j-proceed/SKILL.md +1 -1
  69. package/skills/j-publish/SKILL.md +1 -1
  70. package/skills/j-publish/adapters/npm-ci.md +29 -0
  71. package/skills/j-publish/scripts/npm_ci_pipeline.sh +9 -0
  72. package/skills/j-publish/scripts/npm_pipeline.sh +18 -0
  73. package/skills/j-publish/scripts/npm_stage_pipeline.sh +81 -41
  74. package/skills/j-reconcile/SKILL.md +1 -0
  75. package/skills/j-redo/SKILL.md +1 -1
  76. package/skills/j-status/SKILL.md +12 -0
  77. package/skills/j-todo/SKILL.md +2 -2
  78. package/skills/j-uncharted/SKILL.md +8 -7
  79. package/skills/j-uncharted/scripts/validate-proposed-items.sh +18 -2
  80. package/skills/jenga/SKILL.md +55 -16
  81. package/skills/jenga/playbooks/idea-to-committed.json +20 -0
  82. package/skills/jenga/playbooks/schema.json +1 -1
  83. package/skills/jenga/scripts/load-nl-catalog.js +22 -6
  84. package/skills/jenga/scripts/load-playbooks.sh +968 -41
  85. package/skills/jenga/scripts/match-playbook.sh +1 -1
  86. package/skills/jenga/scripts/render-playbook-confirmation.sh +162 -8
  87. package/skills/jenga/scripts/run-playbook-step.sh +535 -42
  88. package/skills/jenga-permission-level/SKILL.md +4 -4
  89. package/templates/KNOWLEDGE_GRAPH_STUB_SCHEMA_TEMPLATE.md +128 -0
  90. package/templates/SCRUM_BOARD_SCHEMA.md +18 -6
  91. package/templates/playbook-types.json +8 -0
  92. package/skills/jenga/playbooks/brainstorm-to-mirror.json +0 -22
@@ -0,0 +1,155 @@
1
+ ---
2
+ name: j.playbook-new
3
+ description: Guided wizard that walks you through authoring a new project-local playbook — id, name, description, keywords, examples, and an ordered list of skills — validates every input against the real catalogs, and self-validates the written file before reporting success.
4
+ keywords:
5
+ - new playbook
6
+ - create playbook
7
+ - author playbook
8
+ - project playbook
9
+ - custom playbook
10
+ examples:
11
+ - "I want to create my own playbook"
12
+ - "help me author a new project-local playbook"
13
+ - "set up a custom workflow chain for my project"
14
+ - "j.playbook-new"
15
+ ---
16
+
17
+ # Playbook New — Guided Playbook-Authoring Wizard
18
+
19
+ ## Purpose
20
+
21
+ `skills/jenga/scripts/load-playbooks.sh` merges two playbook sources into one catalog: the
22
+ framework-owned `skills/jenga/playbooks/` tree, and a project-owned `project/.playbooks/`
23
+ directory (`E53_S09_T01`). This skill is the guided authoring path for the second source — it
24
+ walks the user step by step through the required fields, validates each one against the real,
25
+ live catalogs (never a hand-maintained list of skill names or existing playbook ids), writes
26
+ `project/.playbooks/<id>.json`, and re-validates the just-written file via
27
+ `skills/jenga/scripts/load-playbooks.sh lookup` before ever declaring success. Hand-editing the
28
+ written JSON file remains the escape hatch for anything this wizard doesn't author (see the "v1
29
+ scope cut" note below).
30
+
31
+ **All deterministic work — slug/uniqueness validation, catalog lookups, and the JSON write
32
+ itself — lives in `skills/j-playbook-new/scripts/playbook-new.sh`** (per `CLAUDE.md`'s "Scripts
33
+ Over Inline Logic" principle). This skill's own job is only to run the conversational loop around
34
+ that script and interpret its JSON results — it never re-implements any validation or catalog
35
+ logic inline.
36
+
37
+ **v1 scope cut (deliberate, matches `E53_S09`'s story-level scope cut):** this wizard authors
38
+ plain, ordered, bare-string skill-name chains only — no `forward_from`, `resolve`, `conditional`,
39
+ or `playbook`-type (composition) `StepObject` fields. Hand-edit the written file directly if you
40
+ need any of those.
41
+
42
+ **Scope note — keywords and examples.** The playbook schema
43
+ (`skills/jenga/playbooks/schema.json`) requires every playbook to carry non-empty `keywords` and
44
+ `examples` arrays, exactly like every existing built-in playbook file already does — these are
45
+ what let `/jenga`'s own natural-language matching (`match-playbook.sh`) ever propose this playbook
46
+ from a free-text request. This wizard therefore asks for both, in addition to id/name/description/
47
+ skill-list, so the result is a genuinely useful, matchable playbook rather than one that merely
48
+ parses.
49
+
50
+ ## Instructions
51
+
52
+ 1. **Ask for an id** — a short, stable, kebab-case identifier (e.g. `my-release-flow`). Validate
53
+ it by running:
54
+ ```
55
+ skills/j-playbook-new/scripts/playbook-new.sh validate-id "<id>"
56
+ ```
57
+ Parse the single JSON object printed to stdout:
58
+ - `{"valid": true}` — continue to step 2.
59
+ - `{"valid": false, "reason": "..."}` — show the `reason` to the user verbatim and re-prompt
60
+ for a different id. Do not proceed until a validation call returns `valid: true`.
61
+
62
+ 2. **Ask for a `name`** — a short, human-readable display name (e.g. "My Release Flow"), shown to
63
+ users in confirmation prompts and routing output, mirroring every existing playbook's `name`
64
+ field. No script validation needed beyond "non-empty" — re-prompt if the user gives an empty
65
+ answer.
66
+
67
+ 3. **Ask for a `description`** — one sentence explaining what this playbook accomplishes
68
+ end-to-end. Re-prompt if empty.
69
+
70
+ 4. **Ask for `keywords`** — one or more short phrases (1-3 words each) a user might type that
71
+ should match this playbook, mirroring every existing playbook's `keywords` field (see
72
+ `skills/jenga/playbooks/brainstorm-to-mirror.json` for a concrete example of the expected
73
+ shape and specificity). Accept them one at a time or as a single comma-separated batch —
74
+ your judgment, whichever the user's response shape suggests. Require at least one.
75
+
76
+ 5. **Ask for `examples`** — one or more natural-language example prompts a user might type that
77
+ should resolve to this playbook (again, mirror the existing built-in playbooks' style and
78
+ level of specificity). Require at least one; at least one example should plausibly span the
79
+ full breadth of the playbook's steps, not just its first one, so it's distinguishable from a
80
+ single-skill match — use your judgment coaching the user toward this if their first example is
81
+ too narrow.
82
+
83
+ 6. **Ask for an ordered list of skill names** — one at a time, or as a single ordered batch —
84
+ your judgment based on how the user responds. For **each** name entered, validate it by
85
+ running:
86
+ ```
87
+ skills/j-playbook-new/scripts/playbook-new.sh validate-skill "<name>"
88
+ ```
89
+ Parse the JSON result:
90
+ - `{"valid": true}` — accept it into the ordered list and continue.
91
+ - `{"valid": false, "reason": "..."}` — show the `reason` verbatim and re-prompt for that
92
+ position in the list (do not silently drop it or guess a correction).
93
+
94
+ Once the user signals they're done adding skills, require **at least 2** total (a playbook is a
95
+ chain — a single-skill "playbook" isn't a meaningful use of this mechanism). If fewer than 2
96
+ were entered, tell the user this and continue prompting for more.
97
+
98
+ 7. **Write the playbook.** Assemble the JSON payload from steps 1-6:
99
+ ```json
100
+ {
101
+ "id": "<id>",
102
+ "name": "<name>",
103
+ "description": "<description>",
104
+ "keywords": ["<keyword 1>", "..."],
105
+ "examples": ["<example 1>", "..."],
106
+ "steps": ["<skill 1>", "<skill 2>", "..."]
107
+ }
108
+ ```
109
+ Pipe it to stdin of:
110
+ ```
111
+ skills/j-playbook-new/scripts/playbook-new.sh write
112
+ ```
113
+ Parse the JSON result:
114
+ - `{"written": true, "path": "..."}` — continue to step 8.
115
+ - `{"written": false, "reason": "..."}` — show the `reason` to the user verbatim. This should
116
+ only happen if something changed between validation and write (e.g. a race, or a shape
117
+ issue this wizard's own prompts didn't already catch) — do not silently retry; tell the user
118
+ what failed and, if it's fixable (e.g. the id collided after all), loop back to the relevant
119
+ earlier step.
120
+
121
+ 8. **Self-validate before declaring success — never skip this step.** Run:
122
+ ```
123
+ skills/jenga/scripts/load-playbooks.sh lookup "<id>"
124
+ ```
125
+ Branch on the returned `status`:
126
+ - **`"valid"`** — report success to the user: the playbook was written to
127
+ `project/.playbooks/<id>.json` and is confirmed loadable. Mention it can now be invoked via
128
+ `j.playbook <id>` or matched naturally through `j.jenga`.
129
+ - **`"invalid"`** — report the `reason` field to the user **verbatim** — never a generic
130
+ failure message. This is a real defect (the write succeeded but load-time validation still
131
+ rejects it) — do not claim success.
132
+ - **`"not_found"`** — report to the user that the write appears to have silently failed (the
133
+ file the wizard just wrote could not be found by the loader) — this would indicate an
134
+ environment problem (e.g. a different project root being resolved by the two scripts), not a
135
+ normal outcome. Never claim success.
136
+
137
+ Under no circumstances report success to the user without having seen `"status": "valid"` from
138
+ this exact call.
139
+
140
+ ## Edge Cases
141
+
142
+ - **The user wants to add `forward_from`/`resolve`/`conditional`/composition to a step.** Tell
143
+ them this wizard doesn't author those fields (v1 scope cut) and that they can hand-edit
144
+ `project/.playbooks/<id>.json` afterward — the schema supports these fields identically
145
+ regardless of which directory a playbook file lives in.
146
+ - **The id collides with a project playbook that already exists on disk but is currently
147
+ invalid** (e.g. a hand-edited file with a JSON syntax error) — `validate-id` still rejects it (it
148
+ checks raw file existence, not just catalog membership) rather than silently overwriting a file
149
+ the user may not realize is broken.
150
+ - **A skill name the user enters exists under multiple forms in the catalog** (e.g. both a bare
151
+ and a `j-`-prefixed directory, during this repo's ongoing `E50` naming-contract transition) —
152
+ accept whichever exact form the user typed if `validate-skill` reports it valid; this wizard
153
+ does not impose a preference between forms the catalog itself doesn't distinguish.
154
+ - **The user cancels mid-wizard** — do not write anything; only step 7 ever touches disk, and only
155
+ once a complete, locally-validated payload exists.
@@ -0,0 +1,332 @@
1
+ #!/usr/bin/env bash
2
+ # ---------------------------------------------------------------------------
3
+ # skills/j-playbook-new/scripts/playbook-new.sh
4
+ #
5
+ # Deterministic helper behind the `j.playbook-new` guided wizard (E53_S09_T02). Per CLAUDE.md's
6
+ # "Scripts Over Inline Logic" principle, every mechanical step of the wizard -- id slug/uniqueness
7
+ # validation, skill-name validation against the real catalog, and the JSON write itself -- lives
8
+ # here rather than as inline agent prose in skills/j-playbook-new/SKILL.md. The agent driving the
9
+ # wizard calls this script once per step and interprets its JSON result; it never re-implements
10
+ # any of this logic itself.
11
+ #
12
+ # Reuses, never re-implements, the two existing single-source-of-truth catalogs:
13
+ # - skills/jenga/scripts/load-playbooks.sh (merged builtin+project playbook catalog, E53_S09_T01)
14
+ # - skills/jenga/scripts/load-nl-catalog.sh (the real, generated skill catalog /jenga's own
15
+ # natural-language matching already uses)
16
+ #
17
+ # ---------------------------------------------------------------------------
18
+ # USAGE
19
+ # ---------------------------------------------------------------------------
20
+ # playbook-new.sh validate-id <id>
21
+ #
22
+ # Checks <id> is slug-safe (the same pattern skills/jenga/playbooks/schema.json requires:
23
+ # ^[a-z0-9]+(-[a-z0-9]+)*$) and does not collide with any id already present in the merged catalog
24
+ # (skills/jenga/scripts/load-playbooks.sh, no arguments -- built-in and project sources both) OR an
25
+ # existing (possibly currently invalid, and therefore catalog-invisible) file at
26
+ # project/.playbooks/<id>.json. Prints exactly one JSON object to stdout:
27
+ # {"valid": true}
28
+ # {"valid": false, "reason": "<human-readable reason>"}
29
+ # Exit 0 for BOTH outcomes -- mirrors load-playbooks.sh's own `lookup` mode convention of
30
+ # reserving a non-zero exit for usage/setup errors only, never for a normal negative validation
31
+ # result the caller is expected to branch on.
32
+ #
33
+ # playbook-new.sh validate-skill <name>
34
+ #
35
+ # Checks <name> is a real, currently-loadable skill per skills/jenga/scripts/load-nl-catalog.sh's
36
+ # generated catalog -- the exact source /jenga's own natural-language matching already uses, never
37
+ # a hand-maintained list. Prints:
38
+ # {"valid": true}
39
+ # {"valid": false, "reason": "..."}
40
+ # Exit 0 for both outcomes, same convention as validate-id.
41
+ #
42
+ # playbook-new.sh write
43
+ #
44
+ # Reads a single JSON object from stdin:
45
+ # {"id": "...", "name": "...", "description": "...", "keywords": ["..."],
46
+ # "examples": ["..."], "steps": ["...", "..."]}
47
+ # Pre-validates it against the same required-field / non-empty-list / steps-shape rules
48
+ # skills/jenga/playbooks/schema.json declares (id slug pattern, all six required fields present,
49
+ # keywords/examples non-empty lists of non-empty strings, steps a list of >= 2 non-empty strings --
50
+ # this wizard authors bare-string steps only, per this task's v1 scope cut) BEFORE writing anything
51
+ # to disk. This is a defensive, redundant pre-check only -- the actual source of truth for validity
52
+ # remains load-playbooks.sh's own load-time validation, which the wizard's own self-validation step
53
+ # (SKILL.md step 8) always runs afterward regardless of this result. Creates project/.playbooks/ if
54
+ # it does not yet exist, and refuses to overwrite an already-existing file for the same id. On
55
+ # success, writes project/.playbooks/<id>.json (pretty-printed) and prints
56
+ # {"written": true, "path": "<absolute path>"}. On any shape/overwrite failure, prints
57
+ # {"written": false, "reason": "..."} and writes nothing. Exit 0 for both outcomes.
58
+ #
59
+ # ---------------------------------------------------------------------------
60
+ # PROJECT ROOT RESOLUTION
61
+ # ---------------------------------------------------------------------------
62
+ # Honors JENGA_PLAYBOOKS_TEST_ROOT -- the SAME override variable load-playbooks.sh itself defines
63
+ # (see that script's header "TESTING OVERRIDE"), not a second, script-specific one -- so a fixture
64
+ # pointing this script at a throwaway project root also makes load-playbooks.sh (invoked internally
65
+ # by validate-id, and by the wizard's own later self-validation step) resolve project/.playbooks/
66
+ # under that same root. Never set this variable in a real invocation.
67
+ #
68
+ # ---------------------------------------------------------------------------
69
+ # EXIT CODES
70
+ # ---------------------------------------------------------------------------
71
+ # 0 a result JSON object was printed to stdout (whether valid:true/false or written:true/false)
72
+ # 2 usage error, or a real setup failure (missing python3, the sibling load-playbooks.sh /
73
+ # load-nl-catalog.sh scripts could not be located, one of them exited non-zero, or the
74
+ # project/.playbooks/ directory could not be created)
75
+ #
76
+ # ---------------------------------------------------------------------------
77
+
78
+ set -euo pipefail
79
+
80
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
81
+ LOAD_PLAYBOOKS="$SCRIPT_DIR/../../jenga/scripts/load-playbooks.sh"
82
+ LOAD_NL_CATALOG="$SCRIPT_DIR/../../jenga/scripts/load-nl-catalog.sh"
83
+
84
+ if [ ! -f "$LOAD_PLAYBOOKS" ]; then
85
+ echo "Error: could not locate load-playbooks.sh at $LOAD_PLAYBOOKS" >&2
86
+ exit 2
87
+ fi
88
+ if [ ! -f "$LOAD_NL_CATALOG" ]; then
89
+ echo "Error: could not locate load-nl-catalog.sh at $LOAD_NL_CATALOG" >&2
90
+ exit 2
91
+ fi
92
+
93
+ if ! command -v python3 >/dev/null 2>&1; then
94
+ echo "Error: python3 is required by playbook-new.sh" >&2
95
+ exit 2
96
+ fi
97
+
98
+ if [ -n "${JENGA_PLAYBOOKS_TEST_ROOT:-}" ]; then
99
+ # Test-only override -- see header "PROJECT ROOT RESOLUTION" above. Never set in a real
100
+ # invocation.
101
+ PROJECT_DIR="$JENGA_PLAYBOOKS_TEST_ROOT"
102
+ else
103
+ PROJECT_DIR="${JENGA_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || pwd)}}"
104
+ fi
105
+ PLAYBOOKS_TARGET_DIR="$PROJECT_DIR/project/.playbooks"
106
+
107
+ if [ $# -lt 1 ]; then
108
+ echo "Usage: $(basename "$0") <validate-id|validate-skill|write> [args...]" >&2
109
+ exit 2
110
+ fi
111
+ MODE="$1"
112
+ shift
113
+
114
+ PY_SCRIPT="$(mktemp -t playbook-new-XXXXXX.py)"
115
+ trap 'rm -f "$PY_SCRIPT"' EXIT
116
+
117
+ cat > "$PY_SCRIPT" <<'PY'
118
+ import json
119
+ import os
120
+ import re
121
+ import sys
122
+
123
+ mode = sys.argv[1]
124
+ SLUG_RE = re.compile(r'^[a-z0-9]+(-[a-z0-9]+)*$')
125
+
126
+
127
+ def emit(obj):
128
+ print(json.dumps(obj))
129
+
130
+
131
+ if mode == "validate-id":
132
+ target_id = sys.argv[2]
133
+ playbooks_target_dir = sys.argv[3]
134
+
135
+ if not SLUG_RE.match(target_id):
136
+ emit({
137
+ "valid": False,
138
+ "reason": (
139
+ f"'{target_id}' is not slug-safe -- must match ^[a-z0-9]+(-[a-z0-9]+)*$ "
140
+ f"(lowercase letters, digits, single hyphens between segments)"
141
+ ),
142
+ })
143
+ sys.exit(0)
144
+
145
+ existing_file = os.path.join(playbooks_target_dir, f"{target_id}.json")
146
+ if os.path.isfile(existing_file):
147
+ emit({
148
+ "valid": False,
149
+ "reason": (
150
+ f"a file already exists at {existing_file} -- choose a different id or edit "
151
+ f"that file directly"
152
+ ),
153
+ })
154
+ sys.exit(0)
155
+
156
+ try:
157
+ catalog = json.load(sys.stdin)
158
+ except Exception as e:
159
+ emit({
160
+ "valid": False,
161
+ "reason": f"could not read the playbook catalog to check for collisions ({e})",
162
+ })
163
+ sys.exit(0)
164
+
165
+ for entry in catalog:
166
+ if entry.get("id") == target_id:
167
+ src = entry.get("source", "unknown")
168
+ emit({
169
+ "valid": False,
170
+ "reason": (
171
+ f"id '{target_id}' collides with an already-loaded {src} playbook (see "
172
+ f"skills/jenga/scripts/load-playbooks.sh's merged catalog) -- choose a "
173
+ f"different id"
174
+ ),
175
+ })
176
+ sys.exit(0)
177
+
178
+ emit({"valid": True})
179
+ sys.exit(0)
180
+
181
+ elif mode == "validate-skill":
182
+ target_name = sys.argv[2]
183
+
184
+ try:
185
+ nl_catalog = json.load(sys.stdin)
186
+ except Exception as e:
187
+ emit({"valid": False, "reason": f"could not read the generated skill catalog ({e})"})
188
+ sys.exit(0)
189
+
190
+ for entry in nl_catalog:
191
+ if entry.get("name") == target_name:
192
+ emit({"valid": True})
193
+ sys.exit(0)
194
+
195
+ emit({
196
+ "valid": False,
197
+ "reason": (
198
+ f"'{target_name}' is not a recognized skill directory name in the generated skill "
199
+ f"catalog (skills/jenga/scripts/load-nl-catalog.sh) -- check spelling and the exact "
200
+ f"directory-name form the catalog currently lists"
201
+ ),
202
+ })
203
+ sys.exit(0)
204
+
205
+ elif mode == "write":
206
+ playbooks_target_dir = sys.argv[2]
207
+
208
+ try:
209
+ payload = json.load(sys.stdin)
210
+ except Exception as e:
211
+ emit({"written": False, "reason": f"stdin was not valid JSON ({e})"})
212
+ sys.exit(0)
213
+
214
+ if not isinstance(payload, dict):
215
+ emit({"written": False, "reason": "stdin JSON must be an object"})
216
+ sys.exit(0)
217
+
218
+ required_fields = ["id", "name", "description", "keywords", "examples", "steps"]
219
+ missing = [f for f in required_fields if f not in payload]
220
+ if missing:
221
+ emit({"written": False, "reason": f"missing required field(s) {missing}"})
222
+ sys.exit(0)
223
+
224
+ target_id = payload["id"]
225
+ if not isinstance(target_id, str) or not SLUG_RE.match(target_id):
226
+ emit({
227
+ "written": False,
228
+ "reason": f"id '{target_id}' is not slug-safe -- must match ^[a-z0-9]+(-[a-z0-9]+)*$",
229
+ })
230
+ sys.exit(0)
231
+
232
+ for field in ("name", "description"):
233
+ if not isinstance(payload[field], str) or not payload[field]:
234
+ emit({"written": False, "reason": f"'{field}' must be a non-empty string"})
235
+ sys.exit(0)
236
+
237
+ for field in ("keywords", "examples"):
238
+ value = payload[field]
239
+ if (
240
+ not isinstance(value, list)
241
+ or len(value) == 0
242
+ or not all(isinstance(v, str) and v for v in value)
243
+ ):
244
+ emit({
245
+ "written": False,
246
+ "reason": f"'{field}' must be a non-empty list of non-empty strings",
247
+ })
248
+ sys.exit(0)
249
+
250
+ steps = payload["steps"]
251
+ if not isinstance(steps, list) or len(steps) < 2 or not all(isinstance(s, str) and s for s in steps):
252
+ emit({
253
+ "written": False,
254
+ "reason": (
255
+ "'steps' must be a list of at least 2 non-empty strings (bare skill-name steps "
256
+ "only -- this wizard authors no StepObject fields, per this task's v1 scope cut)"
257
+ ),
258
+ })
259
+ sys.exit(0)
260
+
261
+ target_file = os.path.join(playbooks_target_dir, f"{target_id}.json")
262
+ if os.path.isfile(target_file):
263
+ emit({
264
+ "written": False,
265
+ "reason": f"a file already exists at {target_file} -- refusing to overwrite",
266
+ })
267
+ sys.exit(0)
268
+
269
+ try:
270
+ os.makedirs(playbooks_target_dir, exist_ok=True)
271
+ except OSError as e:
272
+ print(f"Error: could not create {playbooks_target_dir}: {e}", file=sys.stderr)
273
+ sys.exit(2)
274
+
275
+ ordered = {
276
+ "id": target_id,
277
+ "name": payload["name"],
278
+ "description": payload["description"],
279
+ "keywords": payload["keywords"],
280
+ "examples": payload["examples"],
281
+ "steps": steps,
282
+ }
283
+
284
+ try:
285
+ with open(target_file, "w", encoding="utf-8") as fh:
286
+ json.dump(ordered, fh, indent=2)
287
+ fh.write("\n")
288
+ except OSError as e:
289
+ print(f"Error: could not write {target_file}: {e}", file=sys.stderr)
290
+ sys.exit(2)
291
+
292
+ emit({"written": True, "path": os.path.abspath(target_file)})
293
+ sys.exit(0)
294
+
295
+ else:
296
+ print(f"Error: unrecognized mode '{mode}' (usage: validate-id|validate-skill|write)", file=sys.stderr)
297
+ sys.exit(2)
298
+ PY
299
+
300
+ case "$MODE" in
301
+ validate-id)
302
+ if [ $# -lt 1 ] || [ -z "${1:-}" ]; then
303
+ echo "Usage: $(basename "$0") validate-id <id>" >&2
304
+ exit 2
305
+ fi
306
+ TARGET_ID="$1"
307
+ if ! CATALOG_JSON="$("$LOAD_PLAYBOOKS")"; then
308
+ echo "Error: load-playbooks.sh failed while checking id '$TARGET_ID' for collisions" >&2
309
+ exit 2
310
+ fi
311
+ printf '%s' "$CATALOG_JSON" | python3 "$PY_SCRIPT" validate-id "$TARGET_ID" "$PLAYBOOKS_TARGET_DIR"
312
+ ;;
313
+ validate-skill)
314
+ if [ $# -lt 1 ] || [ -z "${1:-}" ]; then
315
+ echo "Usage: $(basename "$0") validate-skill <name>" >&2
316
+ exit 2
317
+ fi
318
+ TARGET_NAME="$1"
319
+ if ! NL_CATALOG_JSON="$("$LOAD_NL_CATALOG")"; then
320
+ echo "Error: load-nl-catalog.sh failed while validating skill name '$TARGET_NAME'" >&2
321
+ exit 2
322
+ fi
323
+ printf '%s' "$NL_CATALOG_JSON" | python3 "$PY_SCRIPT" validate-skill "$TARGET_NAME"
324
+ ;;
325
+ write)
326
+ python3 "$PY_SCRIPT" write "$PLAYBOOKS_TARGET_DIR"
327
+ ;;
328
+ *)
329
+ echo "Error: unrecognized mode '$MODE' (usage: $(basename "$0") <validate-id|validate-skill|write> [args...])" >&2
330
+ exit 2
331
+ ;;
332
+ esac
@@ -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,12 @@ jobs:
191
191
  - name: Install dependencies
192
192
  run: npm ci
193
193
 
194
+ - name: Install project/app workspace dependencies (E29_S05_T01)
195
+ run: npm ci --prefix project/app
196
+
197
+ - name: Regenerate lib/legacy-shipped-paths.json (E26_S08_T03)
198
+ run: node scripts/generate-legacy-shipped-paths.js || echo "::warning::legacy-shipped-paths generation failed; publishing without an updated list"
199
+
194
200
  - name: Publish to npm
195
201
  run: npm publish --provenance --access ${NPM_ACCESS} --tag ${DIST_TAG}
196
202
 
@@ -221,6 +227,9 @@ jobs:
221
227
  - name: Install dependencies
222
228
  run: npm ci
223
229
 
230
+ - name: Install project/app workspace dependencies (E29_S05_T01)
231
+ run: npm ci --prefix project/app
232
+
224
233
  - name: Stage to npm
225
234
  run: npm stage publish --provenance --access ${NPM_ACCESS} --tag ${DIST_TAG}
226
235
 
@@ -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"