@jenga-ai/agent 3.2.0 → 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 (60) 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-playbook/SKILL.md +12 -0
  55. package/skills/j-playbook-new/SKILL.md +155 -0
  56. package/skills/j-playbook-new/scripts/playbook-new.sh +332 -0
  57. package/skills/j-publish/scripts/npm_ci_pipeline.sh +6 -0
  58. package/skills/jenga/scripts/load-nl-catalog.js +22 -6
  59. package/skills/jenga/scripts/load-playbooks.sh +123 -24
  60. package/templates/SCRUM_BOARD_SCHEMA.md +18 -6
@@ -0,0 +1,470 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # verify-consumer-install.sh — real-consumer rehearsal for E47_S02_T02 / E47_S01
4
+ #
5
+ # Verifies that project/app/api/server.js and its 5 parsers (board.js, git-log.js, rapports.js,
6
+ # architecture.js, knowledge-graph.js) resolve their data root via
7
+ # project/app/api/lib/resolve-project-root.js (E47_S02_T01) — not a fixed __dirname-relative climb —
8
+ # and that the dashboard's API + built UI actually ship and run for a real npm consumer (E47_S01),
9
+ # against five scenarios, matching the verification bar E46_S01 already set for /init:
10
+ #
11
+ # A. SELF-HOSTING — dashboard launched from this repo's own checkout (no env override, pure
12
+ # cwd walk-up) still serves this repo's own board/history/architecture data unchanged.
13
+ # B. SYMLINKED INVOCATION — dashboard launched with cwd reached through a symlink to this repo's
14
+ # own checkout still resolves correctly (same bug class E46_S01 fixed for /init).
15
+ # C. REAL CONSUMER INSTALL — a scratch "package" is `npm pack`ed directly from a throwaway copy
16
+ # of this repo's own git-tracked tree (`git archive HEAD`, unmodified — no scratch patch of
17
+ # any kind) and `npm install`ed into a second scratch "consumer" directory that has its own,
18
+ # distinctly-fixtured `project/board`/`project/rapports`/`project/knowledge-graph`. The
19
+ # dashboard is launched from inside that consumer directory and its API responses must
20
+ # reflect the *consumer's* fixture data, never this repo's own. No manual dependency install
21
+ # step is run inside the installed copy — the consumer's own top-level `npm install` (E47_S01_T03's
22
+ # root `dependencies`) must be sufficient on its own for express/cors/gray-matter to resolve.
23
+ # D. SERVED UI (installed tarball) — `--serve-app` launched against the SAME already-installed
24
+ # consumer copy from Scenario C, with no build step run inside the consumer directory at any
25
+ # point, confirms the UI ships pre-built (E47_S01_T02's `prepack` step) and is servable as-is.
26
+ # E. `jenga dashboard start`/`jenga dashboard open` CLI VERBS (installed tarball, E47_S03) — the
27
+ # SAME already-installed consumer copy from Scenario C is driven through its own installed
28
+ # `bin/jenga.js dashboard start`/`open` CLI entry point (not a direct `node
29
+ # .../dashboard-start.cjs` invocation like Scenarios C/D use) and asserted against the same
30
+ # consumer-fixture-not-leaked checks Scenario C already performs, plus the unknown/missing
31
+ # sub-verb usage-error path. This proves the CLI wiring E47_S03_T01 added inside this
32
+ # monorepo also works end-to-end from a real `npm pack`/`npm install`, serving the actual
33
+ # packaged tarball contents rather than a scratch/dev-mode copy.
34
+ # F. THIS REPO'S OWN SCRIPTS (regression) — `npm run dashboard:start`, `npm run dashboard:open`,
35
+ # and `npm run ui:build` all still exist and run without error from this repo's own checkout
36
+ # (story E47_S01's AC4 — no regression to this monorepo's own internal use).
37
+ #
38
+ # SAFETY: all scratch state lives under `mktemp -d`. This script never modifies this repo's own
39
+ # tracked package.json, board, or any other file — only throwaway copies.
40
+ #
41
+ # Usage: bash scripts/verify-consumer-install.sh
42
+ # Exit: 0 = all assertions passed, 1 = at least one failed.
43
+
44
+ set -uo pipefail
45
+
46
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
47
+ SCRATCH="$(mktemp -d "${TMPDIR:-/tmp}/jenga-consumer-verify.XXXXXX")"
48
+
49
+ PASS=0
50
+ FAIL=0
51
+
52
+ pass() { PASS=$((PASS + 1)); printf ' \033[32mPASS\033[0m %s\n' "$1"; }
53
+ fail() { FAIL=$((FAIL + 1)); printf ' \033[31mFAIL\033[0m %s\n' "$1"; }
54
+
55
+ # Servers are stopped via `pkill -f <unique-absolute-path-fragment>` rather than `kill <pid>` —
56
+ # each scenario requires server.js from a distinct absolute path (this repo's own checkout, the
57
+ # symlinked path, or the scratch-installed tarball copy), so matching on that path is unambiguous
58
+ # and, unlike a bare `kill`, works reliably in sandboxed shells that gate direct PID signaling.
59
+ stop_server() { pkill -f "$1" >/dev/null 2>&1 || true; }
60
+
61
+ cleanup() {
62
+ stop_server "$REPO_ROOT/project/app/api/server.js"
63
+ stop_server "$SCRATCH/symlinked-repo/project/app/api/server.js"
64
+ stop_server "$SCRATCH/consumer-project/node_modules/@jenga-ai/agent/project/app/ui/scripts/dashboard-start.cjs"
65
+ # Fallback for Scenario E's regression server, which is launched via nested `npm run --prefix`
66
+ # layers rather than a direct absolute `node <path>` call, so its argv may not carry the
67
+ # REPO_ROOT-prefixed absolute path the pattern above expects.
68
+ stop_server "dashboard-start.cjs"
69
+ if [ "${KEEP_FIXTURE:-0}" = "1" ]; then
70
+ printf '\n Fixture retained at: %s\n' "$SCRATCH"
71
+ else
72
+ rm -rf "$SCRATCH"
73
+ fi
74
+ }
75
+ trap cleanup EXIT
76
+
77
+ wait_for_health() {
78
+ local port="$1" tries=0
79
+ while [ "$tries" -lt 40 ]; do
80
+ if curl -s -o /dev/null -w '%{http_code}' "http://localhost:${port}/v1/health" 2>/dev/null | grep -q '^2'; then
81
+ return 0
82
+ fi
83
+ tries=$((tries + 1))
84
+ sleep 0.25
85
+ done
86
+ return 1
87
+ }
88
+
89
+ # ── Scenario A: self-hosting ─────────────────────────────────────────────────
90
+ echo "== Scenario A: self-hosting (this repo's own checkout) =="
91
+ PORT_A=41001
92
+ ( cd "$REPO_ROOT" && JENGA_API_PORT="$PORT_A" node "$REPO_ROOT/project/app/api/server.js" >"$SCRATCH/server-a.log" 2>&1 & )
93
+
94
+ if wait_for_health "$PORT_A"; then
95
+ BOARD_A="$(curl -s "http://localhost:${PORT_A}/v1/board")"
96
+ ARCH_A="$(curl -s "http://localhost:${PORT_A}/v1/architecture")"
97
+ HISTORY_A="$(curl -s "http://localhost:${PORT_A}/v1/history?limit=5")"
98
+ if echo "$BOARD_A" | grep -q '"id":"E01"'; then
99
+ pass "self-hosting: /v1/board contains this repo's own E01"
100
+ else
101
+ fail "self-hosting: /v1/board missing this repo's own E01"
102
+ fi
103
+ if echo "$ARCH_A" | grep -q '"name":"jenga"'; then
104
+ pass "self-hosting: /v1/architecture reflects this repo's own package.json"
105
+ else
106
+ fail "self-hosting: /v1/architecture did not reflect this repo's own package.json"
107
+ fi
108
+ if echo "$HISTORY_A" | grep -q '"type":"git_commit"'; then
109
+ pass "self-hosting: /v1/history returns this repo's own git commits"
110
+ else
111
+ fail "self-hosting: /v1/history did not return any git commits"
112
+ fi
113
+ else
114
+ fail "self-hosting: server never became healthy on port $PORT_A (see $SCRATCH/server-a.log)"
115
+ fi
116
+ stop_server "$REPO_ROOT/project/app/api/server.js"
117
+
118
+ # ── Scenario B: symlinked invocation path ────────────────────────────────────
119
+ echo "== Scenario B: symlinked invocation path =="
120
+ SYMLINK_PATH="$SCRATCH/symlinked-repo"
121
+ ln -s "$REPO_ROOT" "$SYMLINK_PATH"
122
+ PORT_B=41002
123
+ ( cd "$SYMLINK_PATH" && JENGA_API_PORT="$PORT_B" node "$SYMLINK_PATH/project/app/api/server.js" >"$SCRATCH/server-b.log" 2>&1 & )
124
+
125
+ if wait_for_health "$PORT_B"; then
126
+ BOARD_B="$(curl -s "http://localhost:${PORT_B}/v1/board")"
127
+ if echo "$BOARD_B" | grep -q '"id":"E01"'; then
128
+ pass "symlinked cwd: /v1/board still resolves to this repo's own data"
129
+ else
130
+ fail "symlinked cwd: /v1/board did not resolve to this repo's own data"
131
+ fi
132
+ else
133
+ fail "symlinked cwd: server never became healthy on port $PORT_B (see $SCRATCH/server-b.log)"
134
+ fi
135
+ stop_server "$SYMLINK_PATH/project/app/api/server.js"
136
+
137
+ # ── Scenario C: real consumer install (npm pack + npm install) ──────────────
138
+ echo "== Scenario C: real consumer install (npm pack + npm install) =="
139
+
140
+ PKG_SRC="$SCRATCH/pkg-src"
141
+ mkdir -p "$PKG_SRC"
142
+ ( cd "$REPO_ROOT" && git archive HEAD ) | tar -x -C "$PKG_SRC"
143
+
144
+ # Install project/app's own dev dependencies inside the scratch source copy — this is what a
145
+ # maintainer's real checkout already has in place before running `npm publish`/`npm pack`
146
+ # themselves (the prepack script below needs project/app/ui's own vite install to actually build
147
+ # dist/). This is NOT a package.json patch and NOT a workaround for missing packaging — packing
148
+ # itself, and everything below, now runs against the real tracked package.json exactly as a real
149
+ # `npm publish` would see it (E47_S01_T01/T02/T03 landed — no scratch `files` patch anymore).
150
+ ( cd "$PKG_SRC/project/app" && npm install --no-audit --no-fund >"$SCRATCH/npm-install-pkgsrc-app.log" 2>&1 )
151
+
152
+ TARBALL_DIR="$SCRATCH/tarball"
153
+ mkdir -p "$TARBALL_DIR"
154
+ # `| tail -1`: the prepack step (E47_S01_T02) now runs a real `vite build` before packing, and
155
+ # vite's own progress output goes to stdout alongside npm's own tarball-filename line — even
156
+ # under `--silent`, which only suppresses npm's own notices, not a lifecycle script's output.
157
+ # `npm pack`'s own stdout contract still ends with exactly the tarball filename on its own final
158
+ # line, so taking the last line isolates it from the build noise printed before it.
159
+ TARBALL_NAME="$(cd "$PKG_SRC" && npm pack --silent --pack-destination "$TARBALL_DIR" 2>"$SCRATCH/npm-pack.log" | tail -1)"
160
+ if [ -z "$TARBALL_NAME" ] || [ ! -f "$TARBALL_DIR/$TARBALL_NAME" ]; then
161
+ fail "npm pack did not produce a tarball (see $SCRATCH/npm-pack.log)"
162
+ else
163
+ pass "npm pack produced $TARBALL_NAME"
164
+
165
+ CONSUMER="$SCRATCH/consumer-project"
166
+ mkdir -p "$CONSUMER"
167
+ ( cd "$CONSUMER" && npm init -y >/dev/null 2>"$SCRATCH/npm-init.log" )
168
+ ( cd "$CONSUMER" && npm install --no-audit --no-fund "$TARBALL_DIR/$TARBALL_NAME" >"$SCRATCH/npm-install.log" 2>&1 )
169
+
170
+ INSTALLED_APP="$CONSUMER/node_modules/@jenga-ai/agent/project/app"
171
+ INSTALLED_API="$INSTALLED_APP/api"
172
+ if [ -f "$INSTALLED_API/server.js" ] && [ -f "$INSTALLED_API/lib/resolve-project-root.js" ]; then
173
+ pass "installed tarball contains server.js and the shared resolver"
174
+ else
175
+ fail "installed tarball is missing server.js or lib/resolve-project-root.js (see $SCRATCH/npm-install.log)"
176
+ fi
177
+
178
+ # E47_S01_T02: the prepack build step must have shipped a real, non-empty built dist/ — no
179
+ # build step is ever run inside the consumer directory itself, only at pack time in PKG_SRC.
180
+ INSTALLED_DIST="$INSTALLED_APP/ui/dist"
181
+ if [ -d "$INSTALLED_DIST" ] && [ -f "$INSTALLED_DIST/index.html" ] && [ -n "$(ls -A "$INSTALLED_DIST" 2>/dev/null)" ]; then
182
+ pass "installed tarball contains a non-empty project/app/ui/dist/ (prepack build shipped)"
183
+ else
184
+ fail "installed tarball is missing a built project/app/ui/dist/ (see $SCRATCH/npm-pack.log)"
185
+ fi
186
+
187
+ # E47_S01_T03: express/cors/gray-matter must resolve from the consumer's own top-level
188
+ # node_modules (root package.json dependencies) — no manual install inside the installed copy.
189
+ DEPS_RESOLVE_LOG="$SCRATCH/deps-resolve.log"
190
+ if node -e '
191
+ const path = require("path");
192
+ const serverPath = path.join(process.argv[1], "server.js");
193
+ for (const dep of ["express", "cors", "gray-matter"]) {
194
+ require.resolve(dep, { paths: [path.dirname(serverPath)] });
195
+ }
196
+ ' "$INSTALLED_API" >"$DEPS_RESOLVE_LOG" 2>&1; then
197
+ pass "consumer install: express/cors/gray-matter all resolve with no manual dependency install"
198
+ else
199
+ fail "consumer install: express/cors/gray-matter failed to resolve (see $DEPS_RESOLVE_LOG)"
200
+ fi
201
+
202
+ # Distinct consumer fixture data — deliberately unlike anything in this repo's own board.
203
+ mkdir -p "$CONSUMER/project/board/epics" "$CONSUMER/project/rapports" "$CONSUMER/project/knowledge-graph"
204
+ cat > "$CONSUMER/project/board/epics/ZZZ_scratch-fixture-epic.md" <<'FIXTURE'
205
+ ---
206
+ id: ZZZ_FIXTURE
207
+ title: Scratch Consumer Fixture Epic — Not A Real Jenga Epic
208
+ status: Pending
209
+ date_created: 2026-09-11
210
+ stories: []
211
+ ---
212
+
213
+ # Epic: Scratch Consumer Fixture Epic — Not A Real Jenga Epic
214
+ FIXTURE
215
+ echo '{"nodes": [], "edges": []}' > "$CONSUMER/project/knowledge-graph/graph.json"
216
+
217
+ # A real (throwaway) git repo with one distinctive commit, so /v1/history has something
218
+ # consumer-specific to assert on, rather than an ambiguous empty array. node_modules is
219
+ # excluded from the commit purely so this doesn't take forever to `git add`.
220
+ echo 'node_modules/' > "$CONSUMER/.gitignore"
221
+ ( cd "$CONSUMER" \
222
+ && git init -q \
223
+ && git config user.email "fixture@example.com" \
224
+ && git config user.name "Fixture Consumer" \
225
+ && git add -A \
226
+ && git commit -q -m "ZZZ_FIXTURE_COMMIT: scratch consumer project init" )
227
+
228
+ # E33_S07_T02: jenga-permission-level-switch.sh consumer-layout coverage. E33_S07_T01 fixed
229
+ # this script to resolve its level templates from the package root
230
+ # (node_modules/@jenga-ai/agent/templates/permission-levels/) rather than the consumer's git
231
+ # root, since that's exactly where a consumer install's templates actually ship. This
232
+ # regressed silently once already (exit 2, zero files changed, in every real consumer
233
+ # install) precisely because no layout like this one exercised it. Independent of the
234
+ # dashboard server, so it runs here rather than waiting on wait_for_health below.
235
+ PERM_SWITCH="$CONSUMER/node_modules/@jenga-ai/agent/scripts/jenga-permission-level-switch.sh"
236
+ if [ ! -f "$PERM_SWITCH" ]; then
237
+ fail "consumer install: jenga-permission-level-switch.sh not found in installed tarball at $PERM_SWITCH"
238
+ else
239
+ for n in 1 2 3 4 5; do
240
+ case "$n" in
241
+ 1) name="locked" ;;
242
+ 2) name="guarded" ;;
243
+ 3) name="standard" ;;
244
+ 4) name="elevated" ;;
245
+ 5) name="unrestricted" ;;
246
+ esac
247
+ PERM_LOG="$SCRATCH/perm-switch-level-$n.log"
248
+ if ( cd "$CONSUMER" && bash "$PERM_SWITCH" "$n" >"$PERM_LOG" 2>&1 ); then
249
+ if grep -q "Switched to level $n ($name)\.\$" "$PERM_LOG"; then
250
+ pass "consumer install: jenga-permission-level-switch.sh level $n exits 0 with the expected message"
251
+ else
252
+ fail "consumer install: jenga-permission-level-switch.sh level $n exited 0 but did not print the expected message (see $PERM_LOG)"
253
+ fi
254
+ else
255
+ fail "consumer install: jenga-permission-level-switch.sh level $n exited non-zero (see $PERM_LOG)"
256
+ fi
257
+
258
+ if [ -f "$CONSUMER/.claude/settings.json" ] && [ -f "$CONSUMER/.agents/settings.json" ]; then
259
+ pass "consumer install: level $n wrote .claude/settings.json and .agents/settings.json into the CONSUMER project root"
260
+ else
261
+ fail "consumer install: level $n did not write both settings files into the consumer project root"
262
+ fi
263
+
264
+ if [ -f "$CONSUMER/node_modules/@jenga-ai/agent/.claude/settings.json" ] || [ -f "$CONSUMER/node_modules/@jenga-ai/agent/.agents/settings.json" ]; then
265
+ fail "consumer install: level $n wrote settings files under node_modules/ instead of the consumer root"
266
+ else
267
+ pass "consumer install: level $n did not write settings files under node_modules/"
268
+ fi
269
+
270
+ if grep -q "\"session_level\": $n" "$CONSUMER/.jenga-permission-level.json" 2>/dev/null; then
271
+ pass "consumer install: level $n's .jenga-permission-level.json reflects session_level $n"
272
+ else
273
+ fail "consumer install: level $n's .jenga-permission-level.json does not reflect session_level $n (see $CONSUMER/.jenga-permission-level.json)"
274
+ fi
275
+ done
276
+ fi
277
+
278
+ PORT_C=41003
279
+ DASHBOARD_START="$CONSUMER/node_modules/@jenga-ai/agent/project/app/ui/scripts/dashboard-start.cjs"
280
+ ( cd "$CONSUMER" && JENGA_API_PORT="$PORT_C" node "$DASHBOARD_START" --port "$PORT_C" >"$SCRATCH/server-c.log" 2>&1 & )
281
+
282
+ if wait_for_health "$PORT_C"; then
283
+ BOARD_C="$(curl -s "http://localhost:${PORT_C}/v1/board")"
284
+ if echo "$BOARD_C" | grep -q 'ZZZ_FIXTURE'; then
285
+ pass "consumer install: /v1/board serves the CONSUMER's own fixture epic"
286
+ else
287
+ fail "consumer install: /v1/board did not contain the consumer's fixture epic"
288
+ fi
289
+ if echo "$BOARD_C" | grep -q '"id":"E01"'; then
290
+ fail "consumer install: /v1/board leaked THIS REPO's own E01 epic into the consumer response"
291
+ else
292
+ pass "consumer install: /v1/board does not leak this repo's own board data"
293
+ fi
294
+
295
+ HISTORY_C="$(curl -s "http://localhost:${PORT_C}/v1/history?limit=5")"
296
+ if echo "$HISTORY_C" | grep -q 'ZZZ_FIXTURE_COMMIT'; then
297
+ pass "consumer install: /v1/history serves the CONSUMER's own fixture commit"
298
+ else
299
+ fail "consumer install: /v1/history did not contain the consumer's fixture commit"
300
+ fi
301
+
302
+ ARCH_C="$(curl -s "http://localhost:${PORT_C}/v1/architecture")"
303
+ if echo "$ARCH_C" | grep -q '"name":"jenga"'; then
304
+ fail "consumer install: /v1/architecture leaked THIS REPO's own package.json tech_stack entry"
305
+ else
306
+ pass "consumer install: /v1/architecture does not leak this repo's own package.json"
307
+ fi
308
+
309
+ HEALTH_C="$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:${PORT_C}/v1/health")"
310
+ if [ "$HEALTH_C" = "200" ]; then
311
+ pass "consumer install: /v1/health responds 200"
312
+ else
313
+ fail "consumer install: /v1/health responded $HEALTH_C, expected 200"
314
+ fi
315
+ else
316
+ fail "consumer install: server never became healthy on port $PORT_C (see $SCRATCH/server-c.log)"
317
+ fi
318
+ stop_server "$DASHBOARD_START"
319
+
320
+ # ── Scenario D: served UI, against the SAME already-installed consumer copy ────────────
321
+ echo "== Scenario D: served UI (installed tarball, --serve-app, no build step in consumer) =="
322
+ if [ -d "$INSTALLED_DIST" ]; then
323
+ PORT_D=41004
324
+ ( cd "$CONSUMER" && JENGA_API_PORT="$PORT_D" node "$DASHBOARD_START" --port "$PORT_D" --serve-app >"$SCRATCH/server-d.log" 2>&1 & )
325
+
326
+ if wait_for_health "$PORT_D"; then
327
+ ROOT_STATUS_D="$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:${PORT_D}/")"
328
+ if [ "$ROOT_STATUS_D" = "200" ]; then
329
+ pass "served UI: GET / responds 200 from the installed tarball's dist/"
330
+ else
331
+ fail "served UI: GET / responded $ROOT_STATUS_D, expected 200"
332
+ fi
333
+
334
+ INDEX_BODY_D="$(curl -s "http://localhost:${PORT_D}/")"
335
+ if echo "$INDEX_BODY_D" | grep -qi '<div id="root"'; then
336
+ pass "served UI: response body looks like the built index.html (has #root mount div)"
337
+ else
338
+ fail "served UI: response body did not look like the built index.html"
339
+ fi
340
+
341
+ # /v1/* API routes must still work alongside the static SPA fallback.
342
+ HEALTH_D="$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:${PORT_D}/v1/health")"
343
+ if [ "$HEALTH_D" = "200" ]; then
344
+ pass "served UI: /v1/health still responds 200 alongside the static SPA fallback"
345
+ else
346
+ fail "served UI: /v1/health responded $HEALTH_D, expected 200"
347
+ fi
348
+ else
349
+ fail "served UI: server never became healthy on port $PORT_D (see $SCRATCH/server-d.log)"
350
+ fi
351
+ stop_server "$DASHBOARD_START"
352
+ else
353
+ fail "served UI: skipped — no installed project/app/ui/dist/ to serve (see Scenario C above)"
354
+ fi
355
+ fi
356
+
357
+ # ── Scenario E: `jenga dashboard start`/`open` CLI verbs (installed tarball, E47_S03) ────────
358
+ echo "== Scenario E: 'jenga dashboard start'/'open' via installed CLI (npm pack + npm install) =="
359
+
360
+ INSTALLED_BIN="$CONSUMER/node_modules/@jenga-ai/agent/bin/jenga.js"
361
+ if [ -f "$INSTALLED_BIN" ]; then
362
+ # Missing/unknown sub-verb: must print usage and exit non-zero, never crash or silently no-op.
363
+ if ( cd "$CONSUMER" && node "$INSTALLED_BIN" dashboard >"$SCRATCH/cli-missing-subverb.log" 2>&1 ); then
364
+ fail "installed CLI: 'jenga dashboard' (no sub-verb) exited 0, expected non-zero"
365
+ else
366
+ if grep -qi "usage" "$SCRATCH/cli-missing-subverb.log"; then
367
+ pass "installed CLI: 'jenga dashboard' (no sub-verb) exits non-zero with a usage message"
368
+ else
369
+ fail "installed CLI: 'jenga dashboard' (no sub-verb) exited non-zero but printed no usage message"
370
+ fi
371
+ fi
372
+
373
+ if ( cd "$CONSUMER" && node "$INSTALLED_BIN" dashboard bogus-verb >/dev/null 2>"$SCRATCH/cli-unknown-subverb.log" ); then
374
+ fail "installed CLI: 'jenga dashboard bogus-verb' exited 0, expected non-zero"
375
+ else
376
+ pass "installed CLI: 'jenga dashboard bogus-verb' exits non-zero (unknown sub-verb rejected)"
377
+ fi
378
+
379
+ # `jenga dashboard start` — same consumer fixture data Scenario C seeded, driven through the
380
+ # installed CLI entry point rather than a direct node <path>/dashboard-start.cjs invocation.
381
+ PORT_E=41007
382
+ ( cd "$CONSUMER" && node "$INSTALLED_BIN" dashboard start --port "$PORT_E" >"$SCRATCH/server-e.log" 2>&1 & )
383
+
384
+ if wait_for_health "$PORT_E"; then
385
+ BOARD_E="$(curl -s "http://localhost:${PORT_E}/v1/board")"
386
+ if echo "$BOARD_E" | grep -q 'ZZZ_FIXTURE'; then
387
+ pass "installed CLI: 'jenga dashboard start' serves the CONSUMER's own fixture epic"
388
+ else
389
+ fail "installed CLI: 'jenga dashboard start' did not serve the consumer's fixture epic"
390
+ fi
391
+ if echo "$BOARD_E" | grep -q '"id":"E01"'; then
392
+ fail "installed CLI: 'jenga dashboard start' leaked THIS REPO's own E01 epic into the response"
393
+ else
394
+ pass "installed CLI: 'jenga dashboard start' does not leak this repo's own board data"
395
+ fi
396
+ else
397
+ fail "installed CLI: 'jenga dashboard start' never became healthy on port $PORT_E (see $SCRATCH/server-e.log)"
398
+ fi
399
+ stop_server "$CONSUMER/node_modules/@jenga-ai/agent/project/app/ui/scripts/dashboard-start.cjs"
400
+
401
+ # `jenga dashboard start --serve-app` — confirms the served UI is the installed tarball's own
402
+ # pre-built dist/ (E47_S01_T02's prepack step), reached through the CLI wrapper.
403
+ if [ -d "$INSTALLED_DIST" ]; then
404
+ PORT_E2=41008
405
+ ( cd "$CONSUMER" && node "$INSTALLED_BIN" dashboard start --port "$PORT_E2" --serve-app >"$SCRATCH/server-e2.log" 2>&1 & )
406
+ if wait_for_health "$PORT_E2"; then
407
+ ROOT_STATUS_E2="$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:${PORT_E2}/")"
408
+ if [ "$ROOT_STATUS_E2" = "200" ]; then
409
+ pass "installed CLI: 'jenga dashboard start --serve-app' serves the installed tarball's dist/"
410
+ else
411
+ fail "installed CLI: 'jenga dashboard start --serve-app' GET / responded $ROOT_STATUS_E2, expected 200"
412
+ fi
413
+ else
414
+ fail "installed CLI: 'jenga dashboard start --serve-app' never became healthy on port $PORT_E2 (see $SCRATCH/server-e2.log)"
415
+ fi
416
+ stop_server "$CONSUMER/node_modules/@jenga-ai/agent/project/app/ui/scripts/dashboard-start.cjs"
417
+ else
418
+ fail "installed CLI: 'jenga dashboard start --serve-app' skipped — no installed dist/ (see Scenario C above)"
419
+ fi
420
+
421
+ # `jenga dashboard open` always exits 0 by design — run it against nothing listening so it
422
+ # takes the "not healthy" branch and never actually invokes the OS 'open'/'xdg-open' command,
423
+ # which would pop a real browser window in this harness.
424
+ if ( cd "$CONSUMER" && node "$INSTALLED_BIN" dashboard open --port 41009 >"$SCRATCH/open-e.log" 2>&1 ); then
425
+ pass "installed CLI: 'jenga dashboard open' runs without error"
426
+ else
427
+ fail "installed CLI: 'jenga dashboard open' exited non-zero (see $SCRATCH/open-e.log)"
428
+ fi
429
+ else
430
+ fail "installed CLI: bin/jenga.js not found in installed tarball at $INSTALLED_BIN (see Scenario C above)"
431
+ fi
432
+
433
+ # ── Scenario F: this repo's own scripts still work (regression, story AC4) ──────────────────
434
+ echo "== Scenario F: this repo's own dashboard:start / dashboard:open / ui:build (regression) =="
435
+
436
+ PORT_F=41005
437
+ ( cd "$REPO_ROOT" && JENGA_API_PORT="$PORT_F" npm run dashboard:start >"$SCRATCH/server-f.log" 2>&1 & )
438
+ if wait_for_health "$PORT_F"; then
439
+ pass "this repo: 'npm run dashboard:start' launches a healthy server"
440
+ else
441
+ fail "this repo: 'npm run dashboard:start' never became healthy on port $PORT_F (see $SCRATCH/server-f.log)"
442
+ fi
443
+ # Invoked via nested `npm run --prefix` layers rather than a direct absolute `node <path>` call
444
+ # (unlike Scenarios A/B/C/D), so the underlying process's argv may carry the script's path in
445
+ # relative form. Match on the filename alone — by this point in the script, every earlier
446
+ # dashboard-start.cjs instance (Scenarios C, D, and E) has already been explicitly stopped, so
447
+ # this is unambiguous in practice.
448
+ stop_server "dashboard-start.cjs"
449
+
450
+ # dashboard:open always exits 0 by design (see project/app/ui/scripts/dashboard-open.cjs) — run
451
+ # it with nothing listening on its default port so it takes the "not healthy" branch and never
452
+ # actually invokes the OS 'open'/'xdg-open' command, which would pop a real browser window.
453
+ if ( cd "$REPO_ROOT" && JENGA_API_PORT=41006 npm run dashboard:open >"$SCRATCH/open-f.log" 2>&1 ); then
454
+ pass "this repo: 'npm run dashboard:open' runs without error"
455
+ else
456
+ fail "this repo: 'npm run dashboard:open' exited non-zero (see $SCRATCH/open-f.log)"
457
+ fi
458
+
459
+ if ( cd "$REPO_ROOT" && npm run ui:build >"$SCRATCH/ui-build-f.log" 2>&1 ); then
460
+ pass "this repo: 'npm run ui:build' runs without error"
461
+ else
462
+ fail "this repo: 'npm run ui:build' exited non-zero (see $SCRATCH/ui-build-f.log)"
463
+ fi
464
+
465
+ echo
466
+ echo "── Results: $PASS passed, $FAIL failed ──"
467
+ if [ "$FAIL" -gt 0 ]; then
468
+ exit 1
469
+ fi
470
+ exit 0
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: j.cloud-connect
3
+ description: Guided cloud storage setup wizard — installs rclone if missing, lets you pick any rclone-supported backend from a live provider list, runs that backend's own config/auth flow, and independently verifies the remote works.
4
+ keywords:
5
+ - cloud connect
6
+ - connect cloud storage
7
+ - rclone setup
8
+ - configure remote
9
+ - cloud storage backend
10
+ - google drive setup
11
+ examples:
12
+ - "connect a cloud storage account"
13
+ - "set up rclone"
14
+ - "j.cloud-connect"
15
+ - "configure a google drive remote"
16
+ - "add a cloud storage backend"
17
+ ---
18
+
19
+ # Cloud Connect — Guided rclone Setup Wizard
20
+
21
+ ## Purpose
22
+
23
+ `E60_S01` builds a single guided flow for connecting a cloud storage backend via `rclone`: install
24
+ (or detect) `rclone`, pick a backend from rclone's own live provider list, run that backend's own
25
+ `rclone config create` prompts (surfacing any auth URL directly), wait for explicit user confirmation,
26
+ then independently verify the remote works. All of that behavior already lives in two standalone
27
+ scripts — `scripts/install-rclone.sh` (`E60_S01_T01`) and `scripts/configure-backend.sh`
28
+ (`E60_S01_T02`) — each fully self-contained, argument-driven, and independently tested. Per this
29
+ repo's "Scripts Over Inline Logic" principle (`CLAUDE.md`), this `SKILL.md` introduces **no new
30
+ install, selection, config, auth, or verification logic of its own** — it is a thin entry point that
31
+ sequences those two scripts and relays their output.
32
+
33
+ ## Instructions
34
+
35
+ 1. **Run the install/detect step first.** Invoke:
36
+
37
+ ```
38
+ bash skills/j-cloud-connect/scripts/install-rclone.sh
39
+ ```
40
+
41
+ Relay its stdout/stderr to the user as-is (it already produces clear, human-readable status —
42
+ e.g. "rclone is already installed at ...", or an actionable error for an unsupported OS, a
43
+ missing package manager, or a network failure). Do not reinterpret or summarize away its output.
44
+
45
+ - **Exit 0:** `rclone` is confirmed on `PATH` and runnable (whether it was already present or was
46
+ just installed). Proceed to step 2.
47
+ - **Non-zero exit:** installation failed or could not be verified. Stop here — do not proceed to
48
+ step 2 — and surface the script's error message to the user unchanged. It already names the
49
+ specific cause (unsupported OS, missing Homebrew/curl/sudo, network failure, post-install
50
+ verification failure) and, where applicable, a manual-install fallback URL.
51
+
52
+ 2. **Run the backend selection/config/auth/verify step.** Invoke:
53
+
54
+ ```
55
+ bash skills/j-cloud-connect/scripts/configure-backend.sh
56
+ ```
57
+
58
+ with stdio fully inherited/interactive — this script prompts the user directly (backend number,
59
+ remote name, an explicit yes/no confirmation once any backend auth flow has run, e.g. opening an
60
+ OAuth URL) and must be allowed to do so; do not pre-supply answers or short-circuit its prompts.
61
+
62
+ - If the user already stated a specific backend and remote name in their request (e.g. "connect
63
+ my Google Drive as gdrive"), you may pass them positionally instead of relying on the
64
+ interactive prompts: `bash skills/j-cloud-connect/scripts/configure-backend.sh <backend-type>
65
+ <remote-name>` — the script validates `<backend-type>` against the live provider list itself
66
+ and fails clearly if it doesn't match. If the requested backend name doesn't resolve, fall back
67
+ to running the script with no arguments so the user can pick from the live menu.
68
+ - If the user only wants to see what backends are available without configuring one yet, run
69
+ `bash skills/j-cloud-connect/scripts/configure-backend.sh --list` instead and report the printed
70
+ menu — it is a read-only, no-side-effect mode.
71
+ - Relay all of the script's output to the user as-is, including any auth URL rclone's own
72
+ `config create` flow prints — never paraphrase, hide, or re-derive that URL.
73
+ - **Exit 0:** the script itself already printed a `PASS: remote '<name>' is configured and
74
+ verified working.` line plus `rclone about` output — relay that verbatim as the final result.
75
+ - **Non-zero exit:** the script already printed the specific failure reason (invalid backend
76
+ selection, `rclone config create` failure, a declined confirmation, or a `FAIL:` verification
77
+ line from `rclone about`) — relay it unchanged rather than guessing at the cause.
78
+
79
+ 3. **Do not run step 2 if step 1 failed.** The wizard is a strict sequence: `configure-backend.sh`
80
+ itself assumes `rclone` is already on `PATH` and will `die` immediately with a pointer back to
81
+ `install-rclone.sh` if it isn't, so there is nothing to gain by invoking it after a failed
82
+ install — surface the step 1 failure and stop.
83
+
84
+ ## Out of Scope
85
+
86
+ - Any install, detection, or OS-specific package-manager logic — that is entirely
87
+ `scripts/install-rclone.sh`'s scope (`E60_S01_T01`). Do not duplicate or reimplement any part of it
88
+ here.
89
+ - Any backend menu sourcing, `rclone config create` invocation, auth-confirmation prompt, or
90
+ post-confirmation `rclone about` verification — that is entirely `scripts/configure-backend.sh`'s
91
+ scope (`E60_S01_T02`). Do not duplicate, hardcode a backend list, or reimplement any part of it
92
+ here.
93
+ - Per-backend bespoke guidance (e.g. special-casing Google Drive's auth flow beyond what rclone's own
94
+ `config create` already prints) — the story's acceptance criteria require the identical flow for
95
+ every backend; any such special-casing belongs to neither script nor this `SKILL.md`.