@jenga-ai/agent 1.2.4 → 1.3.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 (42) hide show
  1. package/README.md +1 -0
  2. package/agents/developer.md +18 -0
  3. package/agents/scrum-master.md +1 -0
  4. package/agents/tester.md +18 -0
  5. package/hooks/on_session_end.sh +27 -0
  6. package/package.json +18 -17
  7. package/skills/commit/SKILL.md +11 -1
  8. package/skills/dev-done/SKILL.md +46 -0
  9. package/skills/dev-done/scripts/classify-commit-outcome.sh +114 -0
  10. package/skills/init/SKILL.md +7 -6
  11. package/skills/init/assets/scope-thresholds_template.json +7 -0
  12. package/skills/init/scripts/init.sh +6 -0
  13. package/skills/publish/SKILL.md +66 -0
  14. package/skills/publish/adapters/npm-ci.md +34 -0
  15. package/skills/publish/adapters/npm.md +18 -0
  16. package/skills/publish/assets/ci-contract.md +27 -0
  17. package/skills/publish/assets/publish.example.json +27 -0
  18. package/skills/publish/schemas/publish.schema.json +20 -0
  19. package/skills/publish/scripts/npm_ci_pipeline.sh +29 -0
  20. package/skills/publish/scripts/npm_stage_inspect.sh +829 -0
  21. package/skills/publish/scripts/npm_stage_pipeline.sh +427 -0
  22. package/skills/publish/scripts/publish_common.sh +16 -0
  23. package/skills/publish/scripts/show_history.sh +12 -5
  24. package/skills/publish/scripts/validate_npm_stage_env.sh +184 -0
  25. package/skills/publish/scripts/write_ledger_entry.sh +92 -2
  26. package/skills/reconcile/SKILL.md +121 -11
  27. package/skills/reconcile/assets/report_format.md +17 -0
  28. package/skills/reconcile/scripts/resolve-reconcile-scope.sh +489 -0
  29. package/skills/uncharted/SKILL.md +200 -21
  30. package/skills/uncharted/scripts/directory-triage.sh +342 -0
  31. package/skills/uncharted/scripts/elicitation-state.sh +457 -0
  32. package/templates/SCRUM_BOARD_SCHEMA.md +57 -0
  33. package/templates/agent-context.md.tpl +23 -11
  34. package/templates/copilot-instructions.md.tpl +21 -11
  35. package/mcp/router/README.md +0 -19
  36. package/mcp/router/embedder.js +0 -23
  37. package/mcp/router/index.js +0 -204
  38. package/mcp/router/matcher.js +0 -87
  39. package/mcp/router/package-lock.json +0 -1048
  40. package/mcp/router/package.json +0 -11
  41. package/mcp/router/skill-index.js +0 -104
  42. package/skills/route/SKILL.md +0 -180
