@jenga-ai/agent 3.2.0 → 3.5.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 (68) hide show
  1. package/README.md +52 -12
  2. package/agents/developer.md +16 -1
  3. package/agents/scrum-master.md +1 -0
  4. package/bin/jenga.js +10 -0
  5. package/lib/commands/dashboard.js +92 -0
  6. package/lib/skill-allow-list.json +6 -2
  7. package/package.json +21 -2
  8. package/project/app/api/lib/resolve-project-root.js +120 -0
  9. package/project/app/api/package.json +16 -0
  10. package/project/app/api/parsers/architecture.js +72 -0
  11. package/project/app/api/parsers/board.js +141 -0
  12. package/project/app/api/parsers/documentation.js +125 -0
  13. package/project/app/api/parsers/git-log.js +52 -0
  14. package/project/app/api/parsers/ideas.js +62 -0
  15. package/project/app/api/parsers/knowledge-graph.js +73 -0
  16. package/project/app/api/parsers/lib/markdown-dir-reader.js +163 -0
  17. package/project/app/api/parsers/rapports.js +148 -0
  18. package/project/app/api/parsers/todo.js +179 -0
  19. package/project/app/api/response.js +47 -0
  20. package/project/app/api/routes/architecture.js +23 -0
  21. package/project/app/api/routes/board.js +46 -0
  22. package/project/app/api/routes/documentation.js +24 -0
  23. package/project/app/api/routes/health.js +25 -0
  24. package/project/app/api/routes/history.js +55 -0
  25. package/project/app/api/routes/rapports.js +24 -0
  26. package/project/app/api/scripts/capture-snapshot.js +294 -0
  27. package/project/app/api/server.js +112 -0
  28. package/project/app/api/types.js +40 -0
  29. package/project/app/package.json +21 -0
  30. package/project/app/ui/dist/assets/index-7fj-vllY.js +104 -0
  31. package/project/app/ui/dist/assets/index-CdK3Qrep.css +1 -0
  32. package/project/app/ui/dist/index.html +13 -0
  33. package/project/app/ui/package.json +23 -0
  34. package/project/app/ui/scripts/build-snapshot-html.cjs +214 -0
  35. package/project/app/ui/scripts/dashboard-open.cjs +88 -0
  36. package/project/app/ui/scripts/dashboard-start.cjs +87 -0
  37. package/scripts/acquire-concurrency-slot.sh +220 -0
  38. package/scripts/compute-deploy-reconcile.sh +439 -0
  39. package/scripts/jenga-permission-level-switch.sh +19 -3
  40. package/scripts/mark-deployed.sh +532 -0
  41. package/scripts/populate-knowledge-graph.js +429 -0
  42. package/scripts/release-concurrency-slot.sh +129 -0
  43. package/scripts/validate-board.sh +60 -2
  44. package/scripts/verify-consumer-install.sh +470 -0
  45. package/skills/j-cloud-connect/SKILL.md +95 -0
  46. package/skills/j-cloud-connect/scripts/configure-backend.sh +267 -0
  47. package/skills/j-cloud-connect/scripts/install-rclone.sh +153 -0
  48. package/skills/j-dashboard/SKILL.md +144 -0
  49. package/skills/j-dashboard/scripts/launch.sh +121 -0
  50. package/skills/j-dashboard/scripts/resolve-app-dir.sh +164 -0
  51. package/skills/j-dashboard/scripts/snapshot.sh +267 -0
  52. package/skills/j-dashboard-share/SKILL.md +96 -0
  53. package/skills/j-dashboard-share/scripts/upload-snapshot.sh +173 -0
  54. package/skills/j-init/SKILL.md +52 -13
  55. package/skills/j-init/assets/.gitignore_template +1 -2
  56. package/skills/j-init/scripts/apply-scaffold-visibility.sh +192 -0
  57. package/skills/j-init/scripts/init.sh +19 -5
  58. package/skills/j-playbook/SKILL.md +12 -0
  59. package/skills/j-playbook-new/SKILL.md +155 -0
  60. package/skills/j-playbook-new/scripts/playbook-new.sh +332 -0
  61. package/skills/j-publish/scripts/npm_ci_pipeline.sh +6 -0
  62. package/skills/j-skillify/assets/init-new/assets/.gitignore_template +1 -2
  63. package/skills/j-uncharted/SKILL.md +54 -7
  64. package/skills/j-uncharted/assets/NODE_QUESTION_TEMPLATE.md +69 -0
  65. package/skills/j-uncharted/scripts/elicitation-state.sh +45 -7
  66. package/skills/jenga/scripts/load-nl-catalog.js +22 -6
  67. package/skills/jenga/scripts/load-playbooks.sh +146 -35
  68. package/templates/SCRUM_BOARD_SCHEMA.md +18 -6
