@jenga-ai/agent 3.1.1 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -12
- package/agents/developer.md +31 -16
- package/agents/scrum-master.md +18 -17
- package/agents/tester.md +25 -15
- package/bin/jenga.js +10 -0
- package/lib/commands/dashboard.js +92 -0
- package/lib/skill-allow-list.json +7 -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/audit-twin-divergence.sh +625 -0
- package/scripts/check-public-playbook-steps.sh +136 -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-close-story/SKILL.md +1 -1
- 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-do/SKILL.md +19 -19
- package/skills/j-doc-sync/SKILL.md +12 -1
- package/skills/j-idea/SKILL.md +1 -1
- package/skills/j-init/SKILL.md +5 -4
- package/skills/j-init/assets/directory_structure.txt +1 -0
- package/skills/j-init/scripts/detect-existing-codebase.sh +2 -2
- package/skills/j-init/scripts/init.sh +13 -2
- package/skills/j-playbook/SKILL.md +93 -0
- package/skills/j-playbook-new/SKILL.md +155 -0
- package/skills/j-playbook-new/scripts/playbook-new.sh +332 -0
- package/skills/j-proceed/SKILL.md +1 -1
- package/skills/j-publish/SKILL.md +1 -1
- package/skills/j-publish/adapters/npm-ci.md +29 -0
- package/skills/j-publish/scripts/npm_ci_pipeline.sh +9 -0
- package/skills/j-publish/scripts/npm_pipeline.sh +18 -0
- package/skills/j-publish/scripts/npm_stage_pipeline.sh +81 -41
- package/skills/j-reconcile/SKILL.md +1 -0
- package/skills/j-redo/SKILL.md +1 -1
- package/skills/j-status/SKILL.md +12 -0
- package/skills/j-todo/SKILL.md +2 -2
- package/skills/j-uncharted/SKILL.md +8 -7
- package/skills/j-uncharted/scripts/validate-proposed-items.sh +18 -2
- package/skills/jenga/SKILL.md +55 -16
- package/skills/jenga/playbooks/idea-to-committed.json +20 -0
- package/skills/jenga/playbooks/schema.json +1 -1
- package/skills/jenga/scripts/load-nl-catalog.js +22 -6
- package/skills/jenga/scripts/load-playbooks.sh +968 -41
- package/skills/jenga/scripts/match-playbook.sh +1 -1
- package/skills/jenga/scripts/render-playbook-confirmation.sh +162 -8
- package/skills/jenga/scripts/run-playbook-step.sh +535 -42
- package/skills/jenga-permission-level/SKILL.md +4 -4
- package/templates/KNOWLEDGE_GRAPH_STUB_SCHEMA_TEMPLATE.md +128 -0
- package/templates/SCRUM_BOARD_SCHEMA.md +18 -6
- package/templates/playbook-types.json +8 -0
- package/skills/jenga/playbooks/brainstorm-to-mirror.json +0 -22
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ---------------------------------------------------------------------------
|
|
3
|
+
# scripts/acquire-concurrency-slot.sh
|
|
4
|
+
#
|
|
5
|
+
# Acquire a role-scoped concurrency slot against a single orchestrating
|
|
6
|
+
# session's counter file, for E32_S15 (Per-Session Concurrency Cap for
|
|
7
|
+
# Developer/Tester Dispatch). Generalizes the existing per-epic boolean
|
|
8
|
+
# bundle lock ("Epic-Level Bundle Lock" in skills/do/SKILL.md) into a
|
|
9
|
+
# per-role bounded counter with multiple named holders.
|
|
10
|
+
#
|
|
11
|
+
# The counter file lives at:
|
|
12
|
+
# project/queue/concurrency-slots-<session_id>.json
|
|
13
|
+
# and has the shape:
|
|
14
|
+
# {
|
|
15
|
+
# "developer": {"cap": <int>, "holders": {"<holder-id>": "<ISO ts>"}},
|
|
16
|
+
# "tester": {"cap": <int>, "holders": {"<holder-id>": "<ISO ts>"}}
|
|
17
|
+
# }
|
|
18
|
+
#
|
|
19
|
+
# The counter file belongs to exactly the session_id in its own filename.
|
|
20
|
+
# This script never reads or writes a counter file for any session_id other
|
|
21
|
+
# than the one passed as its own argument.
|
|
22
|
+
#
|
|
23
|
+
# Behavior:
|
|
24
|
+
# 1. Before the cap check, any holder entry in the target role's holders
|
|
25
|
+
# map older than slot_ttl_minutes (project/configs/scope-thresholds.json)
|
|
26
|
+
# is dropped ("stale reclaim") — guards against a crashed subagent
|
|
27
|
+
# leaking its slot within THIS session; not a cross-session mechanism.
|
|
28
|
+
# 2. If the role's holder count (after reclaim) is >= its cap, this script
|
|
29
|
+
# exits non-zero and mutates the counter file NOT AT ALL (not even to
|
|
30
|
+
# persist the stale-reclaim result) — a denied acquire is a pure no-op
|
|
31
|
+
# on disk.
|
|
32
|
+
# 3. Otherwise a "<holder-id>: <current ISO timestamp>" entry is added
|
|
33
|
+
# under the role and the script exits 0. If the counter file doesn't
|
|
34
|
+
# exist yet, it is created and seeded with both roles' cap values (read
|
|
35
|
+
# fresh from scope-thresholds.json) and empty holders maps.
|
|
36
|
+
#
|
|
37
|
+
# The whole reclaim + cap-check + mutate sequence runs as ONE atomic critical
|
|
38
|
+
# section under a single scripts/with-lock.sh invocation (this script
|
|
39
|
+
# re-invokes itself with an internal subcommand as the wrapped command) —
|
|
40
|
+
# never as two separate locked calls — so there is no TOCTOU gap between
|
|
41
|
+
# checking the cap and writing the new holder entry.
|
|
42
|
+
#
|
|
43
|
+
# Usage:
|
|
44
|
+
# scripts/acquire-concurrency-slot.sh <role> <holder-id> <session_id>
|
|
45
|
+
#
|
|
46
|
+
# Exit codes:
|
|
47
|
+
# 0 slot acquired — holder entry added (or counter file created + seeded)
|
|
48
|
+
# 1 usage error (bad arguments)
|
|
49
|
+
# 2 scripts/with-lock.sh could not acquire the lock within its timeout —
|
|
50
|
+
# the acquire attempt never ran, counter file untouched
|
|
51
|
+
# 3 role is at/over cap after stale-reclaim — slot NOT acquired, counter
|
|
52
|
+
# file untouched (no mutation, per acceptance criteria)
|
|
53
|
+
# 5 environment error (jq missing, scope-thresholds.json missing/invalid,
|
|
54
|
+
# project root unresolvable)
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
set -euo pipefail
|
|
58
|
+
|
|
59
|
+
SELF="$(basename "$0")"
|
|
60
|
+
|
|
61
|
+
die() {
|
|
62
|
+
local code="$1"; shift
|
|
63
|
+
printf '%s: error: %s\n' "$SELF" "$*" >&2
|
|
64
|
+
exit "$code"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
usage() {
|
|
68
|
+
echo "Usage: $0 <role> <holder-id> <session_id>" >&2
|
|
69
|
+
exit 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Resolve project root (mirrors scripts/write-context-digest.sh's own
|
|
74
|
+
# probing order: JENGA_PROJECT_DIR -> CLAUDE_PROJECT_DIR -> git toplevel ->
|
|
75
|
+
# pwd — kept consistent so this script behaves the same whether invoked
|
|
76
|
+
# directly, from a skill, or from another script).
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
resolve_project_dir() {
|
|
79
|
+
if [ -n "${JENGA_PROJECT_DIR:-}" ]; then
|
|
80
|
+
printf '%s\n' "$JENGA_PROJECT_DIR"
|
|
81
|
+
return 0
|
|
82
|
+
fi
|
|
83
|
+
if [ -n "${CLAUDE_PROJECT_DIR:-}" ]; then
|
|
84
|
+
printf '%s\n' "$CLAUDE_PROJECT_DIR"
|
|
85
|
+
return 0
|
|
86
|
+
fi
|
|
87
|
+
git rev-parse --show-toplevel 2>/dev/null || pwd
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Absolute path to this script itself, so the lock-holding re-invocation
|
|
91
|
+
# below works regardless of how this script was originally invoked (a
|
|
92
|
+
# relative path, a PATH lookup, etc.) and regardless of cwd.
|
|
93
|
+
SCRIPT_ABS_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# Internal locked-mutation entrypoint. Not intended to be invoked directly —
|
|
97
|
+
# scripts/with-lock.sh calls back into this same script with this hidden
|
|
98
|
+
# subcommand once (and only once) the lock on the counter file is held. This
|
|
99
|
+
# is what makes reclaim + cap-check + mutate a single atomic operation
|
|
100
|
+
# instead of two racy locked calls.
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
if [ "${1:-}" = "__locked-acquire" ]; then
|
|
103
|
+
shift
|
|
104
|
+
ROLE="$1"
|
|
105
|
+
HOLDER_ID="$2"
|
|
106
|
+
COUNTER_FILE="$3"
|
|
107
|
+
CONFIG_FILE="$4"
|
|
108
|
+
|
|
109
|
+
command -v jq >/dev/null 2>&1 || die 5 "jq is required but not found on PATH"
|
|
110
|
+
[ -f "$CONFIG_FILE" ] || die 5 "config file not found: $CONFIG_FILE"
|
|
111
|
+
|
|
112
|
+
DEV_CAP="$(jq -r '.max_concurrent_developers // empty' "$CONFIG_FILE")"
|
|
113
|
+
TESTER_CAP="$(jq -r '.max_concurrent_testers // empty' "$CONFIG_FILE")"
|
|
114
|
+
TTL_MINUTES="$(jq -r '.slot_ttl_minutes // empty' "$CONFIG_FILE")"
|
|
115
|
+
|
|
116
|
+
case "$DEV_CAP" in ''|*[!0-9]*) die 5 "invalid or missing max_concurrent_developers in $CONFIG_FILE" ;; esac
|
|
117
|
+
case "$TESTER_CAP" in ''|*[!0-9]*) die 5 "invalid or missing max_concurrent_testers in $CONFIG_FILE" ;; esac
|
|
118
|
+
case "$TTL_MINUTES" in ''|*[!0-9]*) die 5 "invalid or missing slot_ttl_minutes in $CONFIG_FILE" ;; esac
|
|
119
|
+
|
|
120
|
+
case "$ROLE" in
|
|
121
|
+
developer) CAP="$DEV_CAP" ;;
|
|
122
|
+
tester) CAP="$TESTER_CAP" ;;
|
|
123
|
+
*) die 1 "invalid role '$ROLE' (expected developer or tester)" ;;
|
|
124
|
+
esac
|
|
125
|
+
|
|
126
|
+
TTL_SECONDS=$(( TTL_MINUTES * 60 ))
|
|
127
|
+
NOW_EPOCH="$(date -u +%s)"
|
|
128
|
+
NOW_ISO="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
129
|
+
|
|
130
|
+
# Base document: the existing counter file, or an in-memory seed if the
|
|
131
|
+
# file doesn't exist yet. The seed is NOT written to disk here — it only
|
|
132
|
+
# gets persisted below, and only on a successful acquire.
|
|
133
|
+
if [ -f "$COUNTER_FILE" ]; then
|
|
134
|
+
BASE_JSON="$(cat "$COUNTER_FILE")"
|
|
135
|
+
else
|
|
136
|
+
BASE_JSON="$(jq -n \
|
|
137
|
+
--argjson dev_cap "$DEV_CAP" \
|
|
138
|
+
--argjson tester_cap "$TESTER_CAP" \
|
|
139
|
+
'{developer: {cap: $dev_cap, holders: {}}, tester: {cap: $tester_cap, holders: {}}}')"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# Compute the reclaimed+decision document entirely in memory. Nothing is
|
|
143
|
+
# written to $COUNTER_FILE by this jq invocation.
|
|
144
|
+
DECISION_JSON="$(printf '%s' "$BASE_JSON" | jq \
|
|
145
|
+
--arg role "$ROLE" \
|
|
146
|
+
--arg holder "$HOLDER_ID" \
|
|
147
|
+
--arg now_iso "$NOW_ISO" \
|
|
148
|
+
--argjson now_epoch "$NOW_EPOCH" \
|
|
149
|
+
--argjson ttl_seconds "$TTL_SECONDS" \
|
|
150
|
+
--argjson cap "$CAP" \
|
|
151
|
+
'
|
|
152
|
+
# Self-heal the role cap to the freshly-read config value, and drop any
|
|
153
|
+
# stale holder (older than ttl_seconds) before the cap check.
|
|
154
|
+
.[$role].cap = $cap
|
|
155
|
+
| .[$role].holders = ((.[$role].holders // {})
|
|
156
|
+
| with_entries(select((.value | fromdateiso8601) >= ($now_epoch - $ttl_seconds))))
|
|
157
|
+
| (.[$role].holders | length) as $count
|
|
158
|
+
| if $count >= $cap then
|
|
159
|
+
{ok: false, doc: .}
|
|
160
|
+
else
|
|
161
|
+
{ok: true, doc: (.[$role].holders[$holder] = $now_iso)}
|
|
162
|
+
end
|
|
163
|
+
')"
|
|
164
|
+
|
|
165
|
+
OK="$(printf '%s' "$DECISION_JSON" | jq -r '.ok')"
|
|
166
|
+
|
|
167
|
+
if [ "$OK" != "true" ]; then
|
|
168
|
+
# At/over cap: exit non-zero, counter file untouched (no write at all).
|
|
169
|
+
exit 3
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# Success: persist atomically (temp file + rename in the same directory).
|
|
173
|
+
mkdir -p "$(dirname "$COUNTER_FILE")" 2>/dev/null || true
|
|
174
|
+
TMP_FILE="$(mktemp "${COUNTER_FILE}.tmp.XXXXXX")"
|
|
175
|
+
printf '%s' "$DECISION_JSON" | jq '.doc' > "$TMP_FILE"
|
|
176
|
+
mv "$TMP_FILE" "$COUNTER_FILE"
|
|
177
|
+
exit 0
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
# Public entrypoint
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
[ "$#" -eq 3 ] || usage
|
|
184
|
+
|
|
185
|
+
ROLE="$1"
|
|
186
|
+
HOLDER_ID="$2"
|
|
187
|
+
SESSION_ID="$3"
|
|
188
|
+
|
|
189
|
+
case "$ROLE" in
|
|
190
|
+
developer|tester) ;;
|
|
191
|
+
*) die 1 "invalid role '$ROLE' (expected developer or tester)" ;;
|
|
192
|
+
esac
|
|
193
|
+
|
|
194
|
+
[ -n "$HOLDER_ID" ] || die 1 "<holder-id> must not be empty"
|
|
195
|
+
[ -n "$SESSION_ID" ] || die 1 "<session_id> must not be empty"
|
|
196
|
+
|
|
197
|
+
# session_id is used verbatim in a filename below — refuse path separators
|
|
198
|
+
# or traversal rather than silently mangling or escaping it (mirrors
|
|
199
|
+
# scripts/write-context-digest.sh's own path-safety checks).
|
|
200
|
+
case "$SESSION_ID" in
|
|
201
|
+
*/*|*..*) die 1 "invalid <session_id> '$SESSION_ID': must not contain '/' or '..'" ;;
|
|
202
|
+
esac
|
|
203
|
+
|
|
204
|
+
PROJECT_DIR="$(resolve_project_dir)"
|
|
205
|
+
COUNTER_FILE="$PROJECT_DIR/project/queue/concurrency-slots-${SESSION_ID}.json"
|
|
206
|
+
CONFIG_FILE="$PROJECT_DIR/project/configs/scope-thresholds.json"
|
|
207
|
+
|
|
208
|
+
command -v jq >/dev/null 2>&1 || die 5 "jq is required but not found on PATH"
|
|
209
|
+
[ -x "$PROJECT_DIR/scripts/with-lock.sh" ] || [ -f "$PROJECT_DIR/scripts/with-lock.sh" ] \
|
|
210
|
+
|| die 5 "scripts/with-lock.sh not found at $PROJECT_DIR/scripts/with-lock.sh"
|
|
211
|
+
|
|
212
|
+
mkdir -p "$PROJECT_DIR/project/queue" 2>/dev/null || true
|
|
213
|
+
|
|
214
|
+
set +e
|
|
215
|
+
"$PROJECT_DIR/scripts/with-lock.sh" "$COUNTER_FILE" -- \
|
|
216
|
+
"$SCRIPT_ABS_PATH" __locked-acquire "$ROLE" "$HOLDER_ID" "$COUNTER_FILE" "$CONFIG_FILE"
|
|
217
|
+
STATUS=$?
|
|
218
|
+
set -e
|
|
219
|
+
|
|
220
|
+
exit "$STATUS"
|