@@ -0,0 +1,457 @@
1
+ #!/usr/bin/env bash
2
+ # ---------------------------------------------------------------------------
3
+ # skills/uncharted/scripts/elicitation-state.sh
4
+ #
5
+ # Deterministic persistence + turn-cap counting for `/uncharted`'s
6
+ # conversational convergence loop (E20_S08_T03) — the "propose understanding,
7
+ # confirm/correct" cycle that runs during `onboard`'s default (non-`--legacy`)
8
+ # flow and `segment --mode investigate`.
9
+ #
10
+ # Two problems this script exists to solve mechanically rather than leave to
11
+ # agent memory across a long, possibly multi-session conversation:
12
+ #
13
+ # 1. HARD TURN CAP (solution-assessment-uncharted-interactive-elicitation.md,
14
+ # Problem 10). A convergence loop with no termination bound can run
15
+ # forever on a genuinely hard case. `turn` increments a per-node
16
+ # counter and exits 3 — not 0 — the instant the cap is reached, so the
17
+ # caller gets an unmissable, mechanical stop signal instead of having to
18
+ # remember to compare numbers itself.
19
+ #
20
+ # 2. MULTI-SESSION PERSISTENCE (same document, Problem 11). A whole-
21
+ # codebase onboard conversation can span many sessions. `init` /
22
+ # `checkpoint` / `pause` / `complete` / `list-paused` give the
23
+ # conversation a durable, resumable state file, checkpointed after each
24
+ # converged node (per that problem's RECOMMENDED solution) rather than
25
+ # only at the very end.
26
+ #
27
+ # This script performs NO judgement — it does not decide what to ask, what
28
+ # counts as high-impact, or when understanding has actually converged. It
29
+ # only counts turns, tracks status, and persists whatever the agent asks it
30
+ # to persist. All of that judgement lives in skills/uncharted/SKILL.md's
31
+ # Convergence Loop subsection.
32
+ #
33
+ # ---------------------------------------------------------------------------
34
+ # STATE FILE
35
+ # ---------------------------------------------------------------------------
36
+ # project/queue/elicitation-state/<id>.json — one file per elicitation run
37
+ # (a whole `onboard` pass, or a single `segment --mode investigate` target).
38
+ # This directory is git-ignored (mirrors project/queue/handoffs/ — see
39
+ # templates/SCRUM_BOARD_SCHEMA.md): it is session-scratch resumability data,
40
+ # never a durable artifact. The durable output of a converged elicitation is
41
+ # the graph write (project/knowledge-graph/graph.json, per the stub schema at
42
+ # project/knowledge-graph/STUB_SCHEMA.md) and the `[ARCH]`-tagged board item —
43
+ # both written by the agent driving the flow, never by this script.
44
+ #
45
+ # Shape:
46
+ # {
47
+ # "id": "<id>",
48
+ # "target": "<free text, e.g. the investigated path or description>",
49
+ # "cap": <int>, // hard turn cap, default 5
50
+ # "status": "in_progress" | "paused" | "complete",
51
+ # "created_at": "<ISO 8601 UTC>",
52
+ # "updated_at": "<ISO 8601 UTC>",
53
+ # "nodes": {
54
+ # "<node-id>": { "turns": <int>, "status": "pending"|"converged"|"flagged", "note": "<text>" }
55
+ # },
56
+ # "checkpoint": { ...arbitrary, agent-defined fields, e.g. directory-triage results... }
57
+ # }
58
+ #
59
+ # ---------------------------------------------------------------------------
60
+ # SUBCOMMANDS
61
+ # ---------------------------------------------------------------------------
62
+ # init --id ID [--target TEXT] [--cap N]
63
+ # Create the state file if it does not already exist (default cap 5,
64
+ # per the solution assessment's "3-5 rounds" recommendation). Idempotent
65
+ # — calling init again on an existing id returns the existing state
66
+ # unchanged rather than resetting it, so a resumed session can call
67
+ # init unconditionally without wiping progress.
68
+ #
69
+ # turn --id ID --node NODE
70
+ # Increment NODE's turn counter by one. Prints
71
+ # {"turns": N, "cap": C, "cap_reached": true|false}. EXITS 3 (not 0)
72
+ # when the increment reaches the cap — the node's status is also set to
73
+ # "flagged" in the state file at that point, so the cap event is
74
+ # durable, not just a transient exit code the caller might not act on.
75
+ #
76
+ # converge --id ID --node NODE [--note TEXT]
77
+ # Mark NODE's status "converged", independent of whether the cap was
78
+ # ever hit (a node can converge on turn 1). Optional TEXT is stored as
79
+ # the node's note (e.g. a one-line summary of what was confirmed).
80
+ #
81
+ # checkpoint --id ID --json FILE
82
+ # Shallow-merge the JSON object in FILE (or stdin when FILE is "-")
83
+ # into the state's top-level "checkpoint" field. New keys are added;
84
+ # existing keys are overwritten by the new value. This is the generic
85
+ # "save progress" primitive — directory-triage results, draft node
86
+ # content, anything else the flow wants durable before it might pause.
87
+ #
88
+ # pause --id ID
89
+ # Set status "paused" and update "updated_at". The caller (the agent
90
+ # driving `/uncharted`) is responsible for also writing a scrum-master
91
+ # SessionEnd handoff with status "elicitation_paused" and this state
92
+ # file's path, per skills/uncharted/SKILL.md's Multi-Session
93
+ # Persistence subsection — this script only updates the state file
94
+ # itself, it does not write handoffs.
95
+ #
96
+ # complete --id ID
97
+ # Set status "complete" and update "updated_at". A completed
98
+ # elicitation's state file is left on disk (not deleted) as an audit
99
+ # trail of what was asked and confirmed; nothing currently prunes it.
100
+ #
101
+ # status --id ID
102
+ # Read-only. Prints the current state file, pretty-printed.
103
+ #
104
+ # list-paused
105
+ # Read-only. Prints a JSON array of every state file currently
106
+ # "status": "paused" — {"id", "state_file", "target", "updated_at"} per
107
+ # entry — for a resuming session (or on_session_end.sh's routing logic)
108
+ # to discover what is waiting to be picked back up.
109
+ #
110
+ # ---------------------------------------------------------------------------
111
+ # CONCURRENCY
112
+ # ---------------------------------------------------------------------------
113
+ # Every mutating subcommand (init/turn/converge/checkpoint/pause/complete)
114
+ # wraps its read-modify-write in scripts/with-lock.sh, keyed to the target
115
+ # state file — the same atomic mkdir-based lock already used for board files
116
+ # and events.json (see hooks/on_session_end.sh and
117
+ # templates/SCRUM_BOARD_SCHEMA.md's File Locking section), not a new
118
+ # concurrency mechanism. The write itself is atomic (temp file in the same
119
+ # directory, then `mv`), matching the pattern in hooks/on_session_end.sh's
120
+ # own events.json append.
121
+ #
122
+ # ---------------------------------------------------------------------------
123
+ # EXIT CODES
124
+ # ---------------------------------------------------------------------------
125
+ # 0 — success
126
+ # 1 — usage error: unknown subcommand/flag, missing required argument
127
+ # 2 — input error: state file missing for a subcommand that requires it
128
+ # (everything except init/list-paused), or --json input unreadable/
129
+ # not a JSON object
130
+ # 3 — turn cap reached (ONLY for `turn`; the increment still happened and
131
+ # was persisted — this is a signal to stop looping, not a failure)
132
+ # 4 — write failure: could not acquire the lock, or the atomic write failed
133
+ #
134
+ # Examples:
135
+ # elicitation-state.sh init --id onboard-2026-09-01 --target . --cap 5
136
+ # elicitation-state.sh turn --id onboard-2026-09-01 --node billing-worker
137
+ # elicitation-state.sh converge --id onboard-2026-09-01 --node billing-worker --note "confirmed: reconciles ledger entries"
138
+ # echo '{"directory_triage": {...}}' | elicitation-state.sh checkpoint --id onboard-2026-09-01 --json -
139
+ # elicitation-state.sh pause --id onboard-2026-09-01
140
+ # elicitation-state.sh list-paused
141
+ #
142
+ # Requires: bash, python3, git (to resolve the repo root).
143
+
144
+ set -euo pipefail
145
+
146
+ SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
147
+ REPO_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)
148
+
149
+ if [ -z "$REPO_ROOT" ]; then
150
+ echo "elicitation-state.sh: could not resolve repository root from $SCRIPT_DIR (not inside a git work tree?)" >&2
151
+ exit 2
152
+ fi
153
+
154
+ WITH_LOCK="$REPO_ROOT/scripts/with-lock.sh"
155
+ STATE_DIR="$REPO_ROOT/project/queue/elicitation-state"
156
+ DEFAULT_CAP=5
157
+
158
+ mkdir -p "$STATE_DIR"
159
+
160
+ usage() {
161
+ cat <<'EOF'
162
+ Usage: elicitation-state.sh <subcommand> [options]
163
+
164
+ Subcommands:
165
+ init --id ID [--target TEXT] [--cap N]
166
+ turn --id ID --node NODE
167
+ converge --id ID --node NODE [--note TEXT]
168
+ checkpoint --id ID --json FILE|-
169
+ pause --id ID
170
+ complete --id ID
171
+ status --id ID
172
+ list-paused
173
+
174
+ See this script's own header comment for full semantics of each subcommand.
175
+
176
+ Exit codes: 0 success, 1 usage error, 2 input error, 3 turn cap reached
177
+ (only for `turn`), 4 write failure.
178
+ EOF
179
+ }
180
+
181
+ if [ "$#" -eq 0 ]; then
182
+ usage
183
+ exit 1
184
+ fi
185
+
186
+ SUBCOMMAND="$1"
187
+ shift
188
+
189
+ if [ "$SUBCOMMAND" = "-h" ] || [ "$SUBCOMMAND" = "--help" ]; then
190
+ usage
191
+ exit 0
192
+ fi
193
+
194
+ ID=""
195
+ NODE=""
196
+ TARGET=""
197
+ CAP="$DEFAULT_CAP"
198
+ NOTE=""
199
+ JSON_ARG=""
200
+
201
+ while [ "$#" -gt 0 ]; do
202
+ case "$1" in
203
+ --id)
204
+ [ "$#" -ge 2 ] || { usage; exit 1; }
205
+ ID="$2"; shift 2 ;;
206
+ --node)
207
+ [ "$#" -ge 2 ] || { usage; exit 1; }
208
+ NODE="$2"; shift 2 ;;
209
+ --target)
210
+ [ "$#" -ge 2 ] || { usage; exit 1; }
211
+ TARGET="$2"; shift 2 ;;
212
+ --cap)
213
+ [ "$#" -ge 2 ] || { usage; exit 1; }
214
+ CAP="$2"; shift 2 ;;
215
+ --note)
216
+ [ "$#" -ge 2 ] || { usage; exit 1; }
217
+ NOTE="$2"; shift 2 ;;
218
+ --json)
219
+ [ "$#" -ge 2 ] || { usage; exit 1; }
220
+ JSON_ARG="$2"; shift 2 ;;
221
+ -h|--help)
222
+ usage; exit 0 ;;
223
+ *)
224
+ echo "elicitation-state.sh: unknown argument '$1'" >&2
225
+ exit 1 ;;
226
+ esac
227
+ done
228
+
229
+ case "$SUBCOMMAND" in
230
+ init|turn|converge|checkpoint|pause|complete|status)
231
+ if [ -z "$ID" ]; then
232
+ echo "elicitation-state.sh: --id is required for '$SUBCOMMAND'" >&2
233
+ exit 1
234
+ fi
235
+ ;;
236
+ list-paused)
237
+ ;;
238
+ *)
239
+ echo "elicitation-state.sh: unknown subcommand '$SUBCOMMAND'" >&2
240
+ exit 1
241
+ ;;
242
+ esac
243
+
244
+ if [ "$SUBCOMMAND" = "turn" ] || [ "$SUBCOMMAND" = "converge" ]; then
245
+ if [ -z "$NODE" ]; then
246
+ echo "elicitation-state.sh: --node is required for '$SUBCOMMAND'" >&2
247
+ exit 1
248
+ fi
249
+ fi
250
+
251
+ if [ "$SUBCOMMAND" = "checkpoint" ] && [ -z "$JSON_ARG" ]; then
252
+ echo "elicitation-state.sh: --json is required for 'checkpoint'" >&2
253
+ exit 1
254
+ fi
255
+
256
+ if [ -n "$ID" ]; then
257
+ STATE_FILE="$STATE_DIR/${ID}.json"
258
+ fi
259
+
260
+ # ---------------------------------------------------------------------------
261
+ # Read-only subcommands — no lock needed, nothing is mutated.
262
+ # ---------------------------------------------------------------------------
263
+
264
+ if [ "$SUBCOMMAND" = "status" ]; then
265
+ if [ ! -f "$STATE_FILE" ]; then
266
+ echo "elicitation-state.sh: no state file for id '$ID' ($STATE_FILE)" >&2
267
+ exit 2
268
+ fi
269
+ python3 -m json.tool "$STATE_FILE"
270
+ exit 0
271
+ fi
272
+
273
+ if [ "$SUBCOMMAND" = "list-paused" ]; then
274
+ python3 -c '
275
+ import json, glob, os, sys
276
+
277
+ state_dir = sys.argv[1]
278
+ paused = []
279
+ for path in sorted(glob.glob(os.path.join(state_dir, "*.json"))):
280
+ try:
281
+ with open(path) as f:
282
+ data = json.load(f)
283
+ except (OSError, ValueError):
284
+ continue
285
+ if data.get("status") == "paused":
286
+ paused.append({
287
+ "id": data.get("id", os.path.splitext(os.path.basename(path))[0]),
288
+ "state_file": path,
289
+ "target": data.get("target", ""),
290
+ "updated_at": data.get("updated_at", ""),
291
+ })
292
+ print(json.dumps(paused, indent=2))
293
+ ' "$STATE_DIR"
294
+ exit 0
295
+ fi
296
+
297
+ # ---------------------------------------------------------------------------
298
+ # Mutating subcommands — everything below goes through with-lock.sh.
299
+ # ---------------------------------------------------------------------------
300
+
301
+ if [ ! -f "$WITH_LOCK" ]; then
302
+ echo "elicitation-state.sh: scripts/with-lock.sh not found at $WITH_LOCK" >&2
303
+ exit 4
304
+ fi
305
+
306
+ if [ "$SUBCOMMAND" != "init" ] && [ ! -f "$STATE_FILE" ]; then
307
+ echo "elicitation-state.sh: no state file for id '$ID' ($STATE_FILE) — run 'init' first" >&2
308
+ exit 2
309
+ fi
310
+
311
+ # checkpoint's JSON payload is read here (outside the lock) so a bad/missing
312
+ # file fails fast with exit 2 before ever touching the lock.
313
+ CHECKPOINT_JSON="{}"
314
+ if [ "$SUBCOMMAND" = "checkpoint" ]; then
315
+ if [ "$JSON_ARG" = "-" ]; then
316
+ CHECKPOINT_JSON=$(cat)
317
+ else
318
+ if [ ! -f "$JSON_ARG" ]; then
319
+ echo "elicitation-state.sh: --json file '$JSON_ARG' does not exist" >&2
320
+ exit 2
321
+ fi
322
+ CHECKPOINT_JSON=$(cat "$JSON_ARG")
323
+ fi
324
+ if ! echo "$CHECKPOINT_JSON" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert isinstance(d, dict)' 2>/dev/null; then
325
+ echo "elicitation-state.sh: --json payload is not a JSON object" >&2
326
+ exit 2
327
+ fi
328
+ fi
329
+
330
+ # The update logic is captured as a standalone python3 script and run via
331
+ # `python3 -c` with every value passed as a positional argv entry (never
332
+ # interpolated into the script text), mirroring the same avoid-fragile-
333
+ # quoting convention hooks/on_session_end.sh already uses for its own
334
+ # events.json read-modify-write.
335
+ UPDATE_SCRIPT=$(cat <<'PYEOF'
336
+ import json, os, sys, tempfile, datetime
337
+
338
+ state_file, subcommand, elicitation_id, target, cap_s, node, note, checkpoint_json = sys.argv[1:9]
339
+
340
+ now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
341
+
342
+ def load():
343
+ with open(state_file) as f:
344
+ return json.load(f)
345
+
346
+ def atomic_write(data):
347
+ dir_name = os.path.dirname(state_file)
348
+ fd, tmp_path = tempfile.mkstemp(prefix=".elicitation_tmp.", dir=dir_name)
349
+ try:
350
+ with os.fdopen(fd, "w") as f:
351
+ json.dump(data, f, indent=2)
352
+ f.write("\n")
353
+ os.replace(tmp_path, state_file)
354
+ except Exception:
355
+ if os.path.exists(tmp_path):
356
+ os.remove(tmp_path)
357
+ raise
358
+
359
+ result = {}
360
+ exit_code = 0
361
+
362
+ if subcommand == "init":
363
+ if os.path.exists(state_file):
364
+ state = load()
365
+ result = {"created": False, "state": state}
366
+ else:
367
+ cap = int(cap_s)
368
+ state = {
369
+ "id": elicitation_id,
370
+ "target": target,
371
+ "cap": cap,
372
+ "status": "in_progress",
373
+ "created_at": now,
374
+ "updated_at": now,
375
+ "nodes": {},
376
+ "checkpoint": {},
377
+ }
378
+ atomic_write(state)
379
+ result = {"created": True, "state": state}
380
+
381
+ elif subcommand == "turn":
382
+ state = load()
383
+ nodes = state.setdefault("nodes", {})
384
+ entry = nodes.setdefault(node, {"turns": 0, "status": "pending", "note": ""})
385
+ entry["turns"] = entry.get("turns", 0) + 1
386
+ cap = state.get("cap", int(cap_s))
387
+ cap_reached = entry["turns"] >= cap
388
+ if cap_reached:
389
+ entry["status"] = "flagged"
390
+ state["updated_at"] = now
391
+ atomic_write(state)
392
+ result = {"turns": entry["turns"], "cap": cap, "cap_reached": cap_reached}
393
+ if cap_reached:
394
+ exit_code = 3
395
+
396
+ elif subcommand == "converge":
397
+ state = load()
398
+ nodes = state.setdefault("nodes", {})
399
+ entry = nodes.setdefault(node, {"turns": 0, "status": "pending", "note": ""})
400
+ entry["status"] = "converged"
401
+ if note:
402
+ entry["note"] = note
403
+ state["updated_at"] = now
404
+ atomic_write(state)
405
+ result = {"node": node, "status": "converged", "turns": entry.get("turns", 0)}
406
+
407
+ elif subcommand == "checkpoint":
408
+ state = load()
409
+ payload = json.loads(checkpoint_json)
410
+ cp = state.setdefault("checkpoint", {})
411
+ cp.update(payload)
412
+ state["updated_at"] = now
413
+ atomic_write(state)
414
+ result = {"checkpoint_keys": list(payload.keys())}
415
+
416
+ elif subcommand == "pause":
417
+ state = load()
418
+ state["status"] = "paused"
419
+ state["updated_at"] = now
420
+ atomic_write(state)
421
+ result = {"status": "paused"}
422
+
423
+ elif subcommand == "complete":
424
+ state = load()
425
+ state["status"] = "complete"
426
+ state["updated_at"] = now
427
+ atomic_write(state)
428
+ result = {"status": "complete"}
429
+
430
+ else:
431
+ sys.stderr.write("elicitation-state.sh: internal error — unhandled subcommand '%s'\n" % subcommand)
432
+ sys.exit(1)
433
+
434
+ print(json.dumps(result, indent=2))
435
+ sys.exit(exit_code)
436
+ PYEOF
437
+ )
438
+
439
+ set +e
440
+ # NOTE: unlike `bash -c`, `python3 -c CODE arg1 arg2...` sets sys.argv[0] to
441
+ # the literal string "-c" (not the first following argument) — there is no
442
+ # python3 equivalent of bash's "$0 placeholder" convention. sys.argv[1:9]
443
+ # below therefore lines up directly with the positional arguments given here,
444
+ # with no placeholder needed.
445
+ "$WITH_LOCK" "$STATE_FILE" -- python3 -c "$UPDATE_SCRIPT" \
446
+ "$STATE_FILE" "$SUBCOMMAND" "$ID" "$TARGET" "$CAP" "$NODE" "$NOTE" "$CHECKPOINT_JSON"
447
+ STATUS=$?
448
+ set -e
449
+
450
+ if [ "$STATUS" -eq 2 ]; then
451
+ # with-lock.sh's own "could not acquire the lock" exit code — remap to
452
+ # this script's write-failure code so callers have one code (4), not two,
453
+ # to check for "the mutation did not happen".
454
+ exit 4
455
+ fi
456
+
457
+ exit "$STATUS"
@@ -254,6 +254,62 @@ construction**. Agents and humans reading the board should not treat a backfille
254
254
  Definition of Done as a build plan, and should not assume that an incomplete-looking backfilled