@@ -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
@@ -191,6 +191,9 @@ 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
+
194
197
  - name: Regenerate lib/legacy-shipped-paths.json (E26_S08_T03)
195
198
  run: node scripts/generate-legacy-shipped-paths.js || echo "::warning::legacy-shipped-paths generation failed; publishing without an updated list"
196
199
 
@@ -224,6 +227,9 @@ jobs:
224
227
  - name: Install dependencies
225
228
  run: npm ci
226
229
 
230
+ - name: Install project/app workspace dependencies (E29_S05_T01)
231
+ run: npm ci --prefix project/app
232
+
227
233
  - name: Stage to npm
228
234
  run: npm stage publish --provenance --access ${NPM_ACCESS} --tag ${DIST_TAG}
229
235
 
@@ -11,5 +11,4 @@ Desktop.ini
11
11
  # Dependency directories
12
12
  node_modules/
13
13
  vendor/
14
- .venv/
15
- EOF
14
+ .venv/
@@ -168,6 +168,7 @@ For genuinely undocumented code, there is often no reliable human oracle to conf
168
168
 
169
169
  - **The deterministic pipeline remains the tool of record for zero-oracle codebases.** `onboard --legacy` and `segment --mode delivery` never depend on anyone confirming intent — they ground everything in mechanical evidence (file structure, dependencies, test coverage) and say so explicitly under `Open Questions` when the evidence doesn't support a conclusion. When there is no one left who understands the code, reach for one of those, not the conversational flow.
170
170
  - **When running the conversational flow, do not manufacture confidence.** If the user's answer is uncertain, hedged, or contradicts what discovery/Investigative Mode found, write the node honestly — do not round an uncertain answer up to a confirmed one. There is no schema field yet to tag confidence (the stub schema is intentionally minimal); until one exists, say so in the node's `description` text itself (e.g. "per the user, this module retries failed charges — unconfirmed against the code, which shows only a single retry attempt") rather than silently dropping the caveat.
171
+ - **`verification_depth: strict` is this guidance's concrete implementation, not a separate idea (E40_S06_T02).** When a candidate's Familiarity Check answer is `No` (Convergence Loop Step 1), Step 4's risk-weighted gating never lets an escalating finding round up to a false confirmation by asking the user to bless it — it auto-flags the node with exactly the hedged-`description` convention this bullet describes and converges it without a prompt. This bullet states the principle; Step 4's `strict` branch is what enforces it mechanically, so the two sections should be read as one mechanism, not two independently-arrived-at claims.
171
172
  - **A confidently wrong answer is not detectable by this flow.** Corroboration against a second signal (commit history, existing docs, a second person) is the only mitigation, and it is not built here — this is the accepted residual risk, not a gap to engineer around mid-conversation.
172
173
 
173
174
  ### Directory Triage
@@ -213,10 +214,56 @@ Silence, a counter-question, or an ambiguous reply is not consent — re-ask, th
213
214
 
214
215
  Runs once per surviving candidate (a subsystem, a named flow, a directory) after Directory Triage. This is the "propose understanding, ask the user to confirm or correct" cycle at the center of the redesign — and the one the scrutiny flagged as having no termination bound and no defense against confirmation fatigue. Both gaps are closed mechanically, not by agent discipline alone:
215
216
 
