@jenga-ai/agent 3.5.0 → 4.0.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/README.md +85 -78
- package/agents/developer.md +1 -1
- package/agents/scrum-master.md +20 -2
- package/agents/tester.md +3 -3
- package/hooks/on_session_end.sh +5 -5
- package/lib/generate-agent-context.js +2 -2
- package/lib/generate-copilot-hooks.js +1 -1
- package/lib/generate-skill-allow-list.js +79 -8
- package/lib/mirror.js +1 -1
- package/lib/postinstall-manifest.js +1 -1
- package/lib/skill-allow-list.json +2 -2
- package/mcp/help/index.js +8 -17
- package/mcp/help/scan.js +73 -0
- package/package.json +5 -1
- package/project/app/api/lib/resolve-project-root.js +1 -1
- package/project/app/api/parsers/knowledge-graph.js +100 -9
- package/project/app/api/routes/health.js +36 -0
- package/project/app/api/scripts/capture-snapshot.js +9 -6
- package/project/app/package.json +4 -0
- package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
- package/project/app/ui/dist/assets/index-C3oiuli_.js +104 -0
- package/project/app/ui/dist/index.html +2 -2
- package/project/app/ui/package.json +4 -0
- package/project/app/ui/scripts/build-snapshot-html.cjs +63 -2
- package/scripts/acquire-concurrency-slot.sh +35 -5
- package/scripts/apply-j-prefix.sh +46 -5
- package/scripts/build-pages-site.sh +1 -1
- package/scripts/check-public-playbook-steps.sh +158 -52
- package/scripts/check-publicignore-match.sh +2 -2
- package/scripts/compute-deploy-reconcile.sh +5 -5
- package/scripts/delete-bare-skill-dirs.sh +329 -0
- package/scripts/generate-legacy-shipped-paths.js +2 -2
- package/scripts/idea_manager.sh +273 -3
- package/scripts/mark-deployed.sh +2 -2
- package/scripts/populate-knowledge-graph.entity-resolution.test.js +254 -0
- package/scripts/populate-knowledge-graph.js +213 -5
- package/scripts/populate-knowledge-graph.staleness.test.js +130 -0
- package/scripts/postinstall.js +1 -1
- package/scripts/release-concurrency-slot.sh +34 -4
- package/scripts/render-ranked-list.sh +270 -0
- package/scripts/repoint-dead-bare-path-prose.py +81 -0
- package/scripts/repoint-skill-refs.sh +539 -0
- package/scripts/rewrite-stale-skill-preambles.py +188 -0
- package/scripts/strip-polyfill-frontmatter.py +166 -0
- package/scripts/todo_manager.sh +16 -1
- package/scripts/validate-typed-object.sh +750 -0
- package/scripts/verify-legacy-seed-reconcile.sh +10 -10
- package/scripts/verify-postinstall-reconcile.sh +7 -7
- package/scripts/write-context-digest.sh +1 -1
- package/skills/j-brainstorm/SKILL.md +3 -4
- package/skills/j-btw/SKILL.md +3 -4
- package/skills/j-clearify/SKILL.md +5 -6
- package/skills/j-close-story/SKILL.md +11 -12
- package/skills/j-close-story/scripts/check-privatized.sh +4 -4
- package/skills/j-close-story/scripts/check-story-closeable.sh +11 -4
- package/skills/j-commit/SKILL.md +3 -4
- package/skills/j-continue/SKILL.md +5 -6
- package/skills/j-deep-dive/SKILL.md +3 -4
- package/skills/j-distribute/CONFIG_SCHEMA.md +82 -5
- package/skills/j-distribute/SKILL.md +3 -4
- package/skills/j-do/SKILL.md +100 -18
- package/skills/j-doc/SKILL.md +3 -4
- package/skills/j-doc-sync/SKILL.md +4 -4
- package/skills/j-dooo/SKILL.md +6 -15
- package/skills/j-error/SKILL.md +3 -4
- package/skills/j-evaluate/SKILL.md +3 -4
- package/skills/j-examplify/SKILL.md +3 -4
- package/skills/j-gitignore/SKILL.md +157 -0
- package/skills/j-gitignore/assets/jenga-paths.txt +50 -0
- package/skills/j-gitignore/scripts/_catalog.sh +105 -0
- package/skills/j-gitignore/scripts/audit-gitignore.sh +194 -0
- package/skills/j-gitignore/scripts/repair-gitignore.sh +226 -0
- package/skills/j-gitignore/scripts/untrack-jenga-files.sh +210 -0
- package/skills/j-help/SKILL.md +3 -4
- package/skills/j-idea/SKILL.md +80 -9
- package/skills/j-idea/assets/idea_template.md +1 -1
- package/skills/j-improve/SKILL.md +4 -5
- package/skills/j-init/SKILL.md +23 -14
- package/skills/j-init/assets/scope-thresholds_template.json +5 -2
- package/skills/j-init/scripts/apply-scaffold-visibility.sh +9 -7
- package/skills/j-init/scripts/init.sh +4 -4
- package/skills/j-jbp/SKILL.md +3 -4
- package/skills/j-lgtm/SKILL.md +3 -4
- package/skills/j-pi-plan/SKILL.md +3 -4
- package/skills/j-playbook/SKILL.md +1 -1
- package/skills/j-proceed/SKILL.md +3 -4
- package/skills/j-publish/SKILL.md +4 -5
- package/skills/j-publish/adapters/npm-ci.md +6 -1
- package/skills/j-publish/adapters/npm.md +1 -1
- package/skills/j-publish/scripts/generate_release_notes.sh +1 -1
- package/skills/j-publish/scripts/npm_stage_inspect.sh +61 -0
- package/skills/j-publish/scripts/run_gates.sh +1 -1
- package/skills/j-reconcile/SKILL.md +40 -7
- package/skills/j-reconcile/assets/report_format.md +11 -0
- package/skills/j-reconcile/scripts/detect-unlinked-code.sh +13 -13
- package/skills/j-reconcile-origin/SKILL.md +3 -4
- package/skills/j-redo/SKILL.md +4 -5
- package/skills/j-skillify/SKILL.md +3 -4
- package/skills/j-spinoff/SKILL.md +4 -5
- package/skills/j-status/SKILL.md +18 -4
- package/skills/j-todo/SKILL.md +44 -5
- package/skills/j-todo/scripts/argument-is-not-ranked-list.sh +92 -0
- package/skills/j-todo/scripts/argument-is-ranked-list.sh +78 -0
- package/skills/j-uncharted/SKILL.md +252 -13
- package/skills/j-uncharted/scripts/detect-dependencies.sh +80 -22
- package/skills/j-uncharted/scripts/detect-tests.sh +1 -1
- package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
- package/skills/j-uncharted/scripts/elicitation-state.sh +1 -1
- package/skills/j-uncharted/scripts/find-scan-baseline.sh +545 -0
- package/skills/j-uncharted/scripts/run-engine.sh +36 -2
- package/skills/j-uncharted/scripts/validate-proposed-items.sh +1 -1
- package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
- package/skills/j-wtf/SKILL.md +4 -5
- package/skills/jenga/SKILL.md +106 -11
- package/skills/jenga/playbooks/board-hygiene.json +32 -0
- package/skills/jenga/playbooks/schema.json +73 -6
- package/skills/jenga/playbooks/understand-then-commit.json +19 -0
- package/skills/jenga/scripts/load-nl-catalog.js +5 -2
- package/skills/jenga/scripts/load-nl-catalog.sh +1 -1
- package/skills/jenga/scripts/load-playbooks.sh +290 -5
- package/skills/jenga/scripts/match-playbook.sh +4 -4
- package/skills/jenga/scripts/run-playbook-step.sh +267 -1
- package/templates/SCRUM_BOARD_SCHEMA.md +14 -1
- package/templates/SKILL_TEMPLATE.md +12 -0
- package/templates/permission-levels/level-1-locked.json +1 -1
- package/templates/permission-levels/level-2-guarded.json +1 -1
- package/templates/permission-levels/level-3-standard.json +1 -1
- package/templates/permission-levels/level-4-elevated.json +2 -2
- package/templates/permission-levels/level-5-unrestricted.json +2 -2
- package/templates/playbook-types.json +40 -6
- package/project/app/ui/dist/assets/index-7fj-vllY.js +0 -104
- package/project/app/ui/dist/assets/index-CdK3Qrep.css +0 -1
- package/scripts/audit-twin-divergence.sh +0 -625
- package/scripts/generate-j-alias.sh +0 -333
- package/skills/j-dev-done/SKILL.md +0 -53
- package/skills/j-dev-done/scripts/classify-commit-outcome.sh +0 -114
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# write-scan-record.sh — write a COMMITTED baseline scan-record after a successful onboard/refresh run
|
|
3
|
+
#
|
|
4
|
+
# Usage: write-scan-record.sh [options] [<report.json>]
|
|
5
|
+
# discover-subsystems.sh <root> | write-scan-record.sh [options]
|
|
6
|
+
# write-scan-record.sh --help
|
|
7
|
+
#
|
|
8
|
+
# `onboard` mode (and, once E40_S07_T02/T03 land, `refresh` mode) needs a durable, DISKED record
|
|
9
|
+
# of what a scan actually found, so a later run can tell what changed since the last one. This
|
|
10
|
+
# script is that record's writer, and only that — baseline DISCOVERY (which scan-record is the
|
|
11
|
+
# authoritative one to diff against) and DIFF CLASSIFICATION (unchanged/changed/new/removed) are
|
|
12
|
+
# separate, not-yet-implemented scripts (E40_S07_T02, E40_S07_T03). This script only writes.
|
|
13
|
+
#
|
|
14
|
+
# This is deliberately NOT elicitation-state.sh's persistence mechanism. elicitation-state.sh's
|
|
15
|
+
# state file is explicit, transient SESSION SCRATCH — git-ignored, meant to survive a pause within
|
|
16
|
+
# one elicitation, not to persist meaning between separate onboard/refresh runs weeks or months
|
|
17
|
+
# apart. This record is the opposite: COMMITTED to the repository (never git-ignored), and is the
|
|
18
|
+
# only thing that gives a future `refresh` run something durable to compare against.
|
|
19
|
+
#
|
|
20
|
+
# This script performs NO discovery of its own. It consumes discover-subsystems.sh's ranked
|
|
21
|
+
# output (the same report apply-subsystem-cap.sh consumes) and contributes only the record write.
|
|
22
|
+
# It is read-only against the analysed codebase — its only write is the scan-record file itself.
|
|
23
|
+
#
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# INPUT
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# discover-subsystems.sh's JSON report, from a file argument or from stdin (`-`, or no argument) —
|
|
28
|
+
# the same input contract apply-subsystem-cap.sh uses, so both scripts can consume the identical
|
|
29
|
+
# discover-subsystems.sh invocation in one onboard run. The fields read are the report's `root`
|
|
30
|
+
# (repo-relative analysed root) plus each `candidates[]` entry's `path`, `files`, and `lines` —
|
|
31
|
+
# real fields discover-subsystems.sh actually emits (see that script's own OUTPUT CONTRACT
|
|
32
|
+
# comment), nothing invented. Everything else in the input report is ignored.
|
|
33
|
+
#
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# OPTIONS
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# --out-dir DIR Where the scan-record is written. Default <repo-root>/project/rapports/analysis
|
|
38
|
+
# — the same directory the Understanding Document convention uses, kept
|
|
39
|
+
# distinguishable from it by filename suffix (see OUTPUT below), never by a
|
|
40
|
+
# separate location.
|
|
41
|
+
# --json-out FILE Also write the same JSON to this exact path (in addition to the timestamped
|
|
42
|
+
# file under --out-dir). Not written unless requested.
|
|
43
|
+
# --label TEXT Human label for the analysed codebase, used only in the output filename's
|
|
44
|
+
# slug. Default: the report's own `root` (or its basename when `root` is "."
|
|
45
|
+
# or empty, the same fallback apply-subsystem-cap.sh uses).
|
|
46
|
+
# -h, --help Show this help and exit 0.
|
|
47
|
+
#
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# OUTPUT
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# stdout : the absolute path of the written scan-record file. One line, nothing else — so
|
|
52
|
+
# `PATH=$(write-scan-record.sh ...)` is safe, matching run-engine.sh's own contract.
|
|
53
|
+
# stderr : notices and diagnostics only, never document content.
|
|
54
|
+
#
|
|
55
|
+
# Filename: uncharted-scan-record-<slug>-<YYYYMMDDTHHMMSSZ>.scan-record.json, under
|
|
56
|
+
# project/rapports/analysis/ — the SAME directory the Understanding Document uses, but never the
|
|
57
|
+
# same file: the `.scan-record.json` suffix (as opposed to a document's `.md`) is what keeps a run
|
|
58
|
+
# that produces both from overloading one artifact with two purposes. A name collision gets a
|
|
59
|
+
# -2, -3... suffix, the same convention run-engine.sh uses, so repeated runs leave a diffable
|
|
60
|
+
# history rather than clobbering each other. This script always writes a FRESH, timestamped file
|
|
61
|
+
# per run rather than mutating a prior one in place — baseline DISCOVERY (deciding which prior
|
|
62
|
+
# scan-record is the one to diff against) is a separate, later concern (E40_S07_T02); this script's
|
|
63
|
+
# only job is to make sure a new one always exists to be found.
|
|
64
|
+
#
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# RECORD SHAPE (the written JSON, and what --json-out duplicates)
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# {
|
|
69
|
+
# "script": "write-scan-record.sh",
|
|
70
|
+
# "version": 1,
|
|
71
|
+
# "baseline_commit": "<short HEAD SHA>" | null, // null only when the repo has no commits yet
|
|
72
|
+
# "scanned_at": "<ISO 8601 UTC timestamp>",
|
|
73
|
+
# "root": "<repo-relative analysed root, from the input report>",
|
|
74
|
+
# "candidates": [
|
|
75
|
+
# { "path": "<path relative to root>", "files": <int>, "lines": <int> }, ...
|
|
76
|
+
# ],
|
|
77
|
+
# "candidate_count": <int>,
|
|
78
|
+
# "notices": [ "<non-fatal diagnostic>", ... ]
|
|
79
|
+
# }
|
|
80
|
+
#
|
|
81
|
+
# `baseline_commit` is `git -C <repo-root> rev-parse --short HEAD` at scan time — the repo that
|
|
82
|
+
# OWNS the engine (this repo), matching every sibling script's REPO_ROOT anchor, not the analysed
|
|
83
|
+
# root (which `import`-staged or out-of-repo targets may not even share a git history with).
|
|
84
|
+
#
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
# EXIT CODES
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# 0 — success; scan-record written, its path printed on stdout
|
|
89
|
+
# 1 — usage error: unknown flag, missing value, more than one positional input
|
|
90
|
+
# 2 — input error: report missing/unreadable/unparseable JSON, or not a discover-subsystems.sh
|
|
91
|
+
# report (no "candidates" array)
|
|
92
|
+
# 4 — write failure: --out-dir (or --json-out's directory) could not be created or is not
|
|
93
|
+
# writable. Deliberately fatal: a "successful" run that silently failed to leave a durable
|
|
94
|
+
# record would defeat the entire point of this script, the same reasoning
|
|
95
|
+
# apply-subsystem-cap.sh applies to its own rapport write.
|
|
96
|
+
#
|
|
97
|
+
# Examples:
|
|
98
|
+
# discover-subsystems.sh . | write-scan-record.sh
|
|
99
|
+
# discover-subsystems.sh . > s.json && write-scan-record.sh --label "my-app" s.json
|
|
100
|
+
# write-scan-record.sh --json-out /tmp/latest-scan-record.json s.json
|
|
101
|
+
#
|
|
102
|
+
# Requires: bash, git, python3. jq is NOT required — JSON is parsed and emitted by python3.
|
|
103
|
+
|
|
104
|
+
set -euo pipefail
|
|
105
|
+
|
|
106
|
+
OUT_DIR=""
|
|
107
|
+
JSON_OUT=""
|
|
108
|
+
LABEL=""
|
|
109
|
+
INPUT=""
|
|
110
|
+
|
|
111
|
+
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
|
112
|
+
|
|
113
|
+
usage() {
|
|
114
|
+
cat <<EOF
|
|
115
|
+
Usage: $(basename "$0") [options] [<report.json>]
|
|
116
|
+
|
|
117
|
+
Write a COMMITTED baseline scan-record from discover-subsystems.sh output: baseline_commit
|
|
118
|
+
(repo HEAD short SHA), scanned_at (ISO 8601 UTC), root, and one candidates[] entry per subsystem
|
|
119
|
+
(path, files, lines). Unlike elicitation-state.sh's transient session scratch, this record is
|
|
120
|
+
never git-ignored and is meant to survive indefinitely between onboard/refresh runs.
|
|
121
|
+
|
|
122
|
+
Arguments:
|
|
123
|
+
<report.json> discover-subsystems.sh output. Omit, or pass "-", to read stdin.
|
|
124
|
+
|
|
125
|
+
Options:
|
|
126
|
+
--out-dir DIR Directory for the scan-record (default: <repo-root>/project/rapports/analysis)
|
|
127
|
+
--json-out FILE Also write the same JSON to this exact path
|
|
128
|
+
--label TEXT Human label used only in the output filename's slug (default: the report's root)
|
|
129
|
+
-h, --help Show this help and exit
|
|
130
|
+
|
|
131
|
+
Exit codes: 0 success, 1 usage error, 2 input error, 4 write failure.
|
|
132
|
+
EOF
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
die_usage() {
|
|
136
|
+
echo "Error: $1" >&2
|
|
137
|
+
echo >&2
|
|
138
|
+
usage >&2
|
|
139
|
+
exit 1
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
require_value() {
|
|
143
|
+
# require_value <flag> <remaining-arg-count>
|
|
144
|
+
[ "$2" -ge 2 ] || die_usage "$1 requires a value"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
# Argument parsing
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
while [ "$#" -gt 0 ]; do
|
|
152
|
+
case "$1" in
|
|
153
|
+
--out-dir) require_value "--out-dir" "$#"; OUT_DIR="$2"; shift 2 ;;
|
|
154
|
+
--out-dir=*) OUT_DIR="${1#*=}"; shift ;;
|
|
155
|
+
--json-out) require_value "--json-out" "$#"; JSON_OUT="$2"; shift 2 ;;
|
|
156
|
+
--json-out=*) JSON_OUT="${1#*=}"; shift ;;
|
|
157
|
+
--label) require_value "--label" "$#"; LABEL="$2"; shift 2 ;;
|
|
158
|
+
--label=*) LABEL="${1#*=}"; shift ;;
|
|
159
|
+
-h|--help) usage; exit 0 ;;
|
|
160
|
+
--)
|
|
161
|
+
shift
|
|
162
|
+
[ "$#" -le 1 ] || die_usage "at most one input report is accepted"
|
|
163
|
+
[ "$#" -eq 0 ] || INPUT="$1"
|
|
164
|
+
break ;;
|
|
165
|
+
-)
|
|
166
|
+
INPUT="-"; shift ;;
|
|
167
|
+
-*)
|
|
168
|
+
die_usage "unknown option \"$1\"" ;;
|
|
169
|
+
*)
|
|
170
|
+
[ -z "$INPUT" ] || die_usage "at most one input report is accepted (got \"$INPUT\" and \"$1\")"
|
|
171
|
+
INPUT="$1"; shift ;;
|
|
172
|
+
esac
|
|
173
|
+
done
|
|
174
|
+
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
# Input resolution
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
if [ -z "$INPUT" ] || [ "$INPUT" = "-" ]; then
|
|
180
|
+
INPUT="-"
|
|
181
|
+
else
|
|
182
|
+
if [ ! -e "$INPUT" ]; then
|
|
183
|
+
echo "Error: input report does not exist: $INPUT" >&2
|
|
184
|
+
echo " Expected discover-subsystems.sh JSON output." >&2
|
|
185
|
+
exit 2
|
|
186
|
+
fi
|
|
187
|
+
if [ ! -f "$INPUT" ] || [ ! -r "$INPUT" ]; then
|
|
188
|
+
echo "Error: input report is not a readable file: $INPUT" >&2
|
|
189
|
+
exit 2
|
|
190
|
+
fi
|
|
191
|
+
INPUT=$(cd -- "$(dirname -- "$INPUT")" && pwd -P)/$(basename -- "$INPUT")
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
# Output destinations — pre-flighted BEFORE any work, same fail-fast contract as
|
|
196
|
+
# run-engine.sh / apply-subsystem-cap.sh: an unwritable destination must surface with nothing
|
|
197
|
+
# done, rather than after the record has already been computed.
|
|
198
|
+
#
|
|
199
|
+
# Anchored on THIS SCRIPT, not on the analysed root: the record belongs to the project that owns
|
|
200
|
+
# the engine, even when the analysed root lives elsewhere (import staging areas, out-of-repo
|
|
201
|
+
# targets).
|
|
202
|
+
# ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
REPO_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)
|
|
205
|
+
[ -n "$REPO_ROOT" ] || REPO_ROOT="$(pwd -P)"
|
|
206
|
+
|
|
207
|
+
[ -n "$OUT_DIR" ] || OUT_DIR="$REPO_ROOT/project/rapports/analysis"
|
|
208
|
+
mkdir -p "$OUT_DIR" 2>/dev/null || {
|
|
209
|
+
echo "Error: could not create output directory: $OUT_DIR" >&2
|
|
210
|
+
exit 4
|
|
211
|
+
}
|
|
212
|
+
[ -w "$OUT_DIR" ] || { echo "Error: output directory is not writable: $OUT_DIR" >&2; exit 4; }
|
|
213
|
+
OUT_DIR=$(cd -- "$OUT_DIR" && pwd -P)
|
|
214
|
+
|
|
215
|
+
if [ -n "$JSON_OUT" ]; then
|
|
216
|
+
JSON_OUT_DIR=$(dirname -- "$JSON_OUT")
|
|
217
|
+
[ -d "$JSON_OUT_DIR" ] || { echo "Error: --json-out directory does not exist: $JSON_OUT_DIR" >&2; exit 4; }
|
|
218
|
+
[ -w "$JSON_OUT_DIR" ] || { echo "Error: --json-out directory is not writable: $JSON_OUT_DIR" >&2; exit 4; }
|
|
219
|
+
JSON_OUT=$(cd -- "$JSON_OUT_DIR" && pwd -P)/$(basename -- "$JSON_OUT")
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
# baseline_commit is the ENGINE REPO's HEAD, not the analysed root's — see header. null (not a
|
|
223
|
+
# usage error) when this repo has no commits yet; a notice records why.
|
|
224
|
+
BASELINE_COMMIT=$(git -C "$REPO_ROOT" rev-parse --short HEAD 2>/dev/null || true)
|
|
225
|
+
SCANNED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
226
|
+
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# Read the discoverer's report, build the record, write it
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
# Captured into a variable and run with `python3 -c`, exactly as run-engine.sh and
|
|
232
|
+
# apply-subsystem-cap.sh do: a `python3 - <<PY` heredoc would occupy python's stdin, and this
|
|
233
|
+
# script must itself be able to read stdin (discover-subsystems.sh . | write-scan-record.sh).
|
|
234
|
+
|
|
235
|
+
PY_SRC=$(cat <<'PY'
|
|
236
|
+
import json
|
|
237
|
+
import os
|
|
238
|
+
import re
|
|
239
|
+
import sys
|
|
240
|
+
|
|
241
|
+
(INPUT, OUT_DIR, JSON_OUT, LABEL, BASELINE_COMMIT, SCANNED_AT, STAMP) = sys.argv[1:8]
|
|
242
|
+
|
|
243
|
+
notices = []
|
|
244
|
+
|
|
245
|
+
# --- read the discoverer's report ---------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
if INPUT == "-":
|
|
249
|
+
raw = sys.stdin.read()
|
|
250
|
+
origin = "stdin"
|
|
251
|
+
else:
|
|
252
|
+
with open(INPUT, "r", encoding="utf-8") as fh:
|
|
253
|
+
raw = fh.read()
|
|
254
|
+
origin = INPUT
|
|
255
|
+
except OSError as exc:
|
|
256
|
+
sys.stderr.write("Error: could not read input report: %s\n" % exc)
|
|
257
|
+
sys.exit(2)
|
|
258
|
+
|
|
259
|
+
if not raw.strip():
|
|
260
|
+
sys.stderr.write("Error: input report is empty (%s).\n" % origin)
|
|
261
|
+
sys.stderr.write(" Expected discover-subsystems.sh JSON output.\n")
|
|
262
|
+
sys.exit(2)
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
report = json.loads(raw)
|
|
266
|
+
except ValueError as exc:
|
|
267
|
+
sys.stderr.write("Error: input report is not valid JSON (%s): %s\n" % (origin, exc))
|
|
268
|
+
sys.stderr.write(" Expected discover-subsystems.sh JSON output.\n")
|
|
269
|
+
sys.exit(2)
|
|
270
|
+
|
|
271
|
+
if not isinstance(report, dict) or not isinstance(report.get("candidates"), list):
|
|
272
|
+
sys.stderr.write("Error: input report has no \"candidates\" array (%s).\n" % origin)
|
|
273
|
+
sys.stderr.write(" This does not look like discover-subsystems.sh output.\n")
|
|
274
|
+
sys.exit(2)
|
|
275
|
+
|
|
276
|
+
produced_by = report.get("script")
|
|
277
|
+
if produced_by and produced_by != "discover-subsystems.sh":
|
|
278
|
+
notices.append(
|
|
279
|
+
"Input reports itself as \"%s\" rather than discover-subsystems.sh; proceeding on the "
|
|
280
|
+
"strength of its candidates array." % produced_by)
|
|
281
|
+
|
|
282
|
+
root = report.get("root") or report.get("root_absolute") or "(unknown root)"
|
|
283
|
+
root_absolute = report.get("root_absolute") or ""
|
|
284
|
+
# "." is what the discoverer reports when the analysed root IS the repo root -- a useless label
|
|
285
|
+
# for a filename slug. Fall back to the directory's real name, same convention
|
|
286
|
+
# apply-subsystem-cap.sh uses for its rapport label.
|
|
287
|
+
if LABEL:
|
|
288
|
+
label = LABEL
|
|
289
|
+
elif root not in (".", "", "./"):
|
|
290
|
+
label = root
|
|
291
|
+
else:
|
|
292
|
+
label = os.path.basename(root_absolute.rstrip("/")) or root
|
|
293
|
+
|
|
294
|
+
if not BASELINE_COMMIT:
|
|
295
|
+
notices.append(
|
|
296
|
+
"Could not resolve a HEAD commit for this repository (no commits yet, or not a git work "
|
|
297
|
+
"tree); \"baseline_commit\" was recorded as null."
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# --- candidates[] — only real fields discover-subsystems.sh actually emits ----------------------
|
|
301
|
+
|
|
302
|
+
candidates = []
|
|
303
|
+
for c in report.get("candidates", []):
|
|
304
|
+
if not isinstance(c, dict):
|
|
305
|
+
continue
|
|
306
|
+
candidates.append({
|
|
307
|
+
"path": c.get("path"),
|
|
308
|
+
"files": c.get("files", 0),
|
|
309
|
+
"lines": c.get("lines", 0),
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
record = {
|
|
313
|
+
"script": "write-scan-record.sh",
|
|
314
|
+
"version": 1,
|
|
315
|
+
"baseline_commit": BASELINE_COMMIT or None,
|
|
316
|
+
"scanned_at": SCANNED_AT,
|
|
317
|
+
"root": root,
|
|
318
|
+
"candidates": candidates,
|
|
319
|
+
"candidate_count": len(candidates),
|
|
320
|
+
"notices": notices,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
# --- filename: slugged label + timestamp, collision-safe --------------------------------------
|
|
324
|
+
|
|
325
|
+
slug = re.sub(r"[^a-z0-9]+", "-", label.lower()).strip("-")[:40]
|
|
326
|
+
if not slug:
|
|
327
|
+
slug = "target"
|
|
328
|
+
|
|
329
|
+
out_file = os.path.join(OUT_DIR, "uncharted-scan-record-%s-%s.scan-record.json" % (slug, STAMP))
|
|
330
|
+
n = 2
|
|
331
|
+
while os.path.exists(out_file):
|
|
332
|
+
out_file = os.path.join(
|
|
333
|
+
OUT_DIR, "uncharted-scan-record-%s-%s-%d.scan-record.json" % (slug, STAMP, n))
|
|
334
|
+
n += 1
|
|
335
|
+
|
|
336
|
+
body = json.dumps(record, indent=2, ensure_ascii=False) + "\n"
|
|
337
|
+
|
|
338
|
+
try:
|
|
339
|
+
with open(out_file, "w", encoding="utf-8") as fh:
|
|
340
|
+
fh.write(body)
|
|
341
|
+
except OSError as exc:
|
|
342
|
+
sys.stderr.write("Error: could not write scan-record: %s\n" % exc)
|
|
343
|
+
sys.exit(4)
|
|
344
|
+
|
|
345
|
+
if JSON_OUT:
|
|
346
|
+
try:
|
|
347
|
+
with open(JSON_OUT, "w", encoding="utf-8") as fh:
|
|
348
|
+
fh.write(body)
|
|
349
|
+
except OSError as exc:
|
|
350
|
+
sys.stderr.write("Error: could not write --json-out file: %s\n" % exc)
|
|
351
|
+
sys.exit(4)
|
|
352
|
+
|
|
353
|
+
for n_ in notices:
|
|
354
|
+
sys.stderr.write("Notice: %s\n" % n_)
|
|
355
|
+
|
|
356
|
+
print(out_file)
|
|
357
|
+
PY
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
python3 -c "$PY_SRC" \
|
|
361
|
+
"$INPUT" "$OUT_DIR" "$JSON_OUT" "$LABEL" "$BASELINE_COMMIT" "$SCANNED_AT" "$STAMP"
|
package/skills/j-wtf/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: j.wtf
|
|
3
|
-
description:
|
|
3
|
+
description: Alias of /clearify — clarifies ambiguous, dense, or under-specified prompts and conversation on request. This folder exists only so the `/wtf` slash command resolves to a skill; behaviour is identical to `/clearify`.
|
|
4
4
|
keywords:
|
|
5
5
|
- wtf
|
|
6
6
|
- confused
|
|
@@ -8,7 +8,6 @@ keywords:
|
|
|
8
8
|
- what does this mean
|
|
9
9
|
- I'm lost
|
|
10
10
|
- j-wtf
|
|
11
|
-
- polyfill
|
|
12
11
|
examples:
|
|
13
12
|
- "wtf"
|
|
14
13
|
- "wtf does this mean"
|
|
@@ -18,10 +17,10 @@ examples:
|
|
|
18
17
|
|
|
19
18
|
# WTF — Alias of /clearify
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
`skills/j-wtf/` is the **canonical, hand-edited** directory for this skill, per CLAUDE.md's "The Canonical Naming Contract" (the `E50` reopening of 2026-09-09, which promoted `skills/j-wtf/` from generated twin to sole canonical form). The `j-` prefix is there for collision safety — a real directory under a distinct name, so a host tool shipping its own same-named built-in command cannot shadow it (Claude Code's native skill resolution is a literal-string, directory-name-based match; see `docs/skill-authoring.md`'s "Invocation Convention").
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
> ⚠️ **`scripts/generate-j-alias.sh` was retired by `E50_S14` and no longer exists — there is nothing to run.** This file was previously generated from a bare `skills/wtf/SKILL.md` source; `E50_S15` deleted that directory. This file is now the sole canonical, hand-edited source for this skill — edit it directly.
|
|
24
23
|
|
|
25
24
|
## Instructions
|
|
26
25
|
|
|
27
|
-
`/wtf` is an alias of `/clearify`. Follow `skills/clearify/SKILL.md` in full — do not duplicate or reimplement its ambiguity-detection logic here. Read that file's `## Instructions` section and execute it exactly as written, using whatever prompt or conversation context is attached to this `/wtf` invocation.
|
|
26
|
+
`/wtf` is an alias of `/clearify`. Follow `skills/j-clearify/SKILL.md` in full — do not duplicate or reimplement its ambiguity-detection logic here. Read that file's `## Instructions` section and execute it exactly as written, using whatever prompt or conversation context is attached to this `/wtf` invocation.
|
package/skills/jenga/SKILL.md
CHANGED
|
@@ -169,11 +169,11 @@ This branch is entered when `detect-nl-intent.sh` (invoked above) classifies the
|
|
|
169
169
|
This branch is entered when `detect-nl-intent.sh` classifies the argument as `nl_intent` — every comma-delimited segment failed the ID grammar, so the raw argument is treated as natural-language intent rather than a malformed ID list. This is purely a new *outcome* of the same argument-shape detection above — no new sigil, trigger prefix, or separate entry point is introduced.
|
|
170
170
|
|
|
171
171
|
1. **Load the catalog** — invoke `skills/jenga/scripts/load-nl-catalog.sh` with no arguments (E53_S01_T02). Its stdout is the full skill catalog (`name`/`description`/`keywords`/`examples`/`prefered_agent` per skill), sourced exclusively from `lib/generate-skill-allow-list.js`'s generated inventory — see the script's own header for the full contract. Never re-derive this catalog by re-scanning `skills/` inline.
|
|
172
|
-
2. **Match** — run
|
|
173
|
-
3. **Confident single match** — report the routing decision using
|
|
174
|
-
4. **No match, or an ambiguous multi-way tie (single-skill match)** — before surfacing
|
|
172
|
+
2. **Match** — run this section's own **Skill Matching & Invocation Contract** (below) — the three-pass keyword → example-similarity → description match, including its tie-break and no-match handling — against this catalog, treating `detect-nl-intent.sh`'s `raw_argument` field as the prompt.
|
|
173
|
+
3. **Confident single match** — report the routing decision using the **Skill Matching & Invocation Contract**'s **Report** format, then invoke the matched skill exactly as its **Invoke** rule already does: load `agents/<prefered_agent>.md` when the matched skill specifies `metadata.prefered_agent`, otherwise execute the skill instructions directly. The matched skill's own execution takes over from here — do not continue into this `/jenga` invocation's Phase 1.
|
|
174
|
+
4. **No match, or an ambiguous multi-way tie (single-skill match)** — before surfacing the **Skill Matching & Invocation Contract**'s generic disambiguation options, attempt a **playbook fallback** (E53_S02): invoke `skills/jenga/scripts/match-playbook.sh "<raw_argument>"`. This step only ever runs when step 3 above did NOT already commit to a confident single-skill match — a confident single-skill match always wins outright and this playbook fallback is never even invoked in that case. Branch on `match-playbook.sh`'s `classification` field:
|
|
175
175
|
- `playbook_match` → continue to **step 5 (Playbook proposal and execution)** below.
|
|
176
|
-
- `ambiguous` or `no_match` → continue to **step 6 (Fall through to
|
|
176
|
+
- `ambiguous` or `no_match` → continue to **step 6 (Fall through to the Skill Matching & Invocation Contract's disambiguation)** below — the exact behavior this branch already had before E53_S02, unchanged.
|
|
177
177
|
5. **Playbook proposal and execution** — entered only on a `playbook_match` result from step 4. A proposed playbook is an ordered chain of skills (e.g. the canonical `brainstorm -> j.todo -> j.do -> j.dev-done -> j.mirror-public` chain defined in `skills/jenga/playbooks/brainstorm-to-mirror.json`) that must be confirmed, editable, and confirmable per `CLAUDE.md`'s Interaction Pattern before any step executes — the same confirm-before-execute posture `/jenga` already applies to the bare/scoped branches via `render-confirmation.sh`.
|
|
178
178
|
a. **Resolve conditional metadata** (`E53_S04_T02`/`T04`) — before rendering, inspect the
|
|
179
179
|
matched playbook's own `steps` array (as returned by `load-playbooks.sh`'s catalog, not the
|
|
@@ -208,12 +208,73 @@ This branch is entered when `detect-nl-intent.sh` classifies the argument as `nl
|
|
|
208
208
|
- **Apply the transform** — when both `forward_from` (successfully resolved immediately above) and `resolve` are present, use your own LLM judgment to reshape/filter/type-bridge the forwarded value per `resolve`'s natural-language instructions (e.g. "pick the first three items", "convert this file_list to a text summary"). The transformed value — never the raw forwarded value — becomes this step's actual invocation input.
|
|
209
209
|
- **Hard-fail, never silent pass-through** — if the transform cannot cleanly produce a usable, type-compatible result (the instructions don't plausibly apply to the actual value, the value is empty/malformed for what's being asked, or the result would not plausibly satisfy the target step's expected input shape), do **not** invoke this step and do **not** guess or pass through a differently-shaped value. Instead call `skills/jenga/scripts/run-playbook-step.sh advance <state_file> failed "<note>"`, where `<note>` follows the format `resolve failed on step '<step name>': could not apply "<resolve text>" to raw value <raw pre-transform value> — <short reason>` (the raw pre-transform value is always included, for debugging). Then follow the `halted` handling in 5e-vi below exactly as any other step failure — immediately stop executing further steps, report `failed_step`/`failed_note`/`completed`/`skipped`/`never_run` verbatim.
|
|
210
210
|
|
|
211
|
-
Then invoke the step exactly as
|
|
211
|
+
Then invoke the step exactly as this section's **Skill Matching & Invocation Contract**'s **Invoke** rule already does for a single matched skill: load `agents/<prefered_agent>.md` when that step's own `SKILL.md` specifies `metadata.prefered_agent`, otherwise execute its instructions directly.
|
|
212
212
|
iii. After a normally-invoked step's execution concludes, call `skills/jenga/scripts/run-playbook-step.sh advance <state_file> passed ["<typed-output-value>"]` (the step completed successfully — supply the step's declared typed output, per its `output_types`, if it produced one) or `... advance <state_file> failed "<short failure note>"` (the step failed).
|
|
213
213
|
iv. On a `step_ready` result, repeat step 5e for the newly-named step.
|
|
214
214
|
v. On a `complete` result, report the full lists of `completed` AND `skipped` steps to the user and stop — the playbook run is finished; do not continue into this `/jenga` invocation's Phase 1.
|
|
215
215
|
vi. On a `halted` result, **immediately stop executing any further steps** — no silent skip-ahead. Report `failed_step`, `failed_note`, `completed`, `skipped` (steps that already finished or were skipped), and `never_run` (steps that never got a chance to run) to the user verbatim from the halt report. Do not continue into this `/jenga` invocation's Phase 1.
|
|
216
|
-
6. **Fall through to
|
|
216
|
+
6. **Fall through to the Skill Matching & Invocation Contract's disambiguation** — entered when step 4 found no playbook match (`ambiguous` or `no_match`). Surface the same disambiguation options this section's **Skill Matching & Invocation Contract** already defines for these cases (browse `/help`, create a new skill via `/btw`, or proceed with the raw prompt). Halt this `/jenga` invocation once the user picks an option; none of Phase 0.75's remaining steps or Phases 1-4 run for this branch.
|
|
217
|
+
|
|
218
|
+
##### Skill Matching & Invocation Contract
|
|
219
|
+
|
|
220
|
+
This contract is inlined here — rather than referenced by path to `skills/j-route/SKILL.md` — because
|
|
221
|
+
`j.route` is one of six skills that never ship publicly in either naming form (see
|
|
222
|
+
`docs/public-mirror-content-parity.md`'s "Which skills are never public"), while `/jenga` itself ships
|
|
223
|
+
publicly. A path reference from a shipped file to an unshipped one is a dead reference in the public
|
|
224
|
+
package even though it resolves fine in this private repo (filed via `j.error` 2026-09-26).
|
|
225
|
+
`skills/j-route/SKILL.md`'s own Step 2/6/7 carry the authoritative copy of this same contract for
|
|
226
|
+
`/route`'s own use — the two are intentionally duplicated for public-mirror reasons; keep them in sync
|
|
227
|
+
by hand if either changes.
|
|
228
|
+
|
|
229
|
+
**Matching** (three passes, stop at first confident match):
|
|
230
|
+
|
|
231
|
+
- *Pass 1 — Keyword Match* — check whether any phrase from a skill's `keywords` list appears verbatim
|
|
232
|
+
(case-insensitive) in the prompt.
|
|
233
|
+
- *Pass 2 — Example Similarity* — compare the prompt against each skill's `examples` list as a
|
|
234
|
+
semantic similarity check; pick the skill whose examples most closely reflect the intent of the
|
|
235
|
+
prompt.
|
|
236
|
+
- *Pass 3 — Description Match* — if no clear winner has emerged, compare the prompt against each
|
|
237
|
+
skill's `description` field; pick the skill whose description best captures what the user is trying
|
|
238
|
+
to do.
|
|
239
|
+
|
|
240
|
+
**Tie-break** (two or more skills score equally) — present the top candidates and ask the user to
|
|
241
|
+
choose:
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
More than one skill matches your prompt. Which should I apply?
|
|
245
|
+
1. /<skill-a> — <one-line description>
|
|
246
|
+
2. /<skill-b> — <one-line description>
|
|
247
|
+
3. Neither — describe what you need
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**No match** — inform the user and offer:
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
No matching skill found for: "<prompt>"
|
|
254
|
+
Would you like to:
|
|
255
|
+
1. Browse all available skills (/help)
|
|
256
|
+
2. Create a new skill for this use case (/btw)
|
|
257
|
+
3. Proceed without a skill (raw prompt)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Invoke** — deliver to the appropriate agent: if the matched skill specifies
|
|
261
|
+
`metadata.prefered_agent`, load that agent's definition from `agents/<prefered_agent>.md` and pass it
|
|
262
|
+
the full context; otherwise execute the skill's instructions directly. Do not summarise or restate the
|
|
263
|
+
matched skill's instructions — deliver them as-is.
|
|
264
|
+
|
|
265
|
+
**Report** — before invoking, briefly inform the user:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
Routing to: /<matched-skill-name>
|
|
269
|
+
Reason: <one sentence explaining why this skill was chosen>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
(`skills/j-route/SKILL.md`'s own Step 7 additionally reports `Board items found`/`Docs found` counts
|
|
273
|
+
from its Steps 3-5 board/doc enrichment — this `/jenga` natural-language branch never performs that
|
|
274
|
+
enrichment, so those two fields do not apply here and are intentionally omitted.)
|
|
275
|
+
|
|
276
|
+
Then proceed immediately — do not wait for user confirmation unless the match was ambiguous (the
|
|
277
|
+
tie-break above already handled that).
|
|
217
278
|
|
|
218
279
|
#### Shared confirmation step (bare and scoped branches only)
|
|
219
280
|
|
|
@@ -228,6 +289,39 @@ After this phase completes (bare and scoped branches via confirmation, wildcard
|
|
|
228
289
|
|
|
229
290
|
---
|
|
230
291
|
|
|
292
|
+
### Phase 0.9 — Generate the Run's Shared `orchestrator_session_id`
|
|
293
|
+
|
|
294
|
+
This phase runs for every entry mode that reaches Phase 1 at all — bare, scoped, and wildcard (`*`) —
|
|
295
|
+
immediately after Phase 0.75 completes and before Phase 1 begins. It does not run for the
|
|
296
|
+
natural-language branch's playbook/single-skill-match outcomes (E53_S01/E53_S02), since those hand off
|
|
297
|
+
entirely to another skill or playbook and never reach Phase 1-4 of this document.
|
|
298
|
+
|
|
299
|
+
Generate exactly **one** `orchestrator_session_id` for this entire `/jenga` run, here and only here.
|
|
300
|
+
Both Phase 3.5's bundle dispatch and Phase 4's first (and every later) wave dispatch need this same
|
|
301
|
+
value, which is why it is generated once, early, before either call site runs.
|
|
302
|
+
|
|
303
|
+
**Format.** Generate it as `jenga-<UTC ISO 8601 basic-format timestamp>` (e.g.
|
|
304
|
+
`jenga-20260917T162349Z`) — a short prefix naming the orchestrating skill, plus a UTC timestamp, with no
|
|
305
|
+
`/` or `..` characters. This follows the same session-id string convention this repo already uses for
|
|
306
|
+
other orchestrator-minted session ids (e.g. `/do`'s own dispatch sessions), and stays compatible with
|
|
307
|
+
`scripts/acquire-concurrency-slot.sh`'s own validation, which refuses a `<session_id>` argument
|
|
308
|
+
containing a path separator or traversal sequence, because the value is used verbatim in filenames such
|
|
309
|
+
as `project/queue/concurrency-slots-<session_id>.json`.
|
|
310
|
+
|
|
311
|
+
**Generate once, reuse everywhere in this run — never regenerate.** This value MUST be reused,
|
|
312
|
+
byte-for-byte unchanged, across:
|
|
313
|
+
- Phase 3.5 step 7b's bundle `/do <E##_S##>` call.
|
|
314
|
+
- Every wave dispatched by Phase 4 step 3, across every loop-back at Phase 4 step 5b. The loop-back
|
|
315
|
+
never mints a new value — it only ever references the one value generated here.
|
|
316
|
+
|
|
317
|
+
This is what allows `E32_S15`'s per-session `max_concurrent_developers`/`max_concurrent_testers` cap
|
|
318
|
+
(`skills/j-do/SKILL.md`'s `### 4.4`) to actually contend across an entire `/jenga` run's dispatches,
|
|
319
|
+
instead of every spawned `/do` sub-agent minting its own private `orchestrator_session_id` and getting
|
|
320
|
+
its own private, uncontended `project/queue/concurrency-slots-<id>.json` counter file — the root-cause
|
|
321
|
+
defect this story (`E32_S16`) fixes.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
231
325
|
### Phase 1 — Decompose Epics into Stories
|
|
232
326
|
|
|
233
327
|
If Phase 0.75 produced a scoped set, restrict this phase to epics that are members of that set (directly selected, or flagged in its `undecomposed` list). Under `/jenga *`, this phase is unrestricted, exactly as before.
|
|
@@ -272,7 +366,7 @@ For each in-scope story that has one or more tasks listed in `todo.md`:
|
|
|
272
366
|
BUNDLE DETECTED: story <E##_S##> — <N> story-scoped tasks will execute as a bundle.
|
|
273
367
|
```
|
|
274
368
|
where `<E##_S##>` is the story ID and `<N>` is the count of tasks in the list.
|
|
275
|
-
b. Call `/do <E##_S##>` once (with the story ID, not individual task IDs). This invokes the bundle execution path in `/do` (implemented in E32_S05_T02), which runs all tasks sequentially in one shared worktree.
|
|
369
|
+
b. Call `/do <E##_S##>` once (with the story ID, not individual task IDs), passing this run's shared `orchestrator_session_id` (generated once in Phase 0.9) into the bundle's sender object as its `session_id` field — a caller-supplied session id per `skills/j-do/SKILL.md`'s standalone-vs-caller-supplied contract (`### 4.4`/`### 5`, `E32_S16_T02`), so the bundle path's own `### 4.4` slot acquire shares this run's one counter file too, rather than minting a private one. This invokes the bundle execution path in `/do` (implemented in E32_S05_T02), which runs all tasks sequentially in one shared worktree.
|
|
276
370
|
c. **Mark these tasks as bundled** — record their task IDs so Phase 4 skips individual dispatch for them.
|
|
277
371
|
8. **Non-bundle stories** — stories with a mixed scope, a zero-length task list, any task missing `execution_scope: story`, or any task with `crucial_level: locked` (step 5) use the normal per-task dispatch in Phase 4 without any change.
|
|
278
372
|
|
|
@@ -282,11 +376,11 @@ Loop through `todo.md` and execute all eligible items, running independent ones
|
|
|
282
376
|
|
|
283
377
|
1. **Collect eligible items** — from `todo.md`, find all items whose board file has `status: Pending` and no unresolved dependencies, **excluding tasks already dispatched as part of a story bundle in Phase 3.5**. If Phase 0.75 produced a scoped set, also exclude any item not a member of that set — execution never runs outside the confirmed/resolved scope. Under `/jenga *`, no such exclusion applies. A dependency is resolved if the blocking item's status is at least `In Progress` or `Passed`.
|
|
284
378
|
2. **Group by parallelism** — items with no shared dependencies and no overlapping output files can run concurrently. Items that depend on each other must be sequenced.
|
|
285
|
-
3. **Invoke `/do` in parallel** — launch each independent item as a **background sub-agent** simultaneously. Do not wait for one to finish before starting another if they are independent.
|
|
379
|
+
3. **Invoke `/do` in parallel** — launch each independent item as a **background sub-agent** simultaneously. Do not wait for one to finish before starting another if they are independent. Pass this run's shared `orchestrator_session_id` (generated once in Phase 0.9, unchanged across every wave) into each sub-agent's sender object as its `session_id` field, per `assets/sender_template.json`'s existing shape and `skills/j-do/SKILL.md`'s caller-supplied-session-id case (`### 4.4`/`### 5`, `E32_S16_T02`) — this is what lets `E32_S15`'s per-session developer/tester concurrency cap actually contend across every sub-agent dispatched within (and across) this run's waves, instead of each one minting its own private session id and its own private, uncontended counter file.
|
|
286
380
|
4. **Mark In Progress** — update `status: In Progress` in each launched item's board file (YAML front-matter) immediately after launch.
|
|
287
381
|
5. **Wait, drain, and loop** — once all active background agents in the wave have completed:
|
|
288
382
|
a. **Drain the scrum triggers queue** — invoke the `## Drain Scrum Triggers Queue` procedure from `agents/scrum-master.md` against `project/queue/scrum_triggers.jsonl`. `/jenga`'s orchestrating agent is the scrum-master, and this is the same session-start procedure applied mid-run: process any `rapport_review`, `status_review`, and `story_rollup` triggers written by the tester sub-sessions that just completed, then clear the file. This ensures rollups become visible on the board (story/epic status updates) before the next wave is collected, instead of sitting unprocessed until some future scrum-master session start.
|
|
289
|
-
b. **Return to step 1** of this phase to pick up any newly unblocked items — including items unblocked by the rollups just processed in (a).
|
|
383
|
+
b. **Return to step 1** of this phase to pick up any newly unblocked items — including items unblocked by the rollups just processed in (a). This loop-back reuses the SAME `orchestrator_session_id` generated once in Phase 0.9 for every subsequent wave's `/do` dispatches — it is never regenerated here or anywhere else in this phase.
|
|
290
384
|
|
|
291
385
|
### Exit condition
|
|
292
386
|
|
|
@@ -312,8 +406,8 @@ When no eligible candidates remain in Phase 4, exit and output:
|
|
|
312
406
|
- **Picker cancelled (bare branch)** — the entire `/jenga` run halts immediately after relaying the cancellation acknowledgement; no phase past 0.75 runs, and nothing on the board is modified.
|
|
313
407
|
- **Confirmation cancelled (bare or scoped branch)** — same as picker cancellation: the entire `/jenga` run halts immediately; no scoped set is produced and no later phase runs.
|
|
314
408
|
- **`detect-nl-intent.sh` classifies the argument as `mixed` (scoped branch)** — the whole invocation halts at Phase 0.75 with each rejected segment's `input`/`reason` reported verbatim, per `detect-nl-intent.sh`'s own classification contract (E53_S01_T01); no partial scope is assembled from the segments that did resolve, and no fallback guess is made for the rejected ones. The user must re-invoke `/jenga <ids>` with corrected input.
|
|
315
|
-
- **`detect-nl-intent.sh` classifies the argument as `nl_intent`, no confident single-skill match, and `match-playbook.sh` (E53_S02) also finds no playbook match** — the natural-language branch's step 4 attempts the playbook fallback first (see the Natural-language branch's step 4/6), and only THEN surfaces
|
|
316
|
-
- **`detect-nl-intent.sh` classifies the argument as `nl_intent`, no confident single-skill match, and `match-playbook.sh` returns an ambiguous multi-way tie between playbooks** — treated the same as the no-playbook-match case above: falls through to
|
|
409
|
+
- **`detect-nl-intent.sh` classifies the argument as `nl_intent`, no confident single-skill match, and `match-playbook.sh` (E53_S02) also finds no playbook match** — the natural-language branch's step 4 attempts the playbook fallback first (see the Natural-language branch's step 4/6), and only THEN surfaces the **Skill Matching & Invocation Contract**'s no-match disambiguation options (browse `/help`, create a new skill via `/btw`, proceed with the raw prompt) instead of guessing; no phase past 0.75 runs until the user picks one.
|
|
410
|
+
- **`detect-nl-intent.sh` classifies the argument as `nl_intent`, no confident single-skill match, and `match-playbook.sh` returns an ambiguous multi-way tie between playbooks** — treated the same as the no-playbook-match case above: falls through to the **Skill Matching & Invocation Contract**'s tie-break prompt (top candidates + a "neither, describe what you need" option) instead of guessing; no phase past 0.75 runs until the user picks one. (`match-playbook.sh`'s own `ambiguous` result — a tie between playbooks — is intentionally not given its own separate disambiguation UI; it is treated identically to `no_match` and routed to the same fallback prose, which already has its own tie-break handling.)
|
|
317
411
|
- **`match-playbook.sh` returns `playbook_match` and the user confirms the full chain, and every step succeeds** — the Natural-language branch's step 5e reports the full `completed` AND `skipped` steps lists to the user and stops; `/jenga`'s own Phase 1 never runs for this invocation (execution was already fully handled by the playbook's own steps, e.g. `j.do`/`j.dev-done`).
|
|
318
412
|
- **`match-playbook.sh` returns `playbook_match` but the user cancels at the chain confirmation step (step 5c)** — identical posture to the existing picker/confirmation cancellation cases above: the entire `/jenga` run halts immediately after relaying the cancellation acknowledgement, with NO step of the chain executed; nothing on the board is modified by this invocation.
|
|
319
413
|
- **`match-playbook.sh` returns `playbook_match`, the user confirms, and a step mid-chain fails** — the Natural-language branch's step 5e(vi) halts immediately on `run-playbook-step.sh`'s `halted` result: no step after the failed one runs (no silent skip-ahead), and the user is shown exactly which steps already completed or were skipped, which step failed (with its note), and which steps never ran.
|
|
@@ -329,3 +423,4 @@ When no eligible candidates remain in Phase 4, exit and output:
|
|
|
329
423
|
- **A playbook step carries composition origin metadata (depth > 1), shown at confirmation (`E53_S05_T03`)** — the Natural-language branch's step 5b relays `render-playbook-confirmation.sh`'s rendered chain, which now visibly indents and labels that step's line with "(from playbook: <id>, depth N)" — composing another playbook's steps into a chain never hides where one playbook ends and another begins from the user, even though the whole chain is still ONE numbered, editable, confirmable list (never a separate confirmation per nested playbook) and checking/unchecking a composed step still works exactly like any other step.
|
|
330
424
|
- **`/jenga *` (wildcard branch)** — never produces a scoped set; Phases 1-4 run fully unrestricted over the entire board, identical to `/jenga`'s behavior before Phase 0.75 existed.
|
|
331
425
|
- **Stale out-of-scope story queued in `todo.md` from an earlier run (scoped run only)** — Phase 3.5's scoped-set guard skips it entirely (not considered for bundling), so it cannot be dispatched via a bundle `/do <E##_S##>` call that would otherwise bypass Phase 4's own scoped-set exclusion; it remains untouched in `todo.md` until a future run's scope includes it.
|
|
426
|
+
- **`orchestrator_session_id` reuse across waves and phases (`E32_S16_T01`)** — the value generated once in Phase 0.9 is the exact same value threaded into Phase 3.5's bundle `/do` call and into every wave's `/do` sub-agent sender objects dispatched by Phase 4, for the entire duration of a single `/jenga` run; Phase 4 step 5b's loop-back never mints a new one. A run that halts before Phase 0.9 completes (e.g. cancelled at Phase 0.75's picker/confirmation) never reaches Phase 3.5 or Phase 4, so the value it would have generated is simply never used.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "board-hygiene",
|
|
3
|
+
"name": "Board Hygiene",
|
|
4
|
+
"description": "Reconciles the scrum board against what is actually implemented, captures whatever drift that turns up as follow-up todos, and reports the resulting board state -- a read-only triage chain that writes no code and produces no commit of its own.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"board hygiene",
|
|
7
|
+
"board triage",
|
|
8
|
+
"board health",
|
|
9
|
+
"tidy the board",
|
|
10
|
+
"board audit"
|
|
11
|
+
],
|
|
12
|
+
"examples": [
|
|
13
|
+
"reconcile the board, capture whatever drift you find as todos, then show me the status",
|
|
14
|
+
"do a full board hygiene pass -- sync it with what's really implemented, queue follow-ups for anything off, and report where we stand",
|
|
15
|
+
"check the board against reality, turn any discrepancies into todo items, and give me an overview at the end",
|
|
16
|
+
"the board feels out of sync -- triage it, capture the fixes, and summarise the state afterwards",
|
|
17
|
+
"audit the board end to end and only queue follow-up work if something is actually wrong"
|
|
18
|
+
],
|
|
19
|
+
"steps": [
|
|
20
|
+
"j-reconcile",
|
|
21
|
+
{
|
|
22
|
+
"skill": "j-todo",
|
|
23
|
+
"forward_from": "j-reconcile",
|
|
24
|
+
"conditional": {
|
|
25
|
+
"depends_on": "j-reconcile",
|
|
26
|
+
"predicate": "non_empty"
|
|
27
|
+
},
|
|
28
|
+
"instruction": "capture each reconciliation finding as a follow-up todo"
|
|
29
|
+
},
|
|
30
|
+
"j-status"
|
|
31
|
+
]
|
|
32
|
+
}
|