255
255
  epic represents unbuilt functionality.
256
256
 
257
+ ## Board Item Tag Conventions
258
+
259
+ Two bracketed title-tag conventions mark board items whose nature differs from ordinary delivery
260
+ work: `[SPIKE]` (bounded research) and `[ARCH]` (durable architectural inventory). Both are
261
+ **title-text conventions, not frontmatter fields** — there is no `tag:` key; the tag is written
262
+ directly into the item's `title` (e.g. `title: "[SPIKE] Security Section"`) and is not validated or
263
+ enforced by `scripts/validate-board.sh`, which treats `title` as free text. Using either tag is
264
+ advisory: it signals intent to readers of the board, `/status` output, and rollup logic, but nothing
265
+ currently gates on its presence.
266
+
267
+ ### `[SPIKE]` — Bounded Research
268
+
269
+ **Scope:** story and task level only.
270
+
271
+ **Meaning:** a time-boxed research or exploration effort whose output is a decision, a design note,
272
+ or an answered question — **not** shippable implementation code. Existing usage (`E06_S03_spike-editable-board.md`,
273
+ `E08_S02_spike-security-section.md`) follows a consistent shape:
274
+
275
+ - Bracketed title: `title: "[SPIKE] <Topic>"`.
276
+ - A `## Spike Questions to Answer` section (or equivalent) in place of, or alongside, ordinary
277
+ Acceptance Criteria.
278
+ - A Definition of Done line stating that no implementation code is produced by the spike itself —
279
+ only findings, a design note, or a recommendation that a follow-up story/task will act on.
280
+
281
+ `[SPIKE]` predates this document; this section formalizes an existing informal convention rather
282
+ than introducing new behavior.
283
+
284
+ ### `[ARCH]` — Durable Architectural Inventory
285
+
286
+ **Scope:** epic, story, and task level. This is the **first tag extended to epic level** — `[SPIKE]`
287
+ has never applied above story/task.
288
+
289
+ **Meaning:** durable architectural-inventory record-keeping — capturing how existing or
290
+ newly-understood code is structured, so the record persists as a lasting reference — as distinct
291
+ from `[SPIKE]`'s bounded, time-boxed research meaning. `[ARCH]`-tagged items are not "temporary
292
+ until answered" the way a spike is; they are the durable output itself (e.g. graph nodes/edges,
293
+ architecture documentation) and are not expected to be superseded by a subsequent non-`[ARCH]` item
294
+ the way a spike's findings feed into a normal follow-up.
295
+
296
+ Introduced for E20_S08's conversational architecture elicitation flow (`/uncharted` integration),
297
+ where generated board items record architectural understanding of code rather than proposing new
298
+ delivery work, at a scale (potentially a whole investigated subsystem) that can reach epic level.
299
+
300
+ **Explicitly distinct from `[SPIKE]`:**
301
+
302
+ | | `[SPIKE]` | `[ARCH]` |
303
+ |---|---|---|
304
+ | Nature | Bounded, time-boxed research | Durable architectural record |
305
+ | Valid levels | Story, task | Epic, story, task |
306
+ | Typical DoD | "No implementation code produced" | Graph nodes/edges written, or architecture documented |
307
+ | Lifecycle | Findings feed a follow-up item | The record itself is the lasting artifact |
308
+
309
+ Do not use `[ARCH]` and `[SPIKE]` interchangeably or on the same item — pick whichever meaning
310
+ actually applies. An epic can only ever be `[ARCH]` (or untagged); `[SPIKE]` is not valid at epic
311
+ level.
312
+
257
313
  ## Execution Scope Fields (Task)
