@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.
Files changed (92) hide show
  1. package/README.md +52 -12
  2. package/agents/developer.md +31 -16
  3. package/agents/scrum-master.md +18 -17
  4. package/agents/tester.md +25 -15
  5. package/bin/jenga.js +10 -0
  6. package/lib/commands/dashboard.js +92 -0
  7. package/lib/skill-allow-list.json +7 -2
  8. package/package.json +21 -2
  9. package/project/app/api/lib/resolve-project-root.js +120 -0
  10. package/project/app/api/package.json +16 -0
  11. package/project/app/api/parsers/architecture.js +72 -0
  12. package/project/app/api/parsers/board.js +141 -0
  13. package/project/app/api/parsers/documentation.js +125 -0
  14. package/project/app/api/parsers/git-log.js +52 -0
  15. package/project/app/api/parsers/ideas.js +62 -0
  16. package/project/app/api/parsers/knowledge-graph.js +73 -0
  17. package/project/app/api/parsers/lib/markdown-dir-reader.js +163 -0
  18. package/project/app/api/parsers/rapports.js +148 -0
  19. package/project/app/api/parsers/todo.js +179 -0
  20. package/project/app/api/response.js +47 -0
  21. package/project/app/api/routes/architecture.js +23 -0
  22. package/project/app/api/routes/board.js +46 -0
  23. package/project/app/api/routes/documentation.js +24 -0
  24. package/project/app/api/routes/health.js +25 -0
  25. package/project/app/api/routes/history.js +55 -0
  26. package/project/app/api/routes/rapports.js +24 -0
  27. package/project/app/api/scripts/capture-snapshot.js +294 -0
  28. package/project/app/api/server.js +112 -0
  29. package/project/app/api/types.js +40 -0
  30. package/project/app/package.json +21 -0
  31. package/project/app/ui/dist/assets/index-7fj-vllY.js +104 -0
  32. package/project/app/ui/dist/assets/index-CdK3Qrep.css +1 -0
  33. package/project/app/ui/dist/index.html +13 -0
  34. package/project/app/ui/package.json +23 -0
  35. package/project/app/ui/scripts/build-snapshot-html.cjs +214 -0
  36. package/project/app/ui/scripts/dashboard-open.cjs +88 -0
  37. package/project/app/ui/scripts/dashboard-start.cjs +87 -0
  38. package/scripts/acquire-concurrency-slot.sh +220 -0
  39. package/scripts/audit-twin-divergence.sh +625 -0
  40. package/scripts/check-public-playbook-steps.sh +136 -0
  41. package/scripts/compute-deploy-reconcile.sh +439 -0
  42. package/scripts/jenga-permission-level-switch.sh +19 -3
  43. package/scripts/mark-deployed.sh +532 -0
  44. package/scripts/populate-knowledge-graph.js +429 -0
  45. package/scripts/release-concurrency-slot.sh +129 -0
  46. package/scripts/validate-board.sh +60 -2
  47. package/scripts/verify-consumer-install.sh +470 -0
  48. package/skills/j-close-story/SKILL.md +1 -1
  49. package/skills/j-cloud-connect/SKILL.md +95 -0
  50. package/skills/j-cloud-connect/scripts/configure-backend.sh +267 -0
  51. package/skills/j-cloud-connect/scripts/install-rclone.sh +153 -0
  52. package/skills/j-dashboard/SKILL.md +144 -0
  53. package/skills/j-dashboard/scripts/launch.sh +121 -0
  54. package/skills/j-dashboard/scripts/resolve-app-dir.sh +164 -0
  55. package/skills/j-dashboard/scripts/snapshot.sh +267 -0
  56. package/skills/j-dashboard-share/SKILL.md +96 -0
  57. package/skills/j-dashboard-share/scripts/upload-snapshot.sh +173 -0
  58. package/skills/j-do/SKILL.md +19 -19
  59. package/skills/j-doc-sync/SKILL.md +12 -1
  60. package/skills/j-idea/SKILL.md +1 -1
  61. package/skills/j-init/SKILL.md +5 -4
  62. package/skills/j-init/assets/directory_structure.txt +1 -0
  63. package/skills/j-init/scripts/detect-existing-codebase.sh +2 -2
  64. package/skills/j-init/scripts/init.sh +13 -2
  65. package/skills/j-playbook/SKILL.md +93 -0
  66. package/skills/j-playbook-new/SKILL.md +155 -0
  67. package/skills/j-playbook-new/scripts/playbook-new.sh +332 -0
  68. package/skills/j-proceed/SKILL.md +1 -1
  69. package/skills/j-publish/SKILL.md +1 -1
  70. package/skills/j-publish/adapters/npm-ci.md +29 -0
  71. package/skills/j-publish/scripts/npm_ci_pipeline.sh +9 -0
  72. package/skills/j-publish/scripts/npm_pipeline.sh +18 -0
  73. package/skills/j-publish/scripts/npm_stage_pipeline.sh +81 -41
  74. package/skills/j-reconcile/SKILL.md +1 -0
  75. package/skills/j-redo/SKILL.md +1 -1
  76. package/skills/j-status/SKILL.md +12 -0
  77. package/skills/j-todo/SKILL.md +2 -2
  78. package/skills/j-uncharted/SKILL.md +8 -7
  79. package/skills/j-uncharted/scripts/validate-proposed-items.sh +18 -2
  80. package/skills/jenga/SKILL.md +55 -16
  81. package/skills/jenga/playbooks/idea-to-committed.json +20 -0
  82. package/skills/jenga/playbooks/schema.json +1 -1
  83. package/skills/jenga/scripts/load-nl-catalog.js +22 -6
  84. package/skills/jenga/scripts/load-playbooks.sh +968 -41
  85. package/skills/jenga/scripts/match-playbook.sh +1 -1
  86. package/skills/jenga/scripts/render-playbook-confirmation.sh +162 -8
  87. package/skills/jenga/scripts/run-playbook-step.sh +535 -42
  88. package/skills/jenga-permission-level/SKILL.md +4 -4
  89. package/templates/KNOWLEDGE_GRAPH_STUB_SCHEMA_TEMPLATE.md +128 -0
  90. package/templates/SCRUM_BOARD_SCHEMA.md +18 -6
  91. package/templates/playbook-types.json +8 -0
  92. 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>\" # continue" >&2
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
- lines.append(f" {box} {n}. {rec['step']}")
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
- lines.append(f" {box} {n}. {rec['step']}")
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