216
- 1. **Dispatch Investigative Mode.** Per `agents/developer.md`'s and `agents/tester.md`'s Investigative Mode sections (E20_S08_T02), dispatch the developer to trace what the code actually does for the candidate, and the tester to trace what the test suite actually exercises and verifies for the same candidate — two distinct vantage points, not two names for the same read. Both are read-only, worktree-sandboxed, no commits, no board writes.
217
- 2. **Propose understanding.** From both traces, draft the candidate's coarse graph node(s)/edge(s) (per the stub schema) and a plain-language summary of what they represent.
218
- 3. **Risk-weighted gating — not every finding gets a prompt.** This is the fix for confirmation fatigue (solution assessment, Problem 6, Solution B — RECOMMENDED): force an explicit confirmation only for **high-uncertainty or high-impact** findings — a node whose description depends on an inference the traces don't fully support, a node with many outgoing edges (structurally central), or one the Human-Oracle-Availability Limitation above already flagged as uncertain. **Auto-accept** low-risk, high-confidence findings — the traces agree, the finding is narrow in scope, nothing about it is surprising — without a prompt, but **log every auto-accepted node** in the elicitation state's checkpoint data (see below) so the decision is auditable later, per that solution's own mitigation for "the scoring mechanism itself misjudges impact."
219
- 4. **Confirm/correct, one round per call to `elicitation-state.sh turn`.** For a node requiring confirmation, present the draft and ask the user to confirm or correct it (per the Interaction Pattern in `CLAUDE.md` — confirm / correct-with-detail / defer as "unconfirmed" / other). Each round, call:
217
+ 1. **Familiarity Check — once per candidate, before Investigative Mode dispatch.** Ask (per the Interaction Pattern in `CLAUDE.md` — numbered list, free-text last):
218
+
219
+ ```
220
+ Are you familiar with this service/segment?
221
+ 1. Yes
222
+ 2. A little
223
+ 3. No
224
+ 4. Other (describe below)
225
+ ```
226
+
227
+ Silence, a counter-question, or an ambiguous reply is not consent — re-ask, the same convention used at every other confirmation gate in this skill. Map the answer to a `verification_depth` scoped to this candidate only — `Yes` → `shallow`, `A little` → `moderate`, `No` → `strict` — and persist it immediately, keyed by this candidate's node id, via `elicitation-state.sh`'s checkpoint mechanism (see Multi-Session Persistence below for the exact call and merge semantics):
228
+
229
+ ```bash
230
+ printf '{"verification_depth": {"%s": "%s"}}' "<candidate-id>" "<shallow|moderate|strict>" \
231
+ | bash skills/j-uncharted/scripts/elicitation-state.sh checkpoint --id <elicitation-id> --json -
232
+ ```
233
+
234
+ **Check before asking.** A candidate whose `verification_depth` is already present in the state file's `checkpoint.verification_depth` (per Multi-Session Persistence below) has already answered this — do not re-ask it, on a fresh session or otherwise.
235
+
236
+ This question operationalizes the Human-Oracle-Availability Limitation above — it is the mechanism for finding out, per candidate, how much weight the user's own confirmations should carry, rather than assuming a uniform level of trust for every candidate in one run. **`verification_depth` is read back and consumed by Step 4's risk-weighted gating below (`E40_S06_T02`)**, which branches its auto-accept/confirm/auto-flag behavior per depth. The fixed internal/external question template used whenever shallow/moderate gating does decide to prompt (Step 5) is `skills/j-uncharted/assets/NODE_QUESTION_TEMPLATE.md` (`E40_S06_T03`) — this step's job remains asking the question and making the answer durable for those steps to read.
237
+ 2. **Dispatch Investigative Mode.** Per `agents/developer.md`'s and `agents/tester.md`'s Investigative Mode sections (E20_S08_T02), dispatch the developer to trace what the code actually does for the candidate, and the tester to trace what the test suite actually exercises and verifies for the same candidate — two distinct vantage points, not two names for the same read. Both are read-only, worktree-sandboxed, no commits, no board writes.
238
+ 3. **Propose understanding.** From both traces, draft the candidate's coarse graph node(s)/edge(s) (per the stub schema) and a plain-language summary of what they represent.
239
+ 4. **Risk-weighted gating — not every finding gets a prompt, and `verification_depth` (Step 1) decides how gating itself behaves, not just what counts as risky.** This is the fix for confirmation fatigue (solution assessment, Problem 6, Solution B — RECOMMENDED). The baseline escalation criteria — the three triggers that force an explicit confirmation — are unchanged from before `E40_S06`:
240
+
241
+ - **T1 — inference-dependent:** the node's description depends on an inference the traces don't fully support.
242
+ - **T2 — structurally central:** the node has many outgoing edges.
243
+ - **T3 — already-flagged uncertain:** the Human-Oracle-Availability Limitation above already flagged this finding as uncertain.
244
+
245
+ A finding tripping none of T1-T3 is **low-risk** and auto-accepts regardless of depth. A finding tripping any of T1-T3 is **escalating**, and what happens to it now branches on the current candidate's `verification_depth` (read from `checkpoint.verification_depth.<candidate-id>`, per Step 1):
246
+
247
+ - **`moderate` (the default, unchanged calibration)** — exactly today's behavior: every escalating finding (any of T1-T3) forces a confirm prompt (Step 5); every low-risk finding auto-accepts without a prompt. **Log every auto-accepted node** in the elicitation state's checkpoint data (see Multi-Session Persistence below) so the decision is auditable later, per that solution's own mitigation for "the scoring mechanism itself misjudges impact."
248
+ - **`shallow` (widened auto-accept)** — the concrete widening rule: **drop T2 (structurally central) as an escalation trigger.** A finding tripping T2 alone — structurally central, but not inference-dependent and not already flagged uncertain — is reclassified low-risk and auto-accepted (still logged, same as above) instead of escalating. T1 and T3 still force a confirm prompt exactly as under `moderate`; only the T2-alone case widens. This is the literal reading of "findings that would sit just below today's high-impact bar" from the task's own framing — a purely structural signal with no corroborating uncertainty is no longer, by itself, enough to interrupt the user.
249
+ - **`strict` (no confirm prompt for escalating findings, ever)** — a finding tripping any of T1-T3 is **never presented to the user**. Instead:
250
+ 1. Write the node directly with a hedged, low-confidence `description`, reusing the exact hedging convention the Human-Oracle-Availability Limitation section already specifies (e.g. "unconfirmed — traces did not fully corroborate this," adapted to name the specific gap).
251
+ 2. Call `elicitation-state.sh converge` directly — **do not call `elicitation-state.sh turn` for this node.** No confirmation round is spent; the node goes straight from "proposed" to "converged," never "pending":
252
+
253
+ ```bash
254
+ bash skills/j-uncharted/scripts/elicitation-state.sh converge --id <elicitation-id> --node <node-id> --note "auto-flagged under strict depth: <one-line reason, e.g. 'structurally central, traces disagree on scope'>"
255
+ ```
256
+ 3. **Log the auto-flag** in the elicitation state's checkpoint data, the same way `moderate`/`shallow` auto-accepts are logged — this is an automatic decision, not a silent one, and stays auditable exactly like every other gating outcome.
257
+
258
+ Low-risk findings under `strict` are unaffected — they auto-accept exactly as under `moderate`/`shallow`. `strict` only changes what happens to the escalating case.
259
+
260
+ The fixed internal/external question template used whenever `shallow`/`moderate` gating does decide to prompt is `skills/j-uncharted/assets/NODE_QUESTION_TEMPLATE.md` (Step 5, `E40_S06_T03`) — dormant under `strict`, since no prompt ever fires there.
261
+ 5. **Confirm/correct, one round per call to `elicitation-state.sh turn`.** Never reached for a `strict`-depth candidate's escalating findings — those converge directly per Step 4 above. For a node requiring confirmation under `shallow`/`moderate`, do **not** present the draft with a fully open-ended "propose understanding, ask to confirm or correct" prompt. Instead use the fixed question set in `skills/j-uncharted/assets/NODE_QUESTION_TEMPLATE.md` (`E40_S06_T03`), selecting the variant by node kind:
262
+
263
+ - **Internal variant** — the node represents the candidate/service itself (the thing this investigation is about).
264
+ - **External variant** — the node represents a dependency or consumer the traces surfaced outside the candidate (something it calls, or something that calls it).
265
+
266
+ Present the drafted node/edge summary from Step 3 first, then ask the selected variant's fixed questions, then offer the same confirm / correct-with-detail / defer-as-unconfirmed / other choice as before (per the Interaction Pattern in `CLAUDE.md`) — the template file spells out both the questions and this response block verbatim, so read it rather than reconstructing either from memory. Each round, call:
220
267
 