258
314
 
259
315
  These six fields control the execution footprint of a task within the `/jenga` and `/do` workflows. They are **optional** — omitting all six is valid and equivalent to `execution_scope: task` / `needs_docs: true`.
@@ -429,6 +485,7 @@ A `crucial_escalation` rapport must also name the target item's ID (`E##`, `E##_
429
485
  | `rapport_review` | on_session_end.sh | New problem rapport(s) detected; create backlog items or mark Failed |
430
486
  | `status_review` | on_session_end.sh | Session ended; review board for stale statuses |
431
487
  | `story_rollup` | tester / on_session_end.sh | All tasks under story complete; check rollup |
488
+ | `elicitation_resume` | on_session_end.sh (from a scrum-master `elicitation_paused` handoff) | A `/uncharted` conversational architecture elicitation (E20_S08_T03) paused mid-run; resume it from the state file persisted by `skills/uncharted/scripts/elicitation-state.sh` |
432
489
 
433
490
  ### `developer_triggers.jsonl` — processed by developer at session start
434
491
 
@@ -15,23 +15,35 @@ This project uses **Jenga** — a skill-based AI agent framework. Jenga organise
15
15
 
16
16
  ### Skill Routing
17
17
 
18
- When a user's message matches a known skill keyword or intent, invoke the skill immediately using the slash-command syntax:
19
-
20
- ```
21
- /skill-name
22
- ```
23
-
24
- Do **not** answer skill-related requests with free-form prose if a matching skill exists — delegate to the skill instead.
25
-
26
- For free-form questions (architecture, code review, debugging, general Q&A) that do not match a skill, answer directly using your full capabilities.
18
+ If you are Claude Code, `/skill-name` is a native harness-level mechanism: the harness itself
19
+ intercepts the literal command and loads the skill for you, independent of anything written here. If
20
+ you are any other agent (Codex, or a generic `AGENTS.md` consumer) with no equivalent native
21
+ interception, you depend entirely on the instructions below to know what "invoking a skill" concretely
22
+ means. Do not improvise a plausible-sounding response instead of following these steps — that is the
23
+ exact failure this section exists to prevent.
24
+
25
+ When the user's message is or matches `/skill-name` (or otherwise clearly matches a known skill's
26
+ keyword or intent):
27
+
28
+ 1. Locate the target file at `{{SKILL_DISCOVERY_PATH}}<skill-name>/SKILL.md` (the discovery path from
29
+ "How Jenga Works" above).
30
+ 2. Open and read that file **in full** before doing anything else.
31
+ 3. Execute its instructions exactly as written, for the rest of this turn — including running any
32
+ shell scripts or commands it references (e.g. via a terminal/shell tool).
33
+ 4. Do not substitute your own judgment about what "running the skill" should look like, and do not
34
+ answer with free-form prose describing what the skill would do — the `SKILL.md` file's contents
35
+ are the authoritative procedure, not a summary or a suggestion.
36
+
37
+ For free-form questions (architecture, code review, debugging, general Q&A) that do not match a skill,
38
+ answer directly using your full capabilities.
27
39
 
