@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.
- package/README.md +52 -12
- package/agents/developer.md +16 -1
- package/agents/scrum-master.md +1 -0
- package/bin/jenga.js +10 -0
- package/lib/commands/dashboard.js +92 -0
- package/lib/skill-allow-list.json +6 -2
- package/package.json +21 -2
- package/project/app/api/lib/resolve-project-root.js +120 -0
- package/project/app/api/package.json +16 -0
- package/project/app/api/parsers/architecture.js +72 -0
- package/project/app/api/parsers/board.js +141 -0
- package/project/app/api/parsers/documentation.js +125 -0
- package/project/app/api/parsers/git-log.js +52 -0
- package/project/app/api/parsers/ideas.js +62 -0
- package/project/app/api/parsers/knowledge-graph.js +73 -0
- package/project/app/api/parsers/lib/markdown-dir-reader.js +163 -0
- package/project/app/api/parsers/rapports.js +148 -0
- package/project/app/api/parsers/todo.js +179 -0
- package/project/app/api/response.js +47 -0
- package/project/app/api/routes/architecture.js +23 -0
- package/project/app/api/routes/board.js +46 -0
- package/project/app/api/routes/documentation.js +24 -0
- package/project/app/api/routes/health.js +25 -0
- package/project/app/api/routes/history.js +55 -0
- package/project/app/api/routes/rapports.js +24 -0
- package/project/app/api/scripts/capture-snapshot.js +294 -0
- package/project/app/api/server.js +112 -0
- package/project/app/api/types.js +40 -0
- package/project/app/package.json +21 -0
- package/project/app/ui/dist/assets/index-7fj-vllY.js +104 -0
- package/project/app/ui/dist/assets/index-CdK3Qrep.css +1 -0
- package/project/app/ui/dist/index.html +13 -0
- package/project/app/ui/package.json +23 -0
- package/project/app/ui/scripts/build-snapshot-html.cjs +214 -0
- package/project/app/ui/scripts/dashboard-open.cjs +88 -0
- package/project/app/ui/scripts/dashboard-start.cjs +87 -0
- package/scripts/acquire-concurrency-slot.sh +220 -0
- package/scripts/compute-deploy-reconcile.sh +439 -0
- package/scripts/jenga-permission-level-switch.sh +19 -3
- package/scripts/mark-deployed.sh +532 -0
- package/scripts/populate-knowledge-graph.js +429 -0
- package/scripts/release-concurrency-slot.sh +129 -0
- package/scripts/validate-board.sh +60 -2
- package/scripts/verify-consumer-install.sh +470 -0
- package/skills/j-cloud-connect/SKILL.md +95 -0
- package/skills/j-cloud-connect/scripts/configure-backend.sh +267 -0
- package/skills/j-cloud-connect/scripts/install-rclone.sh +153 -0
- package/skills/j-dashboard/SKILL.md +144 -0
- package/skills/j-dashboard/scripts/launch.sh +121 -0
- package/skills/j-dashboard/scripts/resolve-app-dir.sh +164 -0
- package/skills/j-dashboard/scripts/snapshot.sh +267 -0
- package/skills/j-dashboard-share/SKILL.md +96 -0
- package/skills/j-dashboard-share/scripts/upload-snapshot.sh +173 -0
- package/skills/j-init/SKILL.md +52 -13
- package/skills/j-init/assets/.gitignore_template +1 -2
- package/skills/j-init/scripts/apply-scaffold-visibility.sh +192 -0
- package/skills/j-init/scripts/init.sh +19 -5
- package/skills/j-playbook/SKILL.md +12 -0
- package/skills/j-playbook-new/SKILL.md +155 -0
- package/skills/j-playbook-new/scripts/playbook-new.sh +332 -0
- package/skills/j-publish/scripts/npm_ci_pipeline.sh +6 -0
- package/skills/j-skillify/assets/init-new/assets/.gitignore_template +1 -2
- package/skills/j-uncharted/SKILL.md +54 -7
- package/skills/j-uncharted/assets/NODE_QUESTION_TEMPLATE.md +69 -0
- package/skills/j-uncharted/scripts/elicitation-state.sh +45 -7
- package/skills/jenga/scripts/load-nl-catalog.js +22 -6
- package/skills/jenga/scripts/load-playbooks.sh +146 -35
- 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`.
|