@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.
- package/README.md +97 -92
- package/agents/developer.md +9 -8
- package/agents/scrum-master.md +57 -23
- package/agents/tester.md +51 -5
- package/hooks/on_session_end.sh +13 -1
- package/lib/generate-agent-context.js +18 -1
- package/lib/generate-copilot-instructions.js +18 -1
- package/lib/generate-skill-allow-list.js +191 -0
- package/lib/skill-allow-list.json +43 -0
- package/package.json +18 -4
- package/scripts/apply-j-prefix.sh +230 -0
- package/scripts/consume-context-digest.sh +103 -0
- package/scripts/postinstall.js +25 -0
- package/scripts/sweep-stale-context-digests.sh +132 -0
- package/scripts/validate-board.sh +5 -0
- package/scripts/write-context-digest.sh +230 -0
- package/skills/brainstorm/SKILL.md +1 -1
- package/skills/btw/SKILL.md +1 -1
- package/skills/clearify/SKILL.md +1 -1
- package/skills/close-story/SKILL.md +78 -6
- package/skills/close-story/scripts/check-privatized.sh +345 -0
- package/skills/commit/SKILL.md +1 -1
- package/skills/continue/SKILL.md +1 -1
- package/skills/deep-dive/SKILL.md +1 -1
- package/skills/dev-done/SKILL.md +1 -1
- package/skills/distribute/SKILL.md +1 -1
- package/skills/do/SKILL.md +100 -10
- package/skills/doc/README.md +155 -0
- package/skills/doc/SKILL.md +43 -13
- package/skills/doc/authoring-notes.md +72 -0
- package/skills/doc/scripts/resolve_last_update.py +149 -0
- package/skills/doc-sync/SKILL.md +1 -1
- package/skills/dooo/SKILL.md +1 -1
- package/skills/error/SKILL.md +1 -1
- package/skills/evaluate/SKILL.md +1 -1
- package/skills/examplify/SKILL.md +1 -1
- package/skills/help/SKILL.md +1 -1
- package/skills/idea/SKILL.md +1 -1
- package/skills/improve/SKILL.md +1 -1
- package/skills/init/SKILL.md +1 -1
- package/skills/init/assets/scope-thresholds_template.json +3 -3
- package/skills/j-init/SKILL.md +168 -0
- package/skills/j-init/assets/.gitignore_template +15 -0
- package/skills/j-init/assets/PROJECT_SUMMARY_template.md +13 -0
- package/skills/j-init/assets/directory_structure.txt +14 -0
- package/skills/j-init/assets/scope-thresholds_template.json +7 -0
- package/skills/j-init/assets/strategy_stub_template.md +38 -0
- package/skills/j-init/assets/test-config_template.json +4 -0
- package/skills/j-init/assets/workflow_template.json +30 -0
- package/skills/j-init/scripts/apply-project-visibility.sh +176 -0
- package/skills/j-init/scripts/detect-existing-codebase.sh +166 -0
- package/skills/j-init/scripts/init.sh +116 -0
- package/skills/jbp/SKILL.md +1 -1
- package/skills/jenga/SKILL.md +1 -1
- package/skills/jenga/scripts/render-confirmation.sh +55 -18
- package/skills/jenga-permission-level/SKILL.md +1 -1
- package/skills/lgtm/SKILL.md +1 -1
- package/skills/pi-plan/SKILL.md +1 -1
- package/skills/proceed/SKILL.md +1 -1
- package/skills/publish/SKILL.md +1 -1
- package/skills/publish/adapters/npm-ci.md +26 -4
- package/skills/publish/scripts/npm_ci_pipeline.sh +21 -1
- package/skills/reconcile/SKILL.md +1 -1
- package/skills/reconcile-origin/SKILL.md +1 -1
- package/skills/redo/SKILL.md +1 -1
- package/skills/skillify/SKILL.md +1 -1
- package/skills/spinoff/SKILL.md +1 -1
- package/skills/status/SKILL.md +1 -1
- package/skills/todo/SKILL.md +40 -3
- package/skills/todo/scripts/add_trivial_task.sh +216 -0
- package/skills/todo/scripts/update_story_tasks.py +87 -0
- package/skills/uncharted/SKILL.md +1 -1
- package/skills/wtf/SKILL.md +1 -1
- package/templates/SCRUM_BOARD_SCHEMA.md +33 -2
- package/templates/agent-context.md.tpl +32 -9
- package/templates/copilot-instructions.md.tpl +25 -8
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# skills/todo/scripts/add_trivial_task.sh
|
|
3
|
+
#
|
|
4
|
+
# Creates a fully-formed task board file for a `/todo --trivial` mission,
|
|
5
|
+
# forced to execution_scope: inline unconditionally, and registers it both
|
|
6
|
+
# in its parent story's tasks: frontmatter list and in project/todo.md.
|
|
7
|
+
#
|
|
8
|
+
# This script performs only the MECHANICAL half of --trivial: templating the
|
|
9
|
+
# task file, assigning the next task number, and wiring it into the board.
|
|
10
|
+
# The JUDGMENT half — estimating file/line counts and deciding what
|
|
11
|
+
# execution_scope the normal heuristic in agents/scrum-master.md would have
|
|
12
|
+
# assigned (computed_tier) — happens in the calling agent's instructions
|
|
13
|
+
# (skills/todo/SKILL.md), per the Skill Implementation Principle: a step that
|
|
14
|
+
# requires reasoning about branching logic, new dependencies, etc. does not
|
|
15
|
+
# belong in a script. This script never reads project/configs/scope-thresholds.json
|
|
16
|
+
# or recomputes computed_tier itself — it only records what it was told.
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# add_trivial_task.sh --story <E##_S##> --title "<title>" \
|
|
20
|
+
# --description "<description>" --criteria "<c1>|<c2>|..." \
|
|
21
|
+
# --computed-tier <inline|task|story> --est-files <N> --est-lines <M> \
|
|
22
|
+
# [--prerequisites "<text>"]
|
|
23
|
+
#
|
|
24
|
+
# Requires an ALREADY-EXISTING parent story file (--trivial does not create
|
|
25
|
+
# stories or epics — resolve/create those first via the normal /todo flow).
|
|
26
|
+
#
|
|
27
|
+
# On success, prints the new task ID (e.g. E32_S14_T05) to stdout and exits 0.
|
|
28
|
+
|
|
29
|
+
set -euo pipefail
|
|
30
|
+
|
|
31
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
|
32
|
+
cd "$REPO_ROOT"
|
|
33
|
+
|
|
34
|
+
TASKS_DIR="project/board/tasks"
|
|
35
|
+
STORIES_DIR="project/board/stories"
|
|
36
|
+
UPDATE_STORY_SCRIPT="skills/todo/scripts/update_story_tasks.py"
|
|
37
|
+
|
|
38
|
+
story_id=""
|
|
39
|
+
title=""
|
|
40
|
+
description=""
|
|
41
|
+
criteria=""
|
|
42
|
+
computed_tier=""
|
|
43
|
+
est_files=""
|
|
44
|
+
est_lines=""
|
|
45
|
+
prerequisites="None."
|
|
46
|
+
|
|
47
|
+
usage() {
|
|
48
|
+
cat >&2 <<EOF
|
|
49
|
+
Usage: $0 --story <E##_S##> --title "<title>" --description "<description>" \\
|
|
50
|
+
--criteria "<c1>|<c2>|..." --computed-tier <inline|task|story> \\
|
|
51
|
+
--est-files <N> --est-lines <M> [--prerequisites "<text>"]
|
|
52
|
+
EOF
|
|
53
|
+
exit 1
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
while [ $# -gt 0 ]; do
|
|
57
|
+
case "$1" in
|
|
58
|
+
--story) story_id="$2"; shift 2 ;;
|
|
59
|
+
--title) title="$2"; shift 2 ;;
|
|
60
|
+
--description) description="$2"; shift 2 ;;
|
|
61
|
+
--criteria) criteria="$2"; shift 2 ;;
|
|
62
|
+
--computed-tier) computed_tier="$2"; shift 2 ;;
|
|
63
|
+
--est-files) est_files="$2"; shift 2 ;;
|
|
64
|
+
--est-lines) est_lines="$2"; shift 2 ;;
|
|
65
|
+
--prerequisites) prerequisites="$2"; shift 2 ;;
|
|
66
|
+
-h|--help) usage ;;
|
|
67
|
+
*) echo "Error: unknown argument '$1'" >&2; usage ;;
|
|
68
|
+
esac
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
for req_name in story_id title description criteria computed_tier est_files est_lines; do
|
|
72
|
+
if [ -z "${!req_name}" ]; then
|
|
73
|
+
echo "Error: --${req_name//_/-} is required" >&2
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
if ! [[ "$story_id" =~ ^E[0-9]+_S[0-9]+$ ]]; then
|
|
79
|
+
echo "Error: --story must look like E##_S## (got '$story_id')" >&2
|
|
80
|
+
exit 1
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
case "$computed_tier" in
|
|
84
|
+
inline|task|story) ;;
|
|
85
|
+
*) echo "Error: --computed-tier must be one of: inline, task, story (got '$computed_tier')" >&2; exit 1 ;;
|
|
86
|
+
esac
|
|
87
|
+
|
|
88
|
+
case "$est_files" in
|
|
89
|
+
''|*[!0-9]*) echo "Error: --est-files must be a non-negative integer (got '$est_files')" >&2; exit 1 ;;
|
|
90
|
+
esac
|
|
91
|
+
case "$est_lines" in
|
|
92
|
+
''|*[!0-9]*) echo "Error: --est-lines must be a non-negative integer (got '$est_lines')" >&2; exit 1 ;;
|
|
93
|
+
esac
|
|
94
|
+
|
|
95
|
+
epic_id="${story_id%%_S*}"
|
|
96
|
+
|
|
97
|
+
# Locate the parent story file. --trivial requires it to already exist.
|
|
98
|
+
story_file=""
|
|
99
|
+
for f in "${STORIES_DIR}/${story_id}_"*.md; do
|
|
100
|
+
if [ -f "$f" ]; then
|
|
101
|
+
story_file="$f"
|
|
102
|
+
break
|
|
103
|
+
fi
|
|
104
|
+
done
|
|
105
|
+
if [ -z "$story_file" ]; then
|
|
106
|
+
echo "Error: no story file found for $story_id under $STORIES_DIR/. --trivial requires an already-resolved story — create it via the normal /todo flow first." >&2
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
if [ ! -f "$UPDATE_STORY_SCRIPT" ]; then
|
|
111
|
+
echo "Error: $UPDATE_STORY_SCRIPT not found." >&2
|
|
112
|
+
exit 1
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Determine the next task number for this story by scanning existing task files.
|
|
116
|
+
max_num=0
|
|
117
|
+
shopt -s nullglob
|
|
118
|
+
for f in "${TASKS_DIR}/${story_id}_T"*.md; do
|
|
119
|
+
base="$(basename "$f")"
|
|
120
|
+
num="$(printf '%s' "$base" | sed -nE "s/^${story_id}_T([0-9]+)_.*/\1/p")"
|
|
121
|
+
if [ -n "$num" ]; then
|
|
122
|
+
num=$((10#$num))
|
|
123
|
+
if [ "$num" -gt "$max_num" ]; then
|
|
124
|
+
max_num=$num
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
done
|
|
128
|
+
shopt -u nullglob
|
|
129
|
+
next_num=$((max_num + 1))
|
|
130
|
+
next_num_padded="$(printf '%02d' "$next_num")"
|
|
131
|
+
task_id="${story_id}_T${next_num_padded}"
|
|
132
|
+
|
|
133
|
+
shopt -s nullglob
|
|
134
|
+
existing_matches=("${TASKS_DIR}/${task_id}_"*.md)
|
|
135
|
+
shopt -u nullglob
|
|
136
|
+
if [ "${#existing_matches[@]}" -gt 0 ]; then
|
|
137
|
+
echo "Error: a task file for $task_id already exists (ID collision). Aborting." >&2
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# Slugify the title for the filename.
|
|
142
|
+
slug="$(printf '%s' "$title" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')"
|
|
143
|
+
slug="${slug:0:60}"
|
|
144
|
+
slug="${slug%-}"
|
|
145
|
+
if [ -z "$slug" ]; then
|
|
146
|
+
slug="trivial-task"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
today="$(date -u +%Y-%m-%d)"
|
|
150
|
+
|
|
151
|
+
scope_rationale="forced inline via --trivial; computed scope would have been '${computed_tier}' — estimated ${est_files} files, ~${est_lines} lines"
|
|
152
|
+
override_justification="/todo --trivial invoked by user on ${today}; execution_scope forced to 'inline', overriding the heuristic's computed '${computed_tier}' tier (see scope_rationale)."
|
|
153
|
+
|
|
154
|
+
# Build the Acceptance Criteria block from the pipe-separated --criteria value.
|
|
155
|
+
criteria_block=""
|
|
156
|
+
IFS='|' read -ra crit_arr <<< "$criteria"
|
|
157
|
+
for c in "${crit_arr[@]}"; do
|
|
158
|
+
c_trimmed="$(printf '%s' "$c" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
|
|
159
|
+
if [ -n "$c_trimmed" ]; then
|
|
160
|
+
criteria_block+="- [ ] ${c_trimmed}"$'\n'
|
|
161
|
+
fi
|
|
162
|
+
done
|
|
163
|
+
if [ -z "$criteria_block" ]; then
|
|
164
|
+
echo "Error: --criteria produced no usable acceptance criteria" >&2
|
|
165
|
+
exit 1
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
task_file="${TASKS_DIR}/${task_id}_${slug}.md"
|
|
169
|
+
|
|
170
|
+
cat > "$task_file" <<EOF
|
|
171
|
+
---
|
|
172
|
+
id: ${task_id}
|
|
173
|
+
story_id: ${story_id}
|
|
174
|
+
epic_id: ${epic_id}
|
|
175
|
+
title: ${title}
|
|
176
|
+
status: Pending
|
|
177
|
+
date_created: ${today}
|
|
178
|
+
date_started:
|
|
179
|
+
date_completed:
|
|
180
|
+
dates_previously_completed:
|
|
181
|
+
reopened_on:
|
|
182
|
+
reopened_reason:
|
|
183
|
+
assigned_to: developer
|
|
184
|
+
docs: []
|
|
185
|
+
execution_scope: inline
|
|
186
|
+
needs_docs: false
|
|
187
|
+
scope_rationale: "${scope_rationale}"
|
|
188
|
+
jenga_assigned: false
|
|
189
|
+
override_justification: "${override_justification}"
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
# Task: ${title}
|
|
193
|
+
|
|
194
|
+
## Description
|
|
195
|
+
${description}
|
|
196
|
+
|
|
197
|
+
## Prerequisites
|
|
198
|
+
${prerequisites}
|
|
199
|
+
|
|
200
|
+
## Acceptance Criteria
|
|
201
|
+
${criteria_block}
|
|
202
|
+
EOF
|
|
203
|
+
|
|
204
|
+
echo "Wrote task file: ${task_file}"
|
|
205
|
+
|
|
206
|
+
# Register the new task under its parent story's tasks: frontmatter list.
|
|
207
|
+
if ! scripts/with-lock.sh "$story_file" -- python3 "$UPDATE_STORY_SCRIPT" "$story_file" "$task_id"; then
|
|
208
|
+
echo "Error: failed to register ${task_id} in ${story_file}'s tasks: list (lock timeout or write failure)." >&2
|
|
209
|
+
exit 1
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# Register the todo entry using the FULL task ID, so /do routes straight into
|
|
213
|
+
# its Inline Execution Path without a redundant breakdown pass.
|
|
214
|
+
bash scripts/todo_manager.sh add "${title}: ${task_id}"
|
|
215
|
+
|
|
216
|
+
echo "${task_id}"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
skills/todo/scripts/update_story_tasks.py
|
|
4
|
+
|
|
5
|
+
Mechanically inserts a task ID into a story board file's `tasks:` frontmatter
|
|
6
|
+
list, if it is not already present. Idempotent (a re-run with the same
|
|
7
|
+
task_id is a no-op, exit 0).
|
|
8
|
+
|
|
9
|
+
This is a pure text/frontmatter edit and does no board-wide validation beyond
|
|
10
|
+
"frontmatter block exists" — it is intended to be invoked already wrapped in
|
|
11
|
+
scripts/with-lock.sh, keyed to the story file, per templates/SCRUM_BOARD_SCHEMA.md's
|
|
12
|
+
File Locking section (board frontmatter writes must go through the lock wrapper).
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
python3 skills/todo/scripts/update_story_tasks.py <story-file> <task-id>
|
|
16
|
+
|
|
17
|
+
Exit codes:
|
|
18
|
+
0 success (task_id inserted, or already present)
|
|
19
|
+
1 usage error
|
|
20
|
+
2 story file has no parseable frontmatter block
|
|
21
|
+
"""
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main() -> int:
|
|
26
|
+
if len(sys.argv) != 3:
|
|
27
|
+
print("Usage: update_story_tasks.py <story-file> <task-id>", file=sys.stderr)
|
|
28
|
+
return 1
|
|
29
|
+
|
|
30
|
+
path, task_id = sys.argv[1], sys.argv[2]
|
|
31
|
+
|
|
32
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
33
|
+
content = f.read()
|
|
34
|
+
|
|
35
|
+
parts = content.split("---", 2)
|
|
36
|
+
if len(parts) < 3:
|
|
37
|
+
print(f"Error: {path} has no valid '---' frontmatter block", file=sys.stderr)
|
|
38
|
+
return 2
|
|
39
|
+
|
|
40
|
+
_, frontmatter, rest = parts
|
|
41
|
+
lines = frontmatter.split("\n")
|
|
42
|
+
task_line = f" - {task_id}"
|
|
43
|
+
|
|
44
|
+
if any(line.strip() == task_line.strip() for line in lines):
|
|
45
|
+
print(f"No-op: {task_id} already present in {path}'s tasks: list")
|
|
46
|
+
return 0
|
|
47
|
+
|
|
48
|
+
idx = None
|
|
49
|
+
inline_empty_idx = None
|
|
50
|
+
for i, line in enumerate(lines):
|
|
51
|
+
stripped = line.strip()
|
|
52
|
+
if stripped == "tasks:":
|
|
53
|
+
idx = i
|
|
54
|
+
break
|
|
55
|
+
if stripped == "tasks: []":
|
|
56
|
+
inline_empty_idx = i
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
if inline_empty_idx is not None:
|
|
60
|
+
lines[inline_empty_idx] = "tasks:"
|
|
61
|
+
lines.insert(inline_empty_idx + 1, task_line)
|
|
62
|
+
elif idx is not None:
|
|
63
|
+
j = idx + 1
|
|
64
|
+
while j < len(lines) and lines[j].strip().startswith("- "):
|
|
65
|
+
j += 1
|
|
66
|
+
lines.insert(j, task_line)
|
|
67
|
+
else:
|
|
68
|
+
# No tasks: key found at all — append one at the end of the frontmatter,
|
|
69
|
+
# trimming any single trailing blank line first so formatting stays tidy.
|
|
70
|
+
if lines and lines[-1].strip() == "":
|
|
71
|
+
lines.pop()
|
|
72
|
+
lines.append("tasks:")
|
|
73
|
+
lines.append(task_line)
|
|
74
|
+
lines.append("")
|
|
75
|
+
|
|
76
|
+
new_frontmatter = "\n".join(lines)
|
|
77
|
+
new_content = "---" + new_frontmatter + "---" + rest
|
|
78
|
+
|
|
79
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
80
|
+
f.write(new_content)
|
|
81
|
+
|
|
82
|
+
print(f"Updated {path}: added {task_id} to tasks:")
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
sys.exit(main())
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: uncharted
|
|
2
|
+
name: j:uncharted
|
|
3
3
|
description: Investigate code that has no Jenga board provenance — a foreign file, an external source being pulled in, or an entire pre-existing codebase — and give it a consistent understanding document plus proper board representation.
|
|
4
4
|
metadata:
|
|
5
5
|
prefered_agent: scrum-master
|
package/skills/wtf/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: wtf
|
|
2
|
+
name: j:wtf
|
|
3
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
|
|
@@ -67,9 +67,28 @@ All status fields must use one of the following exact strings:
|
|
|
67
67
|
| `Blocked` | Cannot proceed; human intervention required |
|
|
68
68
|
| `Backlog` | Epic-level only; queued but not yet prioritized for work |
|
|
69
69
|
| `Done` | Epic-level only; all child stories/tasks closed out |
|
|
70
|
+
| `Merged` | Set after a successful `/self-sync` run's file diff shows the ticket's recorded files were touched |
|
|
71
|
+
| `Publicized` | Set after a successful `/mirror-public` run's file diff shows the ticket's recorded files were touched |
|
|
72
|
+
| `Privatized` | Set at ticket-close time via a static `.publicignore` blocklist membership check (no run dependency) |
|
|
73
|
+
| `Deployed to Stage` | Set when the public `jenga-npm` repo's CI tags a `vX.Y.Z-stage` tag that resolves back (via the `Source-Commit:` trailer) to this ticket's commit |
|
|
74
|
+
| `Deployed to Prod` | Set when the public `jenga-npm` repo's CI tags a `vX.Y.Z` (prod) tag that resolves back to this ticket's commit |
|
|
70
75
|
|
|
71
76
|
Only the **tester agent** may write status values to story and task files. Only the **scrum master** may write status values to epic files and may update story status as part of rollup.
|
|
72
77
|
|
|
78
|
+
All five statuses above are **script-set, never agent-judged** — no agent decides when a ticket becomes `Merged`, `Publicized`, `Privatized`, `Deployed to Stage`, or `Deployed to Prod`; a deterministic script observation sets them, per the mechanisms described below.
|
|
79
|
+
|
|
80
|
+
### Static vs. Reactive Status Setting
|
|
81
|
+
|
|
82
|
+
`Privatized` is set **statically**: at ticket-close time, a script checks whether the ticket's recorded files match the `.publicignore` blocklist. This check has no dependency on any particular run having occurred — it is a pure membership test.
|
|
83
|
+
|
|
84
|
+
The other four — `Merged`, `Publicized`, `Deployed to Stage`, `Deployed to Prod` — are set **reactively**: a script observes the outcome of a specific run (a `/self-sync` or `/mirror-public` file diff, or a public-repo CI tag event) and sets the status only when that run's evidence confirms the ticket was affected. Absent a qualifying run, the status is not set.
|
|
85
|
+
|
|
86
|
+
### Publicized / Privatized / Deployed Lifecycle Relationship
|
|
87
|
+
|
|
88
|
+
`Publicized` and `Privatized` are **mutually exclusive** — a ticket is one or the other, never both. A ticket's files either pass the `.publicignore` blocklist check (making it eligible for `Publicized`) or match it (making it `Privatized`); it cannot satisfy both conditions at once.
|
|
89
|
+
|
|
90
|
+
Only **`Publicized`** tickets are eligible to progress further down the deploy lifecycle, from `Deployed to Stage` to `Deployed to Prod`. A `Privatized` ticket's files never reach the public `jenga-npm` repo, so it can never acquire a CI tag there and therefore can never reach either deploy status.
|
|
91
|
+
|
|
73
92
|
---
|
|
74
93
|
|
|
75
94
|
## File Formats
|
|
@@ -161,7 +180,7 @@ reopened_on: # comma-separated list, e.g. 2026-02-01, 2026-04-10
|
|
|
161
180
|
reopened_reason: # comma-separated list, e.g. "Scope expanded", "Bug found post-release"
|
|
162
181
|
assigned_to: developer | tester | scrum-master
|
|
163
182
|
docs: [] # optional list of repo-relative documentation paths, e.g. ["README.md", "docs/API.md"]
|
|
164
|
-
execution_scope: task # task | story | epic | inline; omit for legacy tasks (defaults to task)
|
|
183
|
+
execution_scope: task # task | story | epic | inline | light; omit for legacy tasks (defaults to task)
|
|
165
184
|
needs_docs: true # boolean; omit for legacy tasks (defaults to true)
|
|
166
185
|
scope_rationale: "" # required when execution_scope is set; must contain a numeric/file-count claim
|
|
167
186
|
jenga_assigned: true # boolean; true = machine-assigned, false = human override
|
|
@@ -315,13 +334,14 @@ level.
|
|
|
315
334
|
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`.
|
|
316
335
|
|
|
317
336
|
**`execution_scope`**
|
|
318
|
-
- Valid values: `task` | `story` | `epic` | `inline`
|
|
337
|
+
- Valid values: `task` | `story` | `epic` | `inline` | `light`
|
|
319
338
|
- When required: optional; omit for legacy tasks (runtime default: `task`)
|
|
320
339
|
- Description: defines how broadly this task's implementation touches the codebase.
|
|
321
340
|
- `task` — standard single-task scope (default)
|
|
322
341
|
- `story` — task may touch files across multiple tasks in the same story
|
|
323
342
|
- `epic` — task may touch files across stories; requires `epic_scope_approval: true` on the parent epic
|
|
324
343
|
- `inline` — trivial change (e.g. config tweak, comment, schema doc); no execution plan or summary document is needed
|
|
344
|
+
- `light` — sits between `inline` and `task` in scope: a single developer subagent pass with no worktree, self-verified via `scripts/smoke-harness.sh` in lieu of a separate tester invocation; if the smoke harness fails, execution falls back to `task` scope
|
|
325
345
|
|
|
326
346
|
**`needs_docs`**
|
|
327
347
|
- Valid values: `true` | `false`
|
|
@@ -528,6 +548,7 @@ Each file is written by an agent as the **last action** of its session, and is s
|
|
|
528
548
|
| `worktree` | developer, tester | Absolute path |
|
|
529
549
|
| `paths` | developer, tester | Commit SHAs |
|
|
530
550
|
| `rapport_file`| tester only | Path to rapport if status is failed/error |
|
|
551
|
+
| `resolved_context` | all, optional | Digest of context the sending agent already resolved; see below |
|
|
531
552
|
| `date` | all | ISO 8601 UTC |
|
|
532
553
|
|
|
533
554
|
**Status values per agent:**
|
|
@@ -535,6 +556,16 @@ Each file is written by an agent as the **last action** of its session, and is s
|
|
|
535
556
|
- `developer`: `implementation_complete`
|
|
536
557
|
- `tester`: `passed`, `passed_with_remarks`, `failed`, `error`
|
|
537
558
|
|
|
559
|
+
**`resolved_context` — digest, not a dump (E49).** An optional field a sending agent populates with a short digest of conclusions it already reached while navigating source documents (e.g. which schema fields apply, which skill precedent governs, which decisions are already made) — so the receiving subagent doesn't have to cold-re-read the same files from scratch. It must stay under a size cap of roughly 100 lines (a few hundred tokens), mirroring `scope_rationale`'s "must contain a measurable claim" discipline: a `resolved_context` value that is a raw file dump or exceeds the cap is not valid. The digest is a starting point only — it never restricts the receiving agent from reading full source files when the digest is insufficient or needs verification. The digest body itself lives in a per-task file at `project/queue/context/<agent>-<session_id>-<task_id>.json`, following the same unique-path, single-use, session-scoped convention as `handoffs/` above (not a shared, clobber-prone slot); this handoff's `resolved_context` field holds a reference to (or the inline content of) that file.
|
|
560
|
+
|
|
561
|
+
**`project/queue/context/` — physical digest files (E49_S01_T02).** The directory itself is kept via `.gitkeep`; individual digest files (`*.json`) are git-ignored for the same reason `handoffs/*.json` is — a committed one can no longer be told apart from a live pending digest by inspection alone. Three scripts implement the convention end to end:
|
|
562
|
+
|
|
563
|
+
- `scripts/write-context-digest.sh` — the sending agent's write path. Takes `--agent`, `--session-id`, `--task-id`, and digest content (`--content`, `--content-file`, or stdin); enforces the ~100-line cap above by **rejecting** (not truncating) an oversized digest, since a silently-truncated digest could cut off mid-thought and mislead the receiver — the sender is the only party that actually knows what's safe to cut. Writes atomically (tmp file in the same directory, then `mv`) and prints the resulting absolute path to stdout for the caller to place in the handoff's `resolved_context` field.
|
|
564
|
+
- `scripts/consume-context-digest.sh <path>` — the receiving agent's read path. Atomically claims the file (rename to a `.claimed.$$` sibling, same TOCTOU-safe pattern `on_session_end.sh` section 4 uses for `handoffs/`), prints its content (full JSON envelope, or just the `digest` field with `--raw`), and deletes it — single-use, like `handoffs/`.
|
|
565
|
+
- `scripts/sweep-stale-context-digests.sh` — an age-based backstop (default 24h, overridable), invoked from `hooks/on_session_end.sh` on every session end regardless of agent, for a digest whose intended receiver never calls the consume script (abandoned dispatch, or a receiver that read the raw file directly and forgot to clean up). Age-based rather than routed-and-deleted-immediately like `handoffs/`, because a digest's consumer is a later session that may not have started yet when some unrelated session's `SessionEnd` hook fires.
|
|
566
|
+
|
|
567
|
+
Populating `resolved_context` when dispatching (scrum-master → developer, developer → tester) is sibling task E49_S01_T03 — not yet wired into `agents/scrum-master.md` or `agents/developer.md` as of this writing. The physical convention above is usable standalone in the meantime.
|
|
568
|
+
|
|
538
569
|
|
|
539
570
|
|
|
540
571
|
Located at `project/configs/workflow.json`. Scaffolded by `/init` and owned by the scrum master.
|
|
@@ -10,20 +10,30 @@ This project uses **Jenga** — a skill-based AI agent framework. Jenga organise
|
|
|
10
10
|
### How Jenga Works
|
|
11
11
|
|
|
12
12
|
- Each **skill** is a self-contained instruction set stored under `{{SKILL_DISCOVERY_PATH}}<skill-name>/`.
|
|
13
|
-
- Skills are invoked by typing
|
|
13
|
+
- Skills are invoked by typing `j:skill-name` in the chat prompt (e.g. `j:status`, `j:commit`). The
|
|
14
|
+
older bare `/skill-name` form (e.g. `/status`, `/commit`) is a **permanent alias** — it keeps
|
|
15
|
+
resolving indefinitely, with no deprecation warning and no removal planned — so treat a message in
|
|
16
|
+
either form as the exact same invocation.
|
|
14
17
|
- The active project directory is available via the `JENGA_PROJECT_DIR` environment variable. **Use `JENGA_PROJECT_DIR` — not `CLAUDE_PROJECT_DIR` or any other agent-specific variable** — as the canonical path to the project folder.
|
|
15
18
|
|
|
16
19
|
### Skill Routing
|
|
17
20
|
|
|
18
|
-
If you are Claude Code,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
If you are Claude Code, both `j:skill-name` (the canonical form) and the older bare `/skill-name`
|
|
22
|
+
(a permanent alias) are native harness-level mechanisms: the harness itself intercepts the literal
|
|
23
|
+
command and loads the skill for you, independent of anything written here. If you are any other agent
|
|
24
|
+
(Codex, or a generic `AGENTS.md` consumer) with no equivalent native interception, you depend entirely
|
|
25
|
+
on the instructions below to know what "invoking a skill" concretely means — for either the
|
|
26
|
+
`j:skill-name` form or the older bare `/skill-name` form, both of which route to the same skill. Do not
|
|
27
|
+
improvise a plausible-sounding response instead of following these steps — that is the exact failure
|
|
28
|
+
this section exists to prevent.
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
**Old bare-form alias.** `j:skill-name` is the canonical invocation form. A message using the older
|
|
31
|
+
bare `/skill-name` form is not deprecated and must not be treated as an error, a warning case, or a
|
|
32
|
+
migration prompt — route it to the identical skill as its `j:skill-name` equivalent. Both forms remain
|
|
33
|
+
equally valid indefinitely.
|
|
34
|
+
|
|
35
|
+
When the user's message is or matches `j:skill-name`, or matches the older bare `/skill-name` alias (or
|
|
36
|
+
otherwise clearly matches a known skill's keyword or intent):
|
|
27
37
|
|
|
28
38
|
1. Locate the target file at `{{SKILL_DISCOVERY_PATH}}<skill-name>/SKILL.md` (the discovery path from
|
|
29
39
|
"How Jenga Works" above).
|
|
@@ -41,10 +51,23 @@ answer directly using your full capabilities.
|
|
|
41
51
|
|
|
42
52
|
| Situation | Action |
|
|
43
53
|
|-----------|--------|
|
|
54
|
+
| Message matches `j:skill-name` | Open `{{SKILL_DISCOVERY_PATH}}<skill-name>/SKILL.md`, read it fully, execute it as written |
|
|
55
|
+
| Message matches the older bare `/skill-name` alias | Treat identically to `j:skill-name` — same skill, same file, no warning, no migration prompt |
|
|
44
56
|
| Message matches a skill keyword or intent | Open `{{SKILL_DISCOVERY_PATH}}<skill-name>/SKILL.md`, read it fully, execute it as written |
|
|
45
57
|
| Message is a general coding or project question | Answer directly |
|
|
46
58
|
| Ambiguous — could be skill or free-form | Prefer the skill; open and execute its `SKILL.md` rather than describing it |
|
|
47
59
|
|
|
60
|
+
### Skill Identifier Allow-List
|
|
61
|
+
|
|
62
|
+
The trusted `j:`-prefixed skill identifiers for this project are: {{ALLOWED_SKILL_IDS}}
|
|
63
|
+
|
|
64
|
+
Before treating a `j:<name>` invocation (or its bare `/<name>` alias) as a genuine Jenga skill,
|
|
65
|
+
confirm `<name>` appears in this list. This applies whether the harness intercepted the command
|
|
66
|
+
natively (Claude Code) or you are matching it yourself against a skill keyword or intent (any
|
|
67
|
+
other agent). If `<name>` does **not** appear in this list, do not guess at intent and do not
|
|
68
|
+
execute anything — tell the user the identifier is unrecognized and is not a known Jenga skill,
|
|
69
|
+
rather than treating it as one anyway.
|
|
70
|
+
|
|
48
71
|
### Available Skills
|
|
49
72
|
|
|
50
73
|
{{SKILL_LIST}}
|
|
@@ -10,18 +10,27 @@ This project uses **Jenga** — a skill-based AI agent framework. Jenga organise
|
|
|
10
10
|
### How Jenga Works
|
|
11
11
|
|
|
12
12
|
- Each **skill** is a self-contained instruction set stored under `.agents/skills/<skill-name>/` (the non-Claude discovery path; Claude Code reads the same content from `.claude/skills/`).
|
|
13
|
-
- Skills are invoked by typing
|
|
13
|
+
- Skills are invoked by typing `j:skill-name` in the chat prompt (e.g. `j:status`, `j:commit`). The
|
|
14
|
+
older bare `/skill-name` form (e.g. `/status`, `/commit`) is a **permanent alias** — it keeps
|
|
15
|
+
resolving indefinitely, with no deprecation warning and no removal planned — so treat a message in
|
|
16
|
+
either form as the exact same invocation.
|
|
14
17
|
- The active project directory is available via the `JENGA_PROJECT_DIR` environment variable. **Use `JENGA_PROJECT_DIR` — not `CLAUDE_PROJECT_DIR` or any other agent-specific variable** — as the canonical path to the project folder.
|
|
15
18
|
|
|
16
19
|
### Skill Routing
|
|
17
20
|
|
|
18
|
-
Unlike Claude Code, GitHub Copilot has **no native slash-command interception** — typing
|
|
19
|
-
does not automatically load or run anything on its own. Copilot
|
|
20
|
-
below to know what "invoking a skill" concretely means. Do not
|
|
21
|
-
instead of following these steps — that is the exact failure
|
|
21
|
+
Unlike Claude Code, GitHub Copilot has **no native slash-command interception** — typing `j:skill-name`
|
|
22
|
+
(or the older bare `/skill-name` alias) does not automatically load or run anything on its own. Copilot
|
|
23
|
+
depends entirely on the instructions below to know what "invoking a skill" concretely means. Do not
|
|
24
|
+
improvise a plausible-sounding response instead of following these steps — that is the exact failure
|
|
25
|
+
this section exists to prevent.
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
**Old bare-form alias.** `j:skill-name` is the canonical invocation form. A message using the older
|
|
28
|
+
bare `/skill-name` form is not deprecated and must not be treated as an error, a warning case, or a
|
|
29
|
+
migration prompt — route it to the identical skill as its `j:skill-name` equivalent. Both forms remain
|
|
30
|
+
equally valid indefinitely.
|
|
31
|
+
|
|
32
|
+
When the user's message is or matches `j:skill-name`, or matches the older bare `/skill-name` alias (or
|
|
33
|
+
otherwise clearly matches a known skill's keyword or intent):
|
|
25
34
|
|
|
26
35
|
1. Locate the target file at `.agents/skills/<skill-name>/SKILL.md` (the discovery path from "How
|
|
27
36
|
Jenga Works" above).
|
|
@@ -37,9 +46,17 @@ answer directly using your full capabilities.
|
|
|
37
46
|
|
|
38
47
|
#### Routing decision table
|
|
39
48
|
|
|
49
|
+
Before acting on any row below that opens a `SKILL.md` file, first check the identifier against
|
|
50
|
+
the trusted allow-list: {{ALLOWED_SKILL_IDS}}. Since Copilot/Codex has no native interception for
|
|
51
|
+
`j:skill-name`, this prose-level check is the entire enforcement mechanism — there is no runtime
|
|
52
|
+
guard behind it.
|
|
53
|
+
|
|
40
54
|
| Situation | Action |
|
|
41
55
|
|-----------|--------|
|
|
42
|
-
| Message matches
|
|
56
|
+
| Message matches `j:skill-name` and `skill-name` is in the allow-list | Open `.agents/skills/<skill-name>/SKILL.md`, read it fully, execute it as written |
|
|
57
|
+
| Message matches the older bare `/skill-name` alias and `skill-name` is in the allow-list | Treat identically to `j:skill-name` — same skill, same file, no warning, no migration prompt |
|
|
58
|
+
| Message matches a skill keyword or intent and the matched skill is in the allow-list | Open `.agents/skills/<skill-name>/SKILL.md`, read it fully, execute it as written |
|
|
59
|
+
| Message matches `j:skill-name` or `/skill-name`, but `skill-name` is **not** in the allow-list | Do not open or execute anything — tell the user the identifier is unrecognized and is not a known Jenga skill |
|
|
43
60
|
| Message is a general coding or project question | Answer directly |
|
|
44
61
|
| Ambiguous — could be skill or free-form | Prefer the skill; open and execute its `SKILL.md` rather than describing it |
|
|
45
62
|
|