28
40
  #### Routing decision table
29
41
 
30
42
  | Situation | Action |
31
43
  |-----------|--------|
32
- | Message matches a skill keyword or intent | Invoke `/skill-name` |
44
+ | Message matches a skill keyword or intent | Open `{{SKILL_DISCOVERY_PATH}}<skill-name>/SKILL.md`, read it fully, execute it as written |
33
45
  | Message is a general coding or project question | Answer directly |
34
- | Ambiguous — could be skill or free-form | Prefer the skill; mention it is being invoked |
46
+ | Ambiguous — could be skill or free-form | Prefer the skill; open and execute its `SKILL.md` rather than describing it |
35
47
 
36
48
  ### Available Skills
37
49
 
@@ -15,23 +15,33 @@ This project uses **Jenga** — a skill-based AI agent framework. Jenga organise
15
15
 
16
16
  ### Skill Routing
17
17
 
18
- When a user's message matches a known skill keyword or intent, invoke the skill immediately using the slash-command syntax:
19
-
20
- ```
21
- /skill-name
22
- ```
23
-
24
- Do **not** answer skill-related requests with free-form prose if a matching skill exists — delegate to the skill instead.
25
-
26
- For free-form questions (architecture, code review, debugging, general Q&A) that do not match a skill, answer directly using your full capabilities.
18
+ Unlike Claude Code, GitHub Copilot has **no native slash-command interception** — typing `/skill-name`
19
+ does not automatically load or run anything on its own. Copilot depends entirely on the instructions
20
+ below to know what "invoking a skill" concretely means. Do not improvise a plausible-sounding response
21
+ instead of following these steps — that is the exact failure this section exists to prevent.
22
+
23
+ When the user's message is or matches `/skill-name` (or otherwise clearly matches a known skill's
24
+ keyword or intent):
25
+
26
+ 1. Locate the target file at `.agents/skills/<skill-name>/SKILL.md` (the discovery path from "How
27
+ Jenga Works" above).
28
+ 2. Open and read that file **in full** before doing anything else.
29
+ 3. Execute its instructions exactly as written, for the rest of this turn — including running any
30
+ shell scripts or commands it references (e.g. via a terminal/shell tool).
31
+ 4. Do not substitute your own judgment about what "running the skill" should look like, and do not
32
+ answer with free-form prose describing what the skill would do — the `SKILL.md` file's contents
33
+ are the authoritative procedure, not a summary or a suggestion.
34
+
35
+ For free-form questions (architecture, code review, debugging, general Q&A) that do not match a skill,
36
+ answer directly using your full capabilities.
27
37
 
28
38
  #### Routing decision table
29
39
 
30
40
  | Situation | Action |
31
41
  |-----------|--------|
32
- | Message matches a skill keyword or intent | Invoke `/skill-name` |
42
+ | Message matches a skill keyword or intent | Open `.agents/skills/<skill-name>/SKILL.md`, read it fully, execute it as written |
33
43
  | Message is a general coding or project question | Answer directly |
34
- | Ambiguous — could be skill or free-form | Prefer the skill; mention it is being invoked |
44
+ | Ambiguous — could be skill or free-form | Prefer the skill; open and execute its `SKILL.md` rather than describing it |
35
45
 
36
46
  ### Available Skills
37
47
 
@@ -1,19 +0,0 @@
1
- # jenga-router
2
-
3
- A stdio MCP server that routes incoming prompts to the appropriate Jenga skill.
4
-
5
- ## Usage
6
-
7
- ```bash
8
- node mcp/router/index.js
9
- ```
10
-
11
- ## Tools
12
-
13
- | Tool | Description |
14
- |------|-------------|
15
- | `ping` | Health check — returns `{ ok: true, uptime: <ms> }` |
16
-
17
- ## Protocol
18
-
19
- Communicates over stdin/stdout using the Model Context Protocol (MCP) with `StdioServerTransport`.
@@ -1,23 +0,0 @@
1
- import { pipeline } from "@huggingface/transformers";
2
-
3
- let _pipe = null;
4
-
5
- /**
6
- * Loads the feature-extraction pipeline for Xenova/all-MiniLM-L6-v2.
7
- * Safe to call multiple times — no-op if already loaded.
8
- */
9
- export async function warmUp() {
10
- if (_pipe) return;
11
- const t = Date.now();
12
- _pipe = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
13
- process.stderr.write(`[jenga-router] Model warmed up in ${Date.now() - t}ms\n`);
14
- }
15
-
16
- /**
17
- * Embeds text using the loaded pipeline.
18
- * Returns a Float32Array of length 384.
19
- */
20
- export async function embed(text) {
21
- const output = await _pipe(text, { pooling: "mean", normalize: true });
22
- return output.data;
23
- }