221
268
  ```bash
222
269
  bash skills/j-uncharted/scripts/elicitation-state.sh turn --id <elicitation-id> --node <node-id>
@@ -233,7 +280,7 @@ Runs once per surviving candidate (a subsystem, a named flow, a directory) after
233
280
  ```
234
281
 
235
282
  Option 3 is the only way past the cap, and it is a per-node, explicit, one-time override — it does not raise the cap for the rest of the run.
236
- 5. **On convergence** (confirmed, corrected-and-accepted, or resolved via the cap choice above), call:
283
+ 6. **On convergence** (confirmed, corrected-and-accepted, or resolved via the cap choice above), call:
237
284
 
238
285
  ```bash
239
286
  bash skills/j-uncharted/scripts/elicitation-state.sh converge --id <elicitation-id> --node <node-id> --note "<one-line summary of what was confirmed>"
@@ -252,9 +299,9 @@ A whole-codebase `onboard` conversation, or an investigation of a large director
252
299
  ```
253
300
 
254
301
  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.
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.
302
+ - **`checkpoint` after every converged node, after the Directory Triage confirmation gate, and after every Familiarity Check answer** — 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, per-candidate `verification_depth`, anything else worth surviving a pause) into the state file. `verification_depth` is stored as one object keyed by candidate id — `checkpoint.verification_depth.<candidate-id>` — and, per the script's own merge semantics (see its header), checking one candidate in never clobbers another candidate already recorded there.
256
303
  - **`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`).
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.
304
+ - **On resume**, read `state_file` directly — every converged node, every flagged node, and the checkpoint data (including the confirmed directory-triage lists and any per-candidate `verification_depth` already recorded) are already there. Do not re-run Directory Triage, re-ask the Familiarity Check for a candidate already present under `checkpoint.verification_depth`, or re-ask about an already-converged node; resume the Convergence Loop only for nodes still `pending` or explicitly deferred, and only ask the Familiarity Check for a candidate that has neither.
258
305
  - **`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.
259
306
 
260
307
  ---