@jenga-ai/agent 3.0.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 +10 -3
- package/bin/jenga.js +10 -0
- package/hooks/copilot_session_end.sh +7 -3
- package/hooks/prompt_router_helper.js +17 -5
- package/lib/commands/doctor.js +351 -0
- package/lib/commands/init.js +16 -0
- package/lib/generate-copilot-hooks.js +116 -0
- package/lib/legacy-shipped-paths.json +336 -0
- package/lib/postinstall-manifest.js +469 -0
- package/lib/skill-allow-list.json +1 -1
- package/package.json +2 -1
- package/scripts/build-pages-site.sh +268 -0
- package/scripts/generate-legacy-shipped-paths.js +248 -0
- package/scripts/postinstall.js +205 -2
- package/scripts/verify-legacy-seed-reconcile.sh +254 -0
- package/scripts/verify-postinstall-reconcile.sh +392 -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
- package/templates/copilot-instructions.md.tpl +32 -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
|
|
@@ -97,6 +97,38 @@ Neither layer inspects a skill's *contents*; both defend the invocation-matching
|
|
|
97
97
|
| Message is a general coding or project question | Answer directly |
|
|
98
98
|
| Ambiguous — could be skill or free-form | Prefer the skill; open and execute its `SKILL.md` rather than describing it |
|
|
99
99
|
|
|
100
|
+
### Sub-Agent Delegation (`prefered_agent`)
|
|
101
|
+
|
|
102
|
+
Some skills declare a `metadata.prefered_agent: <agent_name>` field in their `SKILL.md`
|
|
103
|
+
frontmatter. This names a sub-agent persona — a file under `agents/` (`scrum-master`,
|
|
104
|
+
`developer`, `tester`) — that should execute the skill, the same delegation Claude Code performs
|
|
105
|
+
natively for the same skill via root `CLAUDE.md`'s "Skill Frontmatter" section.
|
|
106
|
+
|
|
107
|
+
**Copilot CLI has its own native custom-agent-loading mechanism for this.** The `--agent <name>`
|
|
108
|
+
CLI flag and the interactive `/agent [name]` command both load a custom agent-definition file
|
|
109
|
+
discovered from `.github/agents/*.md` or `.claude/agents/*.md`, keyed by that file's frontmatter
|
|
110
|
+
`name:` field — not its filename. Jenga's `agents/*.md` personas are mirrored into both of those
|
|
111
|
+
paths by `/self-sync` (`skills/self-sync/scripts/run.js`); cite `.github/agents/` as the
|
|
112
|
+
canonical path for this purpose, parallel to how "How Jenga Works" above cites `.agents/skills/`
|
|
113
|
+
as canonical for skills. `.agents/agents/` is also mirrored (for reasons unrelated to Copilot)
|
|
114
|
+
but is a **confirmed Copilot-discovery dead-end** — the native agent loader does not read it;
|
|
115
|
+
never rely on it when resolving a `prefered_agent`.
|
|
116
|
+
|
|
117
|
+
When a matched skill's frontmatter has `metadata.prefered_agent: <agent_name>`:
|
|
118
|
+
|
|
119
|
+
1. Before executing the skill's instructions, load that persona by invoking `--agent
|
|
120
|
+
<agent_name>` (if starting a new `copilot` invocation) or the interactive `/agent
|
|
121
|
+
<agent_name>` command (if already in an interactive session).
|
|
122
|
+
2. Then proceed with the skill's instructions exactly as written, per the "Skill Routing"
|
|
123
|
+
section above — the loaded agent persona governs *how* the skill executes, not *whether* it
|
|
124
|
+
does, and does not change which `SKILL.md` file gets opened or the allow-list check that
|
|
125
|
+
precedes it.
|
|
126
|
+
3. If a skill's frontmatter has no `prefered_agent` field, execute it directly with no agent
|
|
127
|
+
switch.
|
|
128
|
+
|
|
129
|
+
Valid `<agent_name>` values match the agent definitions under `agents/` (frontmatter `name:`
|
|
130
|
+
field, not the filename): `scrum-master`, `developer`, `tester`.
|
|
131
|
+
|
|
100
132
|
### Available Skills
|
|
101
133
|
|
|
102
134
|
{{SKILL_LIST}}
|