@jenga-ai/agent 3.1.0 → 3.1.1
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 +3 -3
- package/lib/skill-allow-list.json +1 -1
- package/package.json +1 -1
- package/scripts/build-pages-site.sh +268 -0
- package/skills/j-uncharted/scripts/elicitation-state.sh +15 -1
- package/skills/jenga/SKILL.md +39 -7
- package/skills/jenga/playbooks/brainstorm-to-mirror.json +22 -0
- package/skills/jenga/playbooks/schema.json +42 -0
- package/skills/jenga/scripts/detect-nl-intent.sh +179 -0
- package/skills/jenga/scripts/load-nl-catalog.js +206 -0
- package/skills/jenga/scripts/load-nl-catalog.sh +65 -0
- package/skills/jenga/scripts/load-playbooks.sh +194 -0
- package/skills/jenga/scripts/match-playbook.sh +262 -0
- package/skills/jenga/scripts/render-playbook-confirmation.sh +363 -0
- package/skills/jenga/scripts/run-playbook-step.sh +273 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ---------------------------------------------------------------------------
|
|
3
|
+
# skills/jenga/scripts/run-playbook-step.sh
|
|
4
|
+
#
|
|
5
|
+
# Deterministic step-SEQUENCING state tracker for a confirmed playbook chain (E53_S02_T03). This
|
|
6
|
+
# script does NOT execute a skill step itself — invoking a skill's `SKILL.md` instructions (and,
|
|
7
|
+
# where applicable, loading `agents/<prefered_agent>.md`) is inherently the calling agent's job
|
|
8
|
+
# and cannot live in a shell script. What belongs in a script, per `CLAUDE.md`'s Skill
|
|
9
|
+
# Implementation Principle, is the deterministic bookkeeping around that: which step is current,
|
|
10
|
+
# which have completed, which have failed, and enforcing that a failure halts the sequence
|
|
11
|
+
# permanently with no silent skip-ahead — exactly the story's acceptance criterion for mid-chain
|
|
12
|
+
# failure handling.
|
|
13
|
+
#
|
|
14
|
+
# `/jenga`'s natural-language branch (wired in E53_S02_T04) uses this script as follows, after
|
|
15
|
+
# `render-playbook-confirmation.sh` (E53_S02_T03) returns a `confirmed` result:
|
|
16
|
+
#
|
|
17
|
+
# 1. `init` with the confirmed, ordered step list -> get the first step to invoke.
|
|
18
|
+
# 2. Invoke that step (as `/route`'s Step 6 already does for a single matched skill).
|
|
19
|
+
# 3. `advance <state_file> passed` (step succeeded) or `advance <state_file> failed [note]`
|
|
20
|
+
# (step failed) -> get the next step, a "complete" signal, or (on failure) a halt report.
|
|
21
|
+
# 4. Repeat 2-3 until "complete" or a halt report is returned.
|
|
22
|
+
#
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# USAGE
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# run-playbook-step.sh init "<playbook_id>" "<name>" "<comma-separated confirmed step names>"
|
|
27
|
+
# Starts a new run. Creates a state file tracking the ordered step list, a current-step
|
|
28
|
+
# pointer (starts at the first step), and empty completed/failed lists. Emits the first
|
|
29
|
+
# step's info as JSON on stdout and a `STATE_FILE:` path on stderr.
|
|
30
|
+
#
|
|
31
|
+
# run-playbook-step.sh advance <state_file> passed
|
|
32
|
+
# Records the CURRENT step as completed and advances the pointer. If more steps remain,
|
|
33
|
+
# emits the next step's info as JSON (same shape as `init`'s stdout). If that was the last
|
|
34
|
+
# step, emits a completion report instead (see OUTPUT SCHEMA) and removes the state file.
|
|
35
|
+
#
|
|
36
|
+
# run-playbook-step.sh advance <state_file> failed ["<note>"]
|
|
37
|
+
# Records the CURRENT step as failed (optionally with a free-text note) and halts the
|
|
38
|
+
# sequence PERMANENTLY — the state file is marked `halted: true` rather than removed, so a
|
|
39
|
+
# further `advance` call against it is rejected (see EXIT CODES). Emits a halt report (see
|
|
40
|
+
# OUTPUT SCHEMA) listing completed / failed / never-run steps.
|
|
41
|
+
#
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# OUTPUT SCHEMA
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# `init` and a `passed` `advance` call that has more steps remaining both emit, on stdout:
|
|
46
|
+
#
|
|
47
|
+
# {"status": "step_ready", "step": "<skill name>", "step_index": 2, "total_steps": 5}
|
|
48
|
+
#
|
|
49
|
+
# A `passed` `advance` call on the FINAL step emits, on stdout (state file removed):
|
|
50
|
+
#
|
|
51
|
+
# {"status": "complete", "playbook_id": "...", "name": "...",
|
|
52
|
+
# "completed": ["<step1>", "<step2>", ...]}
|
|
53
|
+
#
|
|
54
|
+
# A `failed` `advance` call emits, on stdout (state file retained, marked halted):
|
|
55
|
+
#
|
|
56
|
+
# {"status": "halted", "playbook_id": "...", "name": "...",
|
|
57
|
+
# "completed": ["<step1>", ...], "failed_step": "<stepN>", "failed_note": "<note or null>",
|
|
58
|
+
# "never_run": ["<stepN+1>", ...]}
|
|
59
|
+
#
|
|
60
|
+
# Nothing else is ever written to stdout — errors/warnings go to stderr only.
|
|
61
|
+
#
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
# EXIT CODES
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# 0 `init` succeeded; OR `advance passed` succeeded (whether it returned the next step or a
|
|
66
|
+
# "complete" report); OR `advance failed` succeeded in recording the halt (a "halted" report
|
|
67
|
+
# IS the expected, successful outcome of this call — exit 0, not an error)
|
|
68
|
+
# 2 usage error (missing/malformed arguments, unrecognized outcome word), or a real setup
|
|
69
|
+
# problem (python3 unavailable, state file missing/corrupt)
|
|
70
|
+
# 3 `advance` called against a state file already marked `halted: true` from a prior `failed`
|
|
71
|
+
# call — rejected outright rather than silently resuming; this is the "no skip-ahead, no
|
|
72
|
+
# silent resumption after a halt" guard the story's acceptance criteria require
|
|
73
|
+
#
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
set -euo pipefail
|
|
77
|
+
|
|
78
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
79
|
+
echo "Error: python3 is required by run-playbook-step.sh" >&2
|
|
80
|
+
exit 2
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if [ $# -lt 1 ]; then
|
|
84
|
+
echo "Usage:" >&2
|
|
85
|
+
echo " run-playbook-step.sh init \"<playbook_id>\" \"<name>\" \"<comma-separated confirmed step names>\"" >&2
|
|
86
|
+
echo " run-playbook-step.sh advance <state_file> passed" >&2
|
|
87
|
+
echo " run-playbook-step.sh advance <state_file> failed [\"<note>\"]" >&2
|
|
88
|
+
exit 2
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
SUBCOMMAND="$1"
|
|
92
|
+
shift
|
|
93
|
+
|
|
94
|
+
if [ "$SUBCOMMAND" = "init" ]; then
|
|
95
|
+
if [ $# -ne 3 ]; then
|
|
96
|
+
echo 'Usage: run-playbook-step.sh init "<playbook_id>" "<name>" "<comma-separated steps>"' >&2
|
|
97
|
+
exit 2
|
|
98
|
+
fi
|
|
99
|
+
PLAYBOOK_ID="$1"
|
|
100
|
+
PLAYBOOK_NAME="$2"
|
|
101
|
+
RAW_STEPS="$3"
|
|
102
|
+
|
|
103
|
+
if [ -z "${RAW_STEPS// /}" ]; then
|
|
104
|
+
echo "Error: no steps given to run-playbook-step.sh init" >&2
|
|
105
|
+
exit 2
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
STATE_FILE="$(mktemp -t jenga-playbook-run-XXXXXX.json)"
|
|
109
|
+
|
|
110
|
+
PY_SCRIPT="$(mktemp -t run-playbook-step-init-XXXXXX.py)"
|
|
111
|
+
trap 'rm -f "$PY_SCRIPT"' EXIT
|
|
112
|
+
|
|
113
|
+
cat > "$PY_SCRIPT" <<'PY'
|
|
114
|
+
import json
|
|
115
|
+
import sys
|
|
116
|
+
from datetime import datetime, timezone
|
|
117
|
+
|
|
118
|
+
state_file_path = sys.argv[1]
|
|
119
|
+
playbook_id = sys.argv[2]
|
|
120
|
+
playbook_name = sys.argv[3]
|
|
121
|
+
raw_steps = sys.argv[4]
|
|
122
|
+
|
|
123
|
+
steps = [s.strip() for s in raw_steps.split(",") if s.strip() != ""]
|
|
124
|
+
if not steps:
|
|
125
|
+
print("Error: no valid step names parsed from the given list", file=sys.stderr)
|
|
126
|
+
sys.exit(2)
|
|
127
|
+
|
|
128
|
+
state = {
|
|
129
|
+
"version": 1,
|
|
130
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
131
|
+
"playbook_id": playbook_id,
|
|
132
|
+
"playbook_name": playbook_name,
|
|
133
|
+
"steps": steps,
|
|
134
|
+
"current_index": 0,
|
|
135
|
+
"completed": [],
|
|
136
|
+
"halted": False,
|
|
137
|
+
"failed_step": None,
|
|
138
|
+
"failed_note": None,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
with open(state_file_path, "w", encoding="utf-8") as f:
|
|
142
|
+
json.dump(state, f, indent=2)
|
|
143
|
+
f.write("\n")
|
|
144
|
+
|
|
145
|
+
print(json.dumps({
|
|
146
|
+
"status": "step_ready",
|
|
147
|
+
"step": steps[0],
|
|
148
|
+
"step_index": 1,
|
|
149
|
+
"total_steps": len(steps),
|
|
150
|
+
}))
|
|
151
|
+
print(f"STATE_FILE: {state_file_path}", file=sys.stderr)
|
|
152
|
+
PY
|
|
153
|
+
|
|
154
|
+
python3 "$PY_SCRIPT" "$STATE_FILE" "$PLAYBOOK_ID" "$PLAYBOOK_NAME" "$RAW_STEPS"
|
|
155
|
+
exit 0
|
|
156
|
+
|
|
157
|
+
elif [ "$SUBCOMMAND" = "advance" ]; then
|
|
158
|
+
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
|
159
|
+
echo 'Usage: run-playbook-step.sh advance <state_file> passed|failed ["<note>"]' >&2
|
|
160
|
+
exit 2
|
|
161
|
+
fi
|
|
162
|
+
STATE_FILE="$1"
|
|
163
|
+
OUTCOME="$2"
|
|
164
|
+
NOTE="${3:-}"
|
|
165
|
+
|
|
166
|
+
if [ "$OUTCOME" != "passed" ] && [ "$OUTCOME" != "failed" ]; then
|
|
167
|
+
echo "Error: outcome must be 'passed' or 'failed', got '$OUTCOME'" >&2
|
|
168
|
+
exit 2
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
172
|
+
echo "Error: state file not found at $STATE_FILE" >&2
|
|
173
|
+
echo "The playbook run may have expired (e.g. temp dir was cleared), or already completed." >&2
|
|
174
|
+
exit 2
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
PY_SCRIPT="$(mktemp -t run-playbook-step-advance-XXXXXX.py)"
|
|
178
|
+
trap 'rm -f "$PY_SCRIPT"' EXIT
|
|
179
|
+
|
|
180
|
+
cat > "$PY_SCRIPT" <<'PY'
|
|
181
|
+
import json
|
|
182
|
+
import os
|
|
183
|
+
import sys
|
|
184
|
+
|
|
185
|
+
state_file_path = sys.argv[1]
|
|
186
|
+
outcome = sys.argv[2]
|
|
187
|
+
note = sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] != "" else None
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
with open(state_file_path, encoding="utf-8") as f:
|
|
191
|
+
state = json.load(f)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
print(f"Error: could not read/parse state file at {state_file_path}: {e}", file=sys.stderr)
|
|
194
|
+
sys.exit(2)
|
|
195
|
+
|
|
196
|
+
if state.get("halted"):
|
|
197
|
+
print(
|
|
198
|
+
f"Error: this playbook run already halted on step '{state.get('failed_step')}'. "
|
|
199
|
+
"A further advance() call is rejected -- no silent resumption after a halt. "
|
|
200
|
+
"Start a new run with run-playbook-step.sh init if you want to retry.",
|
|
201
|
+
file=sys.stderr,
|
|
202
|
+
)
|
|
203
|
+
sys.exit(3)
|
|
204
|
+
|
|
205
|
+
steps = state["steps"]
|
|
206
|
+
idx = state["current_index"]
|
|
207
|
+
|
|
208
|
+
if idx >= len(steps):
|
|
209
|
+
print(f"Error: state file at {state_file_path} has no current step (already complete).", file=sys.stderr)
|
|
210
|
+
sys.exit(2)
|
|
211
|
+
|
|
212
|
+
current_step = steps[idx]
|
|
213
|
+
|
|
214
|
+
if outcome == "failed":
|
|
215
|
+
state["halted"] = True
|
|
216
|
+
state["failed_step"] = current_step
|
|
217
|
+
state["failed_note"] = note
|
|
218
|
+
never_run = steps[idx + 1:]
|
|
219
|
+
|
|
220
|
+
with open(state_file_path, "w", encoding="utf-8") as f:
|
|
221
|
+
json.dump(state, f, indent=2)
|
|
222
|
+
f.write("\n")
|
|
223
|
+
|
|
224
|
+
print(json.dumps({
|
|
225
|
+
"status": "halted",
|
|
226
|
+
"playbook_id": state["playbook_id"],
|
|
227
|
+
"name": state["playbook_name"],
|
|
228
|
+
"completed": state["completed"],
|
|
229
|
+
"failed_step": current_step,
|
|
230
|
+
"failed_note": note,
|
|
231
|
+
"never_run": never_run,
|
|
232
|
+
}))
|
|
233
|
+
sys.exit(0)
|
|
234
|
+
|
|
235
|
+
# outcome == "passed"
|
|
236
|
+
state["completed"].append(current_step)
|
|
237
|
+
state["current_index"] = idx + 1
|
|
238
|
+
|
|
239
|
+
if state["current_index"] >= len(steps):
|
|
240
|
+
result = {
|
|
241
|
+
"status": "complete",
|
|
242
|
+
"playbook_id": state["playbook_id"],
|
|
243
|
+
"name": state["playbook_name"],
|
|
244
|
+
"completed": state["completed"],
|
|
245
|
+
}
|
|
246
|
+
try:
|
|
247
|
+
os.remove(state_file_path)
|
|
248
|
+
except OSError:
|
|
249
|
+
pass
|
|
250
|
+
print(json.dumps(result))
|
|
251
|
+
sys.exit(0)
|
|
252
|
+
|
|
253
|
+
with open(state_file_path, "w", encoding="utf-8") as f:
|
|
254
|
+
json.dump(state, f, indent=2)
|
|
255
|
+
f.write("\n")
|
|
256
|
+
|
|
257
|
+
next_step = steps[state["current_index"]]
|
|
258
|
+
print(json.dumps({
|
|
259
|
+
"status": "step_ready",
|
|
260
|
+
"step": next_step,
|
|
261
|
+
"step_index": state["current_index"] + 1,
|
|
262
|
+
"total_steps": len(steps),
|
|
263
|
+
}))
|
|
264
|
+
print(f"STATE_FILE: {state_file_path}", file=sys.stderr)
|
|
265
|
+
PY
|
|
266
|
+
|
|
267
|
+
python3 "$PY_SCRIPT" "$STATE_FILE" "$OUTCOME" "$NOTE"
|
|
268
|
+
exit $?
|
|
269
|
+
|
|
270
|
+
else
|
|
271
|
+
echo "Error: unrecognized subcommand '$SUBCOMMAND' (expected 'init' or 'advance')" >&2
|
|
272
|
+
exit 2
|
|
273
|
+
fi
|