@jenga-ai/agent 1.3.0 → 2.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.
Files changed (76) hide show
  1. package/README.md +97 -92
  2. package/agents/developer.md +9 -8
  3. package/agents/scrum-master.md +57 -23
  4. package/agents/tester.md +51 -5
  5. package/hooks/on_session_end.sh +13 -1
  6. package/lib/generate-agent-context.js +18 -1
  7. package/lib/generate-copilot-instructions.js +18 -1
  8. package/lib/generate-skill-allow-list.js +191 -0
  9. package/lib/skill-allow-list.json +43 -0
  10. package/package.json +18 -4
  11. package/scripts/apply-j-prefix.sh +230 -0
  12. package/scripts/consume-context-digest.sh +103 -0
  13. package/scripts/postinstall.js +25 -0
  14. package/scripts/sweep-stale-context-digests.sh +132 -0
  15. package/scripts/validate-board.sh +5 -0
  16. package/scripts/write-context-digest.sh +230 -0
  17. package/skills/brainstorm/SKILL.md +1 -1
  18. package/skills/btw/SKILL.md +1 -1
  19. package/skills/clearify/SKILL.md +1 -1
  20. package/skills/close-story/SKILL.md +78 -6
  21. package/skills/close-story/scripts/check-privatized.sh +345 -0
  22. package/skills/commit/SKILL.md +1 -1
  23. package/skills/continue/SKILL.md +1 -1
  24. package/skills/deep-dive/SKILL.md +1 -1
  25. package/skills/dev-done/SKILL.md +1 -1
  26. package/skills/distribute/SKILL.md +1 -1
  27. package/skills/do/SKILL.md +100 -10
  28. package/skills/doc/README.md +155 -0
  29. package/skills/doc/SKILL.md +43 -13
  30. package/skills/doc/authoring-notes.md +72 -0
  31. package/skills/doc/scripts/resolve_last_update.py +149 -0
  32. package/skills/doc-sync/SKILL.md +1 -1
  33. package/skills/dooo/SKILL.md +1 -1
  34. package/skills/error/SKILL.md +1 -1
  35. package/skills/evaluate/SKILL.md +1 -1
  36. package/skills/examplify/SKILL.md +1 -1
  37. package/skills/help/SKILL.md +1 -1
  38. package/skills/idea/SKILL.md +1 -1
  39. package/skills/improve/SKILL.md +1 -1
  40. package/skills/init/SKILL.md +1 -1
  41. package/skills/init/assets/scope-thresholds_template.json +3 -3
  42. package/skills/j-init/SKILL.md +168 -0
  43. package/skills/j-init/assets/.gitignore_template +15 -0
  44. package/skills/j-init/assets/PROJECT_SUMMARY_template.md +13 -0
  45. package/skills/j-init/assets/directory_structure.txt +14 -0
  46. package/skills/j-init/assets/scope-thresholds_template.json +7 -0
  47. package/skills/j-init/assets/strategy_stub_template.md +38 -0
  48. package/skills/j-init/assets/test-config_template.json +4 -0
  49. package/skills/j-init/assets/workflow_template.json +30 -0
  50. package/skills/j-init/scripts/apply-project-visibility.sh +176 -0
  51. package/skills/j-init/scripts/detect-existing-codebase.sh +166 -0
  52. package/skills/j-init/scripts/init.sh +116 -0
  53. package/skills/jbp/SKILL.md +1 -1
  54. package/skills/jenga/SKILL.md +1 -1
  55. package/skills/jenga/scripts/render-confirmation.sh +55 -18
  56. package/skills/jenga-permission-level/SKILL.md +1 -1
  57. package/skills/lgtm/SKILL.md +1 -1
  58. package/skills/pi-plan/SKILL.md +1 -1
  59. package/skills/proceed/SKILL.md +1 -1
  60. package/skills/publish/SKILL.md +1 -1
  61. package/skills/publish/adapters/npm-ci.md +26 -4
  62. package/skills/publish/scripts/npm_ci_pipeline.sh +21 -1
  63. package/skills/reconcile/SKILL.md +1 -1
  64. package/skills/reconcile-origin/SKILL.md +1 -1
  65. package/skills/redo/SKILL.md +1 -1
  66. package/skills/skillify/SKILL.md +1 -1
  67. package/skills/spinoff/SKILL.md +1 -1
  68. package/skills/status/SKILL.md +1 -1
  69. package/skills/todo/SKILL.md +40 -3
  70. package/skills/todo/scripts/add_trivial_task.sh +216 -0
  71. package/skills/todo/scripts/update_story_tasks.py +87 -0
  72. package/skills/uncharted/SKILL.md +1 -1
  73. package/skills/wtf/SKILL.md +1 -1
  74. package/templates/SCRUM_BOARD_SCHEMA.md +33 -2
  75. package/templates/agent-context.md.tpl +32 -9
  76. package/templates/copilot-instructions.md.tpl +25 -8
@@ -0,0 +1,345 @@
1
+ #!/usr/bin/env bash
2
+ # ---------------------------------------------------------------------------
3
+ # skills/close-story/scripts/check-privatized.sh
4
+ #
5
+ # Static (run-independent) `.publicignore` membership check for a single
6
+ # board ticket, invoked by /close-story at both task granularity (Step 2's
7
+ # per-task loop) and story granularity (Step 4's finalization), per
8
+ # E51_S03_T03. Unlike `Merged` (skills/self-sync/scripts/mark-merged.sh) and
9
+ # `Publicized` (skills/mirror-public/scripts/mark-publicized.sh, E51_S03_T02),
10
+ # which both react to a specific run's file diff, `Privatized` has NO
11
+ # dependency on any run ever having occurred -- it is a pure blocklist
12
+ # membership test against the ticket's own derived touched-file list, per
13
+ # templates/SCRUM_BOARD_SCHEMA.md's "Static vs. Reactive Status Setting"
14
+ # section.
15
+ #
16
+ # Usage: check-privatized.sh <id> <board-file-path>
17
+ #
18
+ # <id> EST ticket id -- a task id (E##_S##_T##) or a story id
19
+ # (E##_S##). The anchored-grep commit matching (below)
20
+ # is what keeps a story id from over-matching its own
21
+ # children's commits, so the same script handles both
22
+ # granularities with no separate code path.
23
+ # <board-file-path> Path to the ticket's own board markdown file (task or
24
+ # story), used to read its current `status:` and as the
25
+ # write target.
26
+ #
27
+ # Touched-file derivation -- IDENTICAL technique to
28
+ # skills/self-sync/scripts/mark-merged.sh (reused, not reinvented, per this
29
+ # task's own description):
30
+ # 1. git log --all --no-merges --extended-regexp \
31
+ # --grep="<id>([^0-9_]|$)" --pretty=format:'%H'
32
+ # The anchored pattern (not a plain substring grep) is required so a
33
+ # story id like "E51_S03" does not over-match every one of its own
34
+ # child tasks' commits -- the character immediately following a story
35
+ # id in a child task's commit message is "_", which the anchor
36
+ # excludes.
37
+ # 2. Per matched commit: `git diff --name-only <sha>~1..<sha>`, falling
38
+ # back to `git diff-tree --no-commit-id --name-only -r <sha>` for a
39
+ # root commit with no parent.
40
+ # 3. Union (dedup) the file paths across all matched commits.
41
+ # 4. Zero matched commits -> nothing to check -> UNCHANGED (not an error).
42
+ #
43
+ # .publicignore matching -- ported (not sourced) from
44
+ # skills/mirror-public/scripts/mirror.sh's `_publicignore_rule_for` helper:
45
+ # directory-prefix match for trailing-"/" lines, glob match against the full
46
+ # relative path and the basename for everything else, skipping comments,
47
+ # blank lines, and "+"-prefixed include lines (which never block anything).
48
+ # This is a deliberate, flagged duplication of matching logic -- not a
49
+ # `source` of mirror.sh, which unconditionally loads config.json and
50
+ # requires rsync near the top of its execution, neither of which this
51
+ # lighter, dependency-free check needs. Same tradeoff mark-merged.sh's own
52
+ # header comment already documents and accepts for its COPY_SET extraction.
53
+ #
54
+ # A ticket is "fully blocklisted" iff EVERY file in its derived touched-file
55
+ # list matches SOME .publicignore pattern. Partial coverage leaves the
56
+ # ticket unchanged, no error (mirrors AC5's "no files fully covered"
57
+ # language, applied to the blocklist side).
58
+ #
59
+ # Precedence / mismatch logging (AC4, this script's half of it): a ticket
60
+ # already at `status: Publicized` that is found fully blocklisted still gets
61
+ # overwritten to `Privatized` (Privatized wins -- the static, always-knowable
62
+ # check takes precedence per the story's AC4 and
63
+ # templates/SCRUM_BOARD_SCHEMA.md's "Publicized / Privatized / Deployed
64
+ # Lifecycle Relationship" section), but the contradiction is logged to
65
+ # project/logs/events.json as `event: publicized_privatized_mismatch` (same
66
+ # array-of-objects convention already used by session_start/etc., and the
67
+ # same event name E51_S03_T02's own task description documents for the
68
+ # reverse-direction mismatch) so it's flagged for investigation rather than
69
+ # silently resolved. The read-modify-write against events.json is wrapped in
70
+ # scripts/with-lock.sh since multiple sessions may be writing to it
71
+ # concurrently.
72
+ #
73
+ # A ticket already at `status: Privatized` is skipped without error
74
+ # (idempotency) -- same defensive-skip convention mark-merged.sh uses for
75
+ # its own terminal status.
76
+ #
77
+ # Output contract (stdout, single line) -- consumed by /close-story's prose
78
+ # steps to decide whether to report a Privatized ticket:
79
+ # PRIVATIZED status was written (or already Privatized -- treated the
80
+ # same by the caller: either way the ticket ends this call at
81
+ # status Privatized)
82
+ # UNCHANGED ticket left as-is (zero matched commits, partial coverage,
83
+ # or already Privatized with nothing further to do)
84
+ #
85
+ # Exit codes:
86
+ # 0 success (covers both PRIVATIZED and UNCHANGED outcomes)
87
+ # 1 error (bad usage, missing file, or a status-write failure)
88
+ # ---------------------------------------------------------------------------
89
+
90
+ set -euo pipefail
91
+ IFS=$'\n\t'
92
+
93
+ die() {
94
+ printf 'check-privatized.sh: error: %s\n' "$*" >&2
95
+ exit 1
96
+ }
97
+
98
+ log() {
99
+ printf 'check-privatized.sh: %s\n' "$*" >&2
100
+ }
101
+
102
+ usage() {
103
+ cat <<'EOF'
104
+ Usage: check-privatized.sh <id> <board-file-path>
105
+
106
+ Static .publicignore membership check for a single ticket (task or story).
107
+ Prints PRIVATIZED or UNCHANGED to stdout. See script header for full
108
+ semantics.
109
+ EOF
110
+ }
111
+
112
+ if [ $# -ne 2 ]; then
113
+ usage >&2
114
+ die "expected exactly 2 arguments, got $#"
115
+ fi
116
+
117
+ ID="$1"
118
+ BOARD_FILE="$2"
119
+
120
+ [ -f "$BOARD_FILE" ] || die "board file not found: $BOARD_FILE"
121
+
122
+ # -----------------------------------------------------------------------------
123
+ # Locate script + repo root (same symlink-resolution + repo-root derivation
124
+ # pattern as mark-merged.sh, so this script behaves identically whether
125
+ # invoked directly or via a symlink).
126
+ # -----------------------------------------------------------------------------
127
+
128
+ SCRIPT_PATH="${BASH_SOURCE[0]}"
129
+ while [ -h "$SCRIPT_PATH" ]; do
130
+ LINK_TARGET="$(readlink "$SCRIPT_PATH")"
131
+ case "$LINK_TARGET" in
132
+ /*) SCRIPT_PATH="$LINK_TARGET" ;;
133
+ *) SCRIPT_PATH="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)/$LINK_TARGET" ;;
134
+ esac
135
+ done
136
+ SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
137
+
138
+ REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
139
+ [ -n "$REPO_ROOT" ] || die "could not locate repo root (git rev-parse failed from $SCRIPT_DIR)"
140
+
141
+ PUBLICIGNORE="$REPO_ROOT/.publicignore"
142
+ [ -f "$PUBLICIGNORE" ] || die ".publicignore not found at $PUBLICIGNORE"
143
+
144
+ WITH_LOCK_SCRIPT="$REPO_ROOT/scripts/with-lock.sh"
145
+ UPDATE_FRONTMATTER_SCRIPT="$REPO_ROOT/skills/close-story/scripts/update-task-frontmatter.sh"
146
+ EVENTS_FILE="$REPO_ROOT/project/logs/events.json"
147
+
148
+ [ -f "$WITH_LOCK_SCRIPT" ] || die "expected script not found: $WITH_LOCK_SCRIPT"
149
+ [ -f "$UPDATE_FRONTMATTER_SCRIPT" ] || die "expected script not found: $UPDATE_FRONTMATTER_SCRIPT"
150
+
151
+ # -----------------------------------------------------------------------------
152
+ # Frontmatter field reader -- same "line == ---" boundary-detection style
153
+ # already used by update-task-frontmatter.sh / mark-merged.sh.
154
+ # -----------------------------------------------------------------------------
155
+
156
+ read_frontmatter_field() {
157
+ local file="$1" key="$2"
158
+ awk -v key="$key" '
159
+ NR==1 && $0=="---" { infm=1; next }
160
+ infm && $0=="---" { exit }
161
+ infm && $0 ~ ("^" key ":") {
162
+ sub("^" key ":[ \t]*", "", $0)
163
+ print $0
164
+ exit
165
+ }
166
+ ' "$file"
167
+ }
168
+
169
+ trim() {
170
+ local s="$1"
171
+ s="${s#"${s%%[![:space:]]*}"}"
172
+ s="${s%"${s##*[![:space:]]}"}"
173
+ s="${s%\"}"; s="${s#\"}"
174
+ s="${s%\'}"; s="${s#\'}"
175
+ printf '%s' "$s"
176
+ }
177
+
178
+ CURRENT_STATUS="$(trim "$(read_frontmatter_field "$BOARD_FILE" "status")")"
179
+
180
+ if [ "$CURRENT_STATUS" = "Privatized" ]; then
181
+ log "$ID: already Privatized -- skip (idempotent no-op)"
182
+ echo "UNCHANGED"
183
+ exit 0
184
+ fi
185
+
186
+ # -----------------------------------------------------------------------------
187
+ # Touched-file derivation -- identical technique to mark-merged.sh.
188
+ # -----------------------------------------------------------------------------
189
+
190
+ commits_for_ticket() {
191
+ local id="$1"
192
+ git -C "$REPO_ROOT" log --all --no-merges --extended-regexp \
193
+ --grep="${id}([^0-9_]|\$)" --pretty=format:'%H' 2>/dev/null || true
194
+ }
195
+
196
+ commit_files() {
197
+ local sha="$1"
198
+ if git -C "$REPO_ROOT" rev-parse --verify -q "${sha}~1" >/dev/null 2>&1; then
199
+ git -C "$REPO_ROOT" diff --name-only "${sha}~1..${sha}" 2>/dev/null || true
200
+ else
201
+ git -C "$REPO_ROOT" diff-tree --no-commit-id --name-only -r "$sha" 2>/dev/null || true
202
+ fi
203
+ }
204
+
205
+ collect_touched_files() {
206
+ local id="$1"
207
+ local shas sha
208
+ shas="$(commits_for_ticket "$id")"
209
+ [ -n "$shas" ] || return 0
210
+ while IFS= read -r sha; do
211
+ [ -n "$sha" ] || continue
212
+ commit_files "$sha"
213
+ done <<< "$shas" | sort -u
214
+ }
215
+
216
+ TOUCHED="$(collect_touched_files "$ID")"
217
+
218
+ if [ -z "$TOUCHED" ]; then
219
+ log "$ID: zero matched EST-tagged commits -- left unchanged"
220
+ echo "UNCHANGED"
221
+ exit 0
222
+ fi
223
+
224
+ # -----------------------------------------------------------------------------
225
+ # .publicignore matching -- ported from mirror.sh's _publicignore_rule_for.
226
+ #
227
+ # Prints the matching .publicignore line and returns 0 on a match; returns 1
228
+ # with no output if nothing matched. Directory-prefix match for trailing-"/"
229
+ # lines, glob match against the full relative path and the basename for
230
+ # everything else, skipping comments/blank lines/"+"-prefixed include lines.
231
+ # -----------------------------------------------------------------------------
232
+
233
+ _publicignore_rule_for() {
234
+ local rel_path="$1" line pattern base dirpat
235
+ base="$(basename "$rel_path")"
236
+ while IFS= read -r line || [ -n "$line" ]; do
237
+ case "$line" in
238
+ ''|'#'*|'+'*) continue ;;
239
+ esac
240
+ pattern="$line"
241
+ case "$pattern" in
242
+ '- '*) pattern="${pattern#- }" ;;
243
+ esac
244
+ [ -n "$pattern" ] || continue
245
+ case "$pattern" in
246
+ */)
247
+ dirpat="${pattern%/}"
248
+ case "$rel_path" in
249
+ "$dirpat"/*)
250
+ printf '%s\n' "$line"
251
+ return 0
252
+ ;;
253
+ esac
254
+ ;;
255
+ *)
256
+ case "$rel_path" in
257
+ $pattern)
258
+ printf '%s\n' "$line"
259
+ return 0
260
+ ;;
261
+ esac
262
+ case "$base" in
263
+ $pattern)
264
+ printf '%s\n' "$line"
265
+ return 0
266
+ ;;
267
+ esac
268
+ ;;
269
+ esac
270
+ done < "$PUBLICIGNORE"
271
+ return 1
272
+ }
273
+
274
+ # True (0) iff every line of $1 (touched files, newline-separated) matches
275
+ # some .publicignore pattern.
276
+ is_fully_blocklisted() {
277
+ local touched="$1"
278
+ local line
279
+ while IFS= read -r line; do
280
+ [ -n "$line" ] || continue
281
+ _publicignore_rule_for "$line" >/dev/null || return 1
282
+ done <<< "$touched"
283
+ return 0
284
+ }
285
+
286
+ if ! is_fully_blocklisted "$TOUCHED"; then
287
+ log "$ID: not fully covered by .publicignore -- left unchanged"
288
+ echo "UNCHANGED"
289
+ exit 0
290
+ fi
291
+
292
+ # -----------------------------------------------------------------------------
293
+ # Fully blocklisted. Precedence check against a contradictory Publicized
294
+ # status (AC4) before writing.
295
+ # -----------------------------------------------------------------------------
296
+
297
+ if [ "$CURRENT_STATUS" = "Publicized" ]; then
298
+ log "$ID: WARNING -- ticket is status: Publicized but every touched file matches .publicignore; Privatized takes precedence (overwriting), logging mismatch"
299
+
300
+ TOUCHED_JSON_LIST="$(printf '%s\n' "$TOUCHED" | awk '
301
+ BEGIN { printf "[" ; first=1 }
302
+ { if (!first) printf ","; printf "\"%s\"", $0; first=0 }
303
+ END { printf "]" }
304
+ ')"
305
+ [ -n "$TOUCHED_JSON_LIST" ] || TOUCHED_JSON_LIST="[]"
306
+
307
+ MISMATCH_EVENT=$(printf '{"event": "publicized_privatized_mismatch", "agent": "developer", "id": "%s", "file": "%s", "touched_files": %s, "date": "%s"}' \
308
+ "$ID" "$BOARD_FILE" "$TOUCHED_JSON_LIST" "$(date -u +%Y-%m-%dT%H:%M:%SZ)")
309
+
310
+ if [ -f "$EVENTS_FILE" ]; then
311
+ "$WITH_LOCK_SCRIPT" "$EVENTS_FILE" -- bash -c '
312
+ set -euo pipefail
313
+ events_file="$1"
314
+ new_event="$2"
315
+ tmp="$(mktemp)"
316
+ if command -v python3 >/dev/null 2>&1; then
317
+ python3 -c "
318
+ import json, sys
319
+ path = sys.argv[1]
320
+ new_event = json.loads(sys.argv[2])
321
+ with open(path) as f:
322
+ data = json.load(f)
323
+ data.append(new_event)
324
+ with open(path, \"w\") as f:
325
+ json.dump(data, f, indent=2)
326
+ f.write(\"\n\")
327
+ " "$events_file" "$new_event"
328
+ else
329
+ echo "check-privatized.sh: python3 not available -- could not append mismatch event" >&2
330
+ rm -f "$tmp"
331
+ exit 1
332
+ fi
333
+ ' _ "$EVENTS_FILE" "$MISMATCH_EVENT" || log "WARNING: failed to log publicized_privatized_mismatch event for $ID (continuing with status overwrite)"
334
+ else
335
+ log "WARNING: $EVENTS_FILE not found -- could not log publicized_privatized_mismatch event"
336
+ fi
337
+ fi
338
+
339
+ log "$ID: fully covered by .publicignore -- writing status: Privatized ($BOARD_FILE)"
340
+ if ! "$WITH_LOCK_SCRIPT" "$BOARD_FILE" -- bash "$UPDATE_FRONTMATTER_SCRIPT" "$BOARD_FILE" status Privatized; then
341
+ die "failed to write status: Privatized for $ID ($BOARD_FILE)"
342
+ fi
343
+
344
+ echo "PRIVATIZED"
345
+ exit 0
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: commit
2
+ name: j:commit
3
3
  description: Commit implemented epic, story, or task work using the EST naming convention. Also handles user-action prerequisites and new-epic boundaries. Use after completing any EST work item.
4
4
  keywords:
5
5
  - commit
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: continue
2
+ name: j:continue
3
3
  description: Check project status across PROJECT_SUMMARY.md, epics, and stories to determine what should be done next. Reports "All done!" if everything is complete.
4
4
  keywords:
5
5
  - continue
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: deep-dive
2
+ name: j:deep-dive
3
3
  description: >
4
4
  Multi-phase investigation workflow. The scrum-master orchestrates information
5
5
  gathering, interactive brainstorming, critical scrutiny, and solution assessment
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: dev-done
2
+ name: j:dev-done
3
3
  description: Commit the current work and immediately sync it into the .claude/ and .agents/ mirrors. Shortcut that chains /commit followed by /self-sync.
4
4
  keywords:
5
5
  - dev done
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: distribute
2
+ name: j:distribute
3
3
  description: Distribute Jenga AI framework files from this private monorepo to one or more consuming projects via the local filesystem. Manages release type selection, version bumping, dry-run preview, per-target file copy, and a post-distribution git commit.
4
4
  keywords:
5
5
  - distribute
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: do
2
+ name: j:do
3
3
  description: Execute tasks from the scrum board. Reads from project/todo.md, resolves each entry to its full scrum board context, and drives the developer agent through implementation with the correct sender object and communication contract. Loops until all selected tasks are done or the user exits.
4
4
  keywords:
5
5
  - do
@@ -16,6 +16,14 @@ metadata:
16
16
 
17
17
  # Do — Execute Scrum Board Tasks
18
18
 
19
+ ## `--trivial` Flag
20
+
21
+ **Syntax:** `/do <id> --trivial` — a dispatch-time override, distinct from `/todo --trivial` (a creation-time flag documented in `skills/todo/SKILL.md`). Where `/todo --trivial` writes a brand-new task straight to `execution_scope: inline`, `/do <id> --trivial` overrides an **already-existing** task's `execution_scope` — whatever it currently is, including absent (legacy tasks with no execution-scope fields at all) — to `inline` at the moment it's dispatched. See `### 4.1.5. \`--trivial\` Dispatch-Time Override` below for the full mechanics.
22
+
23
+ **No softer fallback tier — hard fallback to the full pipeline instead.** Unlike `light` scope (which sits between `inline` and `task`), `--trivial` always forces `inline` directly, with no intermediate tier to fall back to first. To compensate, a `--trivial`-forced run that fails `scripts/smoke-harness.sh` or shows detected scope creep mid-run automatically re-routes to the full `task` pipeline (worktree + developer + tester) via the same `#### Fallback to Full Task-Scope Pipeline` procedure the `light`-tier fallback uses — see `### 4.2`'s failure-handling branches and the shared Fallback subsection under `### 4.3`.
24
+
25
+ **Human-only, same as `/todo --trivial`.** `--trivial` is invoked by a human typing `/do <id> --trivial` — it is never applied autonomously by `/jenga` or the scrum-master's own breakdown/dispatch passes.
26
+
19
27
  ## Instructions
20
28
 
21
29
  ### 0. Load threshold config
@@ -318,9 +326,40 @@ Before invoking the developer, check whether this task was manually scoped by a
318
326
  ```
319
327
  Then proceed to step 4.2.
320
328
 
329
+ ### 4.1.5. `--trivial` Dispatch-Time Override
330
+
331
+ After override validation (step 4.1) and before branching on `execution_scope` in step 4.2, check whether this invocation was `/do <id> --trivial`.
332
+
333
+ 1. **Detect the flag.** If the task was invoked as `/do <id>` with no `--trivial` flag, skip this entire section and proceed directly to `### 4.2`.
334
+
335
+ 2. **If `--trivial` is present**, read the task's current `execution_scope` from frontmatter. Treat an absent value as `task`, per the epic's backward-compatibility rule (a task that omits `execution_scope` is treated as `execution_scope: task`). Call this the **prior tier** — this covers both tasks nobody flagged as trivial at creation (an already-assigned `story`/`task`/`light` tier) and legacy tasks with no `execution_scope` set at all.
336
+
337
+ 3. **Overwrite `execution_scope` to `inline`** in the task's frontmatter, unconditionally — `--trivial` always forces `inline`, never a softer "lightest safe tier."
338
+
339
+ 4. **Record the override for audit**, reusing the existing `jenga_assigned` / `override_justification` pairing already defined in `templates/SCRUM_BOARD_SCHEMA.md` for exactly this situation ("scope overridden by a human"), rather than inventing a new field:
340
+ - Set `jenga_assigned: false` (if not already `false`).
341
+ - Set (or append to, if already present) `override_justification`:
342
+ ```
343
+ override_justification: "/do --trivial dispatch-time override on <date>: execution_scope forced from '<prior_tier>' to 'inline' by human operator."
344
+ ```
345
+ - Also set (or append to) `scope_rationale`, mirroring the phrasing convention `skills/todo/scripts/add_trivial_task.sh` already uses for the creation-time flag, so both audit fields agree on the prior tier:
346
+ ```
347
+ scope_rationale: "forced inline via /do --trivial (dispatch-time override); prior execution_scope was '<prior_tier>'"
348
+ ```
349
+ - Emit a non-fatal log line (styled like 4.2's `AUTO-CORRECTION` message):
350
+ ```
351
+ TRIVIAL OVERRIDE [<task_id>]: execution_scope forced from "<prior_tier>" to "inline" via --trivial dispatch-time override.
352
+ ```
353
+
354
+ 5. **Set an in-session marker** (this dispatch is a `--trivial`-forced run) — this does not need to be persisted to frontmatter; it only needs to survive for the remainder of this `/do` invocation. This marker is distinct from an organically-assigned `execution_scope: inline` task (one the scrum-master or `/jenga` assigned `inline` to directly, with no `--trivial` involved) because its failure handling differs — see `### 4.2`'s failure-handling steps below. Do not confuse a `--trivial`-forced run with an organic `inline` task when applying those steps.
355
+
356
+ 6. **Proceed to `### 4.2. Inline Execution Path`** with `execution_scope` now `inline`. Everything else about inline execution (implementation, smoke test invocation, commit convention) is identical between an organic `inline` task and a `--trivial`-forced one — only the two failure-handling branches in `### 4.2` differ, per the marker set in step 5 above.
357
+
358
+ **Precedence with the locked-task dispatch guard.** If `crucial_level: locked` (checked by `### 4.2`'s locked-task dispatch guard, which always runs and always wins), `--trivial` is redundant but harmless — the task was already going to be forced `inline`. The locked-task guard's own audit fields take precedence for that correction; do not double-write conflicting `override_justification` text. A `locked` task's fallback behavior remains "halt and report," never the `--trivial` fallback below, regardless of whether `--trivial` was also passed.
359
+
321
360
  ### 4.2. Inline Execution Path (execution_scope: inline)
322
361
 
323
- After resolving the task context (step 4) and passing override validation (step 4.1), read `execution_scope` from the task frontmatter.
362
+ After resolving the task context (step 4), passing override validation (step 4.1), and applying the `--trivial` dispatch-time override if present (step 4.1.5), read `execution_scope` from the task frontmatter.
324
363
 
325
364
  **Locked-task dispatch guard (defense-in-depth).** Before branching on `execution_scope` below, read `crucial_level` from the task frontmatter (per `templates/SCRUM_BOARD_SCHEMA.md`'s Crucial Flag Fields). If `crucial_level: locked`:
326
365
 
@@ -349,12 +388,14 @@ After resolving the task context (step 4) and passing override validation (step
349
388
  WARNING [<task_id>]: scripts/smoke-harness.sh not found. Smoke test skipped (stub pass).
350
389
  ```
351
390
  4. **If the smoke test exits non-zero**:
352
- - Write `status: Failed` to the task's frontmatter.
353
- - Emit:
354
- ```
355
- INLINE TASK FAILED [<task_id>]: smoke test returned non-zero exit code. Task marked Failed. Halting.
356
- ```
357
- - Do not commit. Do not proceed to the next task.
391
+ - **If this is a `--trivial`-forced run** (marker set in step 4.1.5 — and `crucial_level` is not `locked`, which never falls back, per 4.1.5's precedence note): do NOT write `status: Failed`. `--trivial` always forces `inline` with no softer "lightest safe tier" to fall back to first, so a smoke-harness failure here goes straight to the shared `#### Fallback to Full Task-Scope Pipeline` procedure below (origin: `trivial`). Do not proceed with the remaining inline steps below — the Fallback procedure takes over from here.
392
+ - **Otherwise** (an organically-assigned `inline` task, `--trivial` not involved): behavior is unchanged from before —
393
+ - Write `status: Failed` to the task's frontmatter.
394
+ - Emit:
395
+ ```
396
+ INLINE TASK FAILED [<task_id>]: smoke test returned non-zero exit code. Task marked Failed. Halting.
397
+ ```
398
+ - Do not commit. Do not proceed to the next task.
358
399
  5. **If the smoke test passes**:
359
400
  - Commit the changes using the standard commit convention (`task(<task_id>): <short description>`) via `/commit` in inline mode (E32_S04_T03).
360
401
  - Run the **Intent-vs-Diff Check** (see `### 5.1. Intent-vs-Diff Check` below) for this task.
@@ -365,9 +406,58 @@ After resolving the task context (step 4) and passing override validation (step
365
406
  7. `inline` tasks always have `needs_docs: false` — skip plan and summary documentation for the implemented task.
366
407
  8. Continue to `### 6. Verify documentation`, then `### 7. After successful completion`.
367
408
 
368
- If the implementation cannot be completed inline (scope is larger than anticipated), abort and re-route to the normal developer path (step 5) — unless `crucial_level: locked`, in which case do not re-route; re-attempt inline or halt and report, per the locked-task dispatch guard above.
409
+ If the implementation cannot be completed inline (scope is larger than anticipated — detected scope creep mid-run):
410
+ - **If this is a `--trivial`-forced run** (and `crucial_level` is not `locked`): invoke the shared `#### Fallback to Full Task-Scope Pipeline` procedure below (origin: `trivial`) — the same procedure the smoke-harness-failure branch above uses, not a second bespoke re-route.
411
+ - **Otherwise** (an organically-assigned `inline` task): abort and re-route to the normal developer path (step 5), unchanged from before.
412
+ - **Regardless of `--trivial`**, if `crucial_level: locked`, do not re-route via either path above; re-attempt inline or halt and report, per the locked-task dispatch guard above.
369
413
 
370
- **If `execution_scope` is not `inline`** (or is absent / `task` / `story` / `epic`) **and `crucial_level` is not `locked`**, proceed to step 5 (invoke the developer agent) as normal.
414
+ **If `execution_scope` is `light`** (and `crucial_level` is not `locked` — the locked-task dispatch guard above already ran and takes precedence over any scope check), proceed to `### 4.3. Light Execution Path` below instead of step 5.
415
+
416
+ **If `execution_scope` is not `inline` and not `light`** (or is absent / `task` / `story` / `epic`) **and `crucial_level` is not `locked`**, proceed to step 5 (invoke the developer agent) as normal.
417
+
418
+ ### 4.3. Light Execution Path (execution_scope: light)
419
+
420
+ After resolving the task context (step 4), passing override validation (step 4.1), and applying the `--trivial` dispatch-time override if present (step 4.1.5 — note `--trivial` always forces `inline`, so a `light`-scoped task only reaches this section if `--trivial` was *not* passed), if `execution_scope: light` and `crucial_level` is not `locked` (per the locked-task dispatch guard in 4.2, which runs first and always wins), route the task through this path instead of the full `task`-scope pipeline in step 5.
421
+
422
+ `light` sits between `inline` and `task`: unlike `inline`, it spawns a real developer subagent (so it can handle small branching logic that inline's main-session execution isn't suited for); unlike `task`, it does not create a dedicated worktree and does not invoke the tester as a separate step.
423
+
424
+ 1. **Spawn a developer subagent** (Agent tool, `subagent_type: "developer"`) with the same sender object and context payload as step 5 would use, but with an explicit instruction added to the dispatch prompt: **do not create a worktree** — implement directly against the current checkout (the session's existing working tree), not an isolated `.claude/worktrees/<slug>` copy. This is the one concrete difference from the step-5 `task` path: everything else about how the subagent implements the task (reading the task file, following acceptance criteria, following repo conventions) is unchanged.
425
+
426
+ 2. **After the developer subagent reports implementation complete**, run the smoke test harness using the same invocation convention as `### 4.2. Inline Execution Path`:
427
+ - Run `bash scripts/smoke-harness.sh <changed_file>...`, passing the paths the subagent changed. With no arguments the harness infers them from `git diff --name-only HEAD`. It exits `0` on pass and `1` on failure.
428
+ - If `scripts/smoke-harness.sh` does not exist, log a warning and treat the result as a pass:
429
+ ```
430
+ WARNING [<task_id>]: scripts/smoke-harness.sh not found. Smoke test skipped (stub pass).
431
+ ```
432
+
433
+ 3. **If the smoke test passes**:
434
+ - The developer subagent self-verifies the implementation against the task's acceptance criteria. No tester subagent is invoked for a `light`-scoped task — this is a deliberate, documented exception to "the tester is the sole status-writer" (`agents/tester.md`), mirroring the same exception already established for `inline` scope in step 5 of `### 4.2`. Since no tester runs, the developer/orchestrator is the one who writes the terminal status for a `light`-scoped task.
435
+ - Commit the changes using the standard commit convention (`task(<task_id>): <short description>`) via `/commit`.
436
+ - Run the **Intent-vs-Diff Check** (see `### 5.1. Intent-vs-Diff Check` below) for this task.
437
+ - Write `status: Passed` and `date_completed: <today>` to the task's frontmatter if self-verification passes.
438
+ - Remove the task from `project/todo.md`.
439
+ - Continue to `### 6. Verify documentation`, then `### 7. After successful completion`.
440
+
441
+ 4. **If the smoke test fails (non-zero exit)**: do NOT write `status: Failed` and do NOT halt. Instead, invoke `#### Fallback to Full Task-Scope Pipeline` below (origin: `light`).
442
+
443
+ #### Fallback to Full Task-Scope Pipeline
444
+
445
+ This is a self-contained, reusable procedure with two current callers — `### 4.2`'s `--trivial`-forced inline failure branches (origin: `trivial`) and `### 4.3`'s `light`-scope smoke-harness failure (origin: `light`) — given a task that was attempted under a reduced-overhead execution scope and failed its smoke-harness check (or, for `trivial`, showed detected scope creep mid-run), do the following. The only thing that varies by caller is the notice text in step 5; steps 1–4 and 6 are identical regardless of origin.
446
+
447
+ 1. **Do not mark the task `Failed`.** A smoke-harness failure (or detected scope creep) under a reduced-overhead scope means the scope was too small for the task, not that the task itself is unworkable — the correct response is to retry under full isolation, not to reject the work.
448
+ 2. **Create a worktree** for the task, named `<E##_S##_T##-short-slug>` per standard Worktree Management conventions, if one does not already exist for this task. (A task dispatched under `light` scope, or forced `inline` via `--trivial`, never had one — both premises skip worktree creation — so this step always creates a fresh worktree in that case.)
449
+ 3. **Spawn a developer subagent** in that worktree and have it pick up from the current state of the code (the changes already made by the reduced-overhead attempt are still present in the working tree / already committed, if any commit occurred — the subagent continues from there rather than starting over).
450
+ 4. **Invoke the tester agent** per the normal `### 5. Invoke the developer agent` flow's contract — full sender object, commit SHAs, worktree path. The tester is responsible for the terminal status write, exactly as in the standard `task`-scope pipeline.
451
+ 5. **Emit a clear, non-fatal fallback notice** to the user/orchestrator, using the message matching the caller's origin:
452
+ - origin `light`:
453
+ ```
454
+ LIGHT SCOPE FALLBACK [<task_id>]: smoke test failed; re-routing to full task-scope pipeline (worktree + developer + tester).
455
+ ```
456
+ - origin `trivial`:
457
+ ```
458
+ TRIVIAL OVERRIDE FALLBACK [<task_id>]: smoke test failed (or scope creep detected); re-routing to full task-scope pipeline (worktree + developer + tester).
459
+ ```
460
+ 6. Resume normal `task`-scope processing (steps 6–8 below) once the tester returns a verdict.
371
461
 
372
462
  ### 5. Invoke the developer agent
373
463
  Pass the following to the developer agent: