@jenga-ai/agent 3.1.1 → 3.2.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/agents/developer.md +15 -15
- package/agents/scrum-master.md +17 -17
- package/agents/tester.md +25 -15
- package/lib/skill-allow-list.json +3 -2
- package/package.json +1 -1
- package/scripts/audit-twin-divergence.sh +625 -0
- package/scripts/check-public-playbook-steps.sh +136 -0
- package/skills/j-close-story/SKILL.md +1 -1
- 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 +81 -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 +3 -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-playbooks.sh +855 -27
- 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/playbook-types.json +8 -0
- package/skills/jenga/playbooks/brainstorm-to-mirror.json +0 -22
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
# stdout is always a single JSON object, one of:
|
|
64
64
|
#
|
|
65
65
|
# {"classification": "playbook_match", "playbook_id": "brainstorm-to-mirror",
|
|
66
|
-
# "name": "Idea to Public Release", "steps": ["brainstorm", "todo", "do", "dev-done", "mirror-public"]}
|
|
66
|
+
# "name": "Idea to Public Release", "steps": ["j-brainstorm", "j-todo", "j-do", "j-dev-done", "j-mirror-public"]}
|
|
67
67
|
#
|
|
68
68
|
# {"classification": "ambiguous", "candidates": [{"playbook_id": "...", "name": "..."}, ...]}
|
|
69
69
|
#
|
|
@@ -23,15 +23,68 @@
|
|
|
23
23
|
# ---------------------------------------------------------------------------
|
|
24
24
|
# USAGE
|
|
25
25
|
# ---------------------------------------------------------------------------
|
|
26
|
-
# render-playbook-confirmation.sh "<playbook_id>" "<name>" "<comma-separated ordered step names>"
|
|
26
|
+
# render-playbook-confirmation.sh "<playbook_id>" "<name>" "<comma-separated ordered step names>" ["<json-conditionals>" ["<json-origins>"]]
|
|
27
27
|
# Start a new confirmation session. `<playbook_id>` and `<name>` come straight from
|
|
28
28
|
# `match-playbook.sh`'s `playbook_match` output; `<comma-separated ordered step names>` is
|
|
29
29
|
# that same output's `steps` array joined with commas, in original playbook order.
|
|
30
30
|
#
|
|
31
|
+
# `<json-conditionals>` is OPTIONAL (E53_S04_T04). When given, it is a JSON object mapping a
|
|
32
|
+
# conditional step's name -> the name of the EARLIER step it depends on, e.g.
|
|
33
|
+
# `{"stepC": "stepA"}`. Only steps that actually carry a conditional (per `load-playbooks.sh`'s
|
|
34
|
+
# StepObject `conditional` field, resolved by the calling agent from the playbook's raw
|
|
35
|
+
# StepObject data before invoking this script -- this script owns only the RENDERING side of
|
|
36
|
+
# that contract, never the resolution of it) appear as keys. Omitting this argument (the
|
|
37
|
+
# original 3-arg form) renders the chain exactly as before this task -- fully backward
|
|
38
|
+
# compatible.
|
|
39
|
+
#
|
|
40
|
+
# `<json-origins>` is OPTIONAL (E53_S05_T03), and may only be given when `<json-conditionals>`
|
|
41
|
+
# is also given (pass `"{}"` for conditionals if there are none, to reach the 5th slot). It is
|
|
42
|
+
# a JSON object mapping a composed/nested step's name -> `{"playbook_id": "...", "depth": N}`,
|
|
43
|
+
# for every step whose `_origin_depth` (per `load-playbooks.sh`'s catalog output, E53_S05_T01)
|
|
44
|
+
# is greater than 1. Steps absent from this object are depth-1 (the top-level playbook's own
|
|
45
|
+
# steps) and render exactly as before this task. The calling agent (`/jenga`'s
|
|
46
|
+
# natural-language branch, wired in E53_S05_T07) resolves this metadata from
|
|
47
|
+
# `load-playbooks.sh`'s catalog output before invoking this script -- this script owns only
|
|
48
|
+
# the RENDERING side of that contract, mirroring `<json-conditionals>`'s own scope boundary.
|
|
49
|
+
# Omitting this argument entirely reproduces the exact pre-this-task behavior, fully backward
|
|
50
|
+
# compatible.
|
|
51
|
+
#
|
|
31
52
|
# render-playbook-confirmation.sh <state_file> "<raw_reply>"
|
|
32
53
|
# Continue an existing confirmation session. <state_file> is the path printed on STDERR by
|
|
33
54
|
# the start-mode invocation (or by any prior continue-mode invocation). <raw_reply> is the
|
|
34
|
-
# user's raw chat text for this turn.
|
|
55
|
+
# user's raw chat text for this turn. The conditional markers and origin/nesting annotations
|
|
56
|
+
# (if any) persist across continue-mode re-renders automatically -- they are stored in the
|
|
57
|
+
# state file, computed once at start, and never recomputed from arguments.
|
|
58
|
+
#
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# CONDITIONAL MARKER DISPLAY (E53_S04_T02 / E53_S04_T04)
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# A step whose name is a key in the conditionals map gets a visible suffix appended to its line in
|
|
63
|
+
# the rendered chain: " (may be skipped depending on step N's result)", where N is the 1-indexed
|
|
64
|
+
# DISPLAY POSITION (per this session's `order`, not necessarily the original playbook array index)
|
|
65
|
+
# of the step it depends on. This makes the chain's real conditional structure visible before
|
|
66
|
+
# confirmation, per the story's explicit requirement that confirming a chain never hides its real
|
|
67
|
+
# step count or conditional structure from the user. This is display-only: it does not change
|
|
68
|
+
# checked/unchecked mechanics, does not add a new toggle command, and unchecking a conditional step
|
|
69
|
+
# (or the step it depends on) behaves exactly like unchecking any other step -- the ACTUAL runtime
|
|
70
|
+
# skip decision is `run-playbook-step.sh should-skip`'s job, entirely independent of what a user
|
|
71
|
+
# checks/unchecks here.
|
|
72
|
+
#
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# NESTED/ORIGIN DISPLAY (E53_S05_T03)
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# A step whose name is a key in the origins map (depth > 1, i.e. it was spliced in from a composed
|
|
77
|
+
# playbook) gets TWO visual treatments on its rendered line, applied together:
|
|
78
|
+
# - two extra leading spaces of indentation before the checkbox, visually grouping it apart from
|
|
79
|
+
# depth-1 (top-level) steps, and
|
|
80
|
+
# - a trailing annotation: " (from playbook: <playbook_id>, depth <N>)".
|
|
81
|
+
# This is display-only, exactly like the conditional marker, and composes freely with it on the
|
|
82
|
+
# same line (origin annotation first, conditional marker after) -- both are independent,
|
|
83
|
+
# orthogonal annotations. Numbering stays FLAT 1..N across the WHOLE list regardless of nesting --
|
|
84
|
+
# there is still exactly ONE overall numbered, editable, confirmable list, never a separate
|
|
85
|
+
# confirmation per nested playbook, per `CLAUDE.md`'s Interaction Pattern. A playbook with no
|
|
86
|
+
# depth > 1 steps (the `origins` map is empty or the argument was omitted) renders identically to
|
|
87
|
+
# before this task -- no indentation, no annotation, byte-for-byte unchanged.
|
|
35
88
|
#
|
|
36
89
|
# ---------------------------------------------------------------------------
|
|
37
90
|
# TOGGLE COMMAND GRAMMAR (identical to render-confirmation.sh — no cascade, since steps are flat)
|
|
@@ -89,19 +142,21 @@ if ! command -v python3 >/dev/null 2>&1; then
|
|
|
89
142
|
exit 2
|
|
90
143
|
fi
|
|
91
144
|
|
|
92
|
-
if [ $# -eq 3 ]; then
|
|
145
|
+
if [ $# -eq 3 ] || [ $# -eq 4 ] || [ $# -eq 5 ]; then
|
|
93
146
|
MODE="start"
|
|
94
147
|
PLAYBOOK_ID="$1"
|
|
95
148
|
PLAYBOOK_NAME="$2"
|
|
96
149
|
RAW_STEPS="$3"
|
|
150
|
+
RAW_CONDITIONALS="${4:-}"
|
|
151
|
+
RAW_ORIGINS="${5:-}"
|
|
97
152
|
elif [ $# -eq 2 ]; then
|
|
98
153
|
MODE="continue"
|
|
99
154
|
STATE_FILE="$1"
|
|
100
155
|
RAW_REPLY="$2"
|
|
101
156
|
else
|
|
102
157
|
echo "Usage:" >&2
|
|
103
|
-
echo " render-playbook-confirmation.sh \"<playbook_id>\" \"<name>\" \"<comma-separated ordered step names>\" # start" >&2
|
|
104
|
-
echo " render-playbook-confirmation.sh <state_file> \"<raw_reply>\"
|
|
158
|
+
echo " render-playbook-confirmation.sh \"<playbook_id>\" \"<name>\" \"<comma-separated ordered step names>\" [\"<json-conditionals>\" [\"<json-origins>\"]] # start" >&2
|
|
159
|
+
echo " render-playbook-confirmation.sh <state_file> \"<raw_reply>\" # continue" >&2
|
|
105
160
|
exit 2
|
|
106
161
|
fi
|
|
107
162
|
|
|
@@ -125,6 +180,8 @@ state_file_path = sys.argv[1]
|
|
|
125
180
|
playbook_id = sys.argv[2]
|
|
126
181
|
playbook_name = sys.argv[3]
|
|
127
182
|
raw_steps = sys.argv[4]
|
|
183
|
+
raw_conditionals = sys.argv[5] if len(sys.argv) > 5 and sys.argv[5] != "" else None
|
|
184
|
+
raw_origins = sys.argv[6] if len(sys.argv) > 6 and sys.argv[6] != "" else None
|
|
128
185
|
|
|
129
186
|
steps = [s.strip() for s in raw_steps.split(",") if s.strip() != ""]
|
|
130
187
|
|
|
@@ -138,6 +195,56 @@ for i, step in enumerate(steps, start=1):
|
|
|
138
195
|
|
|
139
196
|
total = len(numbering)
|
|
140
197
|
|
|
198
|
+
conditionals = {}
|
|
199
|
+
if raw_conditionals is not None:
|
|
200
|
+
try:
|
|
201
|
+
parsed = json.loads(raw_conditionals)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
print(f"Error: <json-conditionals> is not valid JSON: {e}", file=sys.stderr)
|
|
204
|
+
sys.exit(2)
|
|
205
|
+
if not isinstance(parsed, dict):
|
|
206
|
+
print("Error: <json-conditionals> must be a JSON object", file=sys.stderr)
|
|
207
|
+
sys.exit(2)
|
|
208
|
+
for step_name, depends_on in parsed.items():
|
|
209
|
+
if step_name not in steps:
|
|
210
|
+
print(f"Error: conditional given for '{step_name}', which is not in the step list", file=sys.stderr)
|
|
211
|
+
sys.exit(2)
|
|
212
|
+
if not isinstance(depends_on, str) or depends_on not in steps:
|
|
213
|
+
print(f"Error: conditional for '{step_name}' depends_on '{depends_on}', which is not a step in this playbook", file=sys.stderr)
|
|
214
|
+
sys.exit(2)
|
|
215
|
+
conditionals[step_name] = depends_on
|
|
216
|
+
|
|
217
|
+
# --- E53_S05_T03: origin/nesting metadata (depth > 1 steps only) ---
|
|
218
|
+
origins = {}
|
|
219
|
+
if raw_origins is not None:
|
|
220
|
+
try:
|
|
221
|
+
parsed = json.loads(raw_origins)
|
|
222
|
+
except Exception as e:
|
|
223
|
+
print(f"Error: <json-origins> is not valid JSON: {e}", file=sys.stderr)
|
|
224
|
+
sys.exit(2)
|
|
225
|
+
if not isinstance(parsed, dict):
|
|
226
|
+
print("Error: <json-origins> must be a JSON object", file=sys.stderr)
|
|
227
|
+
sys.exit(2)
|
|
228
|
+
for step_name, meta in parsed.items():
|
|
229
|
+
if step_name not in steps:
|
|
230
|
+
print(f"Error: origin metadata given for '{step_name}', which is not in the step list", file=sys.stderr)
|
|
231
|
+
sys.exit(2)
|
|
232
|
+
if (
|
|
233
|
+
not isinstance(meta, dict)
|
|
234
|
+
or not isinstance(meta.get("playbook_id"), str)
|
|
235
|
+
or not meta.get("playbook_id")
|
|
236
|
+
or not isinstance(meta.get("depth"), int)
|
|
237
|
+
or isinstance(meta.get("depth"), bool)
|
|
238
|
+
or meta.get("depth") <= 1
|
|
239
|
+
):
|
|
240
|
+
print(
|
|
241
|
+
f"Error: origin metadata for '{step_name}' must be an object with a non-empty "
|
|
242
|
+
f"string 'playbook_id' and an integer 'depth' > 1",
|
|
243
|
+
file=sys.stderr,
|
|
244
|
+
)
|
|
245
|
+
sys.exit(2)
|
|
246
|
+
origins[step_name] = {"playbook_id": meta["playbook_id"], "depth": meta["depth"]}
|
|
247
|
+
|
|
141
248
|
state = {
|
|
142
249
|
"version": 1,
|
|
143
250
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
@@ -146,6 +253,8 @@ state = {
|
|
|
146
253
|
"total_steps": total,
|
|
147
254
|
"order": [str(i) for i in range(1, total + 1)],
|
|
148
255
|
"numbering": numbering,
|
|
256
|
+
"conditionals": conditionals,
|
|
257
|
+
"origins": origins,
|
|
149
258
|
}
|
|
150
259
|
|
|
151
260
|
with open(state_file_path, "w", encoding="utf-8") as f:
|
|
@@ -153,12 +262,34 @@ with open(state_file_path, "w", encoding="utf-8") as f:
|
|
|
153
262
|
f.write("\n")
|
|
154
263
|
|
|
155
264
|
|
|
265
|
+
def conditional_marker(step_name):
|
|
266
|
+
"""" (may be skipped depending on step N's result)" for a conditional step, else ''."""
|
|
267
|
+
depends_on = conditionals.get(step_name)
|
|
268
|
+
if not depends_on:
|
|
269
|
+
return ""
|
|
270
|
+
for n in state["order"]:
|
|
271
|
+
if numbering[n]["step"] == depends_on:
|
|
272
|
+
return f" (may be skipped depending on step {n}'s result)"
|
|
273
|
+
return "" # depends_on step not found in this display's order -- defensive, should not happen
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def origin_indent_and_marker(step_name):
|
|
277
|
+
"""(indent, suffix) for a depth>1 (composed/nested) step, else ('', ''). See header 'NESTED/
|
|
278
|
+
ORIGIN DISPLAY' -- indentation groups nested steps visually; the suffix names the originating
|
|
279
|
+
playbook and depth. Numbering itself is never affected -- still flat 1..N."""
|
|
280
|
+
meta = origins.get(step_name)
|
|
281
|
+
if not meta:
|
|
282
|
+
return "", ""
|
|
283
|
+
return " ", f" (from playbook: {meta['playbook_id']}, depth {meta['depth']})"
|
|
284
|
+
|
|
285
|
+
|
|
156
286
|
def render_body():
|
|
157
287
|
lines = []
|
|
158
288
|
for n in state["order"]:
|
|
159
289
|
rec = numbering[n]
|
|
160
290
|
box = "[x]" if rec["checked"] else "[ ]"
|
|
161
|
-
|
|
291
|
+
indent, origin_suffix = origin_indent_and_marker(rec["step"])
|
|
292
|
+
lines.append(f"{indent} {box} {n}. {rec['step']}{origin_suffix}{conditional_marker(rec['step'])}")
|
|
162
293
|
return lines
|
|
163
294
|
|
|
164
295
|
|
|
@@ -203,7 +334,7 @@ print("\n".join(header + body + footer))
|
|
|
203
334
|
print(f"STATE_FILE: {state_file_path}", file=sys.stderr)
|
|
204
335
|
PY
|
|
205
336
|
|
|
206
|
-
python3 "$PY_SCRIPT" "$STATE_FILE" "$PLAYBOOK_ID" "$PLAYBOOK_NAME" "$RAW_STEPS"
|
|
337
|
+
python3 "$PY_SCRIPT" "$STATE_FILE" "$PLAYBOOK_ID" "$PLAYBOOK_NAME" "$RAW_STEPS" "$RAW_CONDITIONALS" "$RAW_ORIGINS"
|
|
207
338
|
exit 0
|
|
208
339
|
fi
|
|
209
340
|
|
|
@@ -242,6 +373,28 @@ order = state["order"]
|
|
|
242
373
|
total = state["total_steps"]
|
|
243
374
|
playbook_id = state["playbook_id"]
|
|
244
375
|
playbook_name = state["playbook_name"]
|
|
376
|
+
conditionals = state.get("conditionals", {})
|
|
377
|
+
origins = state.get("origins", {})
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def conditional_marker(step_name):
|
|
381
|
+
"""" (may be skipped depending on step N's result)" for a conditional step, else ''."""
|
|
382
|
+
depends_on = conditionals.get(step_name)
|
|
383
|
+
if not depends_on:
|
|
384
|
+
return ""
|
|
385
|
+
for n in order:
|
|
386
|
+
if numbering[n]["step"] == depends_on:
|
|
387
|
+
return f" (may be skipped depending on step {n}'s result)"
|
|
388
|
+
return "" # depends_on step not found in this display's order -- defensive, should not happen
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def origin_indent_and_marker(step_name):
|
|
392
|
+
"""(indent, suffix) for a depth>1 (composed/nested) step, else ('', ''). Read from the state
|
|
393
|
+
file's persisted `origins` map -- never recomputed from arguments on a continue-mode turn."""
|
|
394
|
+
meta = origins.get(step_name)
|
|
395
|
+
if not meta:
|
|
396
|
+
return "", ""
|
|
397
|
+
return " ", f" (from playbook: {meta['playbook_id']}, depth {meta['depth']})"
|
|
245
398
|
|
|
246
399
|
|
|
247
400
|
def render_body():
|
|
@@ -249,7 +402,8 @@ def render_body():
|
|
|
249
402
|
for n in order:
|
|
250
403
|
rec = numbering[n]
|
|
251
404
|
box = "[x]" if rec["checked"] else "[ ]"
|
|
252
|
-
|
|
405
|
+
indent, origin_suffix = origin_indent_and_marker(rec["step"])
|
|
406
|
+
lines.append(f"{indent} {box} {n}. {rec['step']}{origin_suffix}{conditional_marker(rec['step'])}")
|
|
253
407
|
return lines
|
|
254
408
|
|
|
255
409
|
|