@jenga-ai/agent 1.2.4 → 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 -91
- package/agents/developer.md +26 -7
- package/agents/scrum-master.md +57 -22
- package/agents/tester.md +68 -4
- package/hooks/on_session_end.sh +40 -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 +35 -20
- 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 +12 -2
- package/skills/continue/SKILL.md +1 -1
- package/skills/deep-dive/SKILL.md +1 -1
- package/skills/dev-done/SKILL.md +46 -0
- package/skills/dev-done/scripts/classify-commit-outcome.sh +114 -0
- 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 +8 -7
- package/skills/init/assets/scope-thresholds_template.json +7 -0
- package/skills/init/scripts/init.sh +6 -0
- 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 +67 -1
- package/skills/publish/adapters/npm-ci.md +60 -4
- package/skills/publish/adapters/npm.md +18 -0
- package/skills/publish/assets/ci-contract.md +27 -0
- package/skills/publish/assets/publish.example.json +27 -0
- package/skills/publish/schemas/publish.schema.json +20 -0
- package/skills/publish/scripts/npm_ci_pipeline.sh +50 -1
- package/skills/publish/scripts/npm_stage_inspect.sh +829 -0
- package/skills/publish/scripts/npm_stage_pipeline.sh +427 -0
- package/skills/publish/scripts/publish_common.sh +16 -0
- package/skills/publish/scripts/show_history.sh +12 -5
- package/skills/publish/scripts/validate_npm_stage_env.sh +184 -0
- package/skills/publish/scripts/write_ledger_entry.sh +92 -2
- package/skills/reconcile/SKILL.md +122 -12
- package/skills/reconcile/assets/report_format.md +17 -0
- package/skills/reconcile/scripts/resolve-reconcile-scope.sh +489 -0
- 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 +201 -22
- package/skills/uncharted/scripts/directory-triage.sh +342 -0
- package/skills/uncharted/scripts/elicitation-state.sh +457 -0
- package/skills/wtf/SKILL.md +1 -1
- package/templates/SCRUM_BOARD_SCHEMA.md +90 -2
- package/templates/agent-context.md.tpl +47 -12
- package/templates/copilot-instructions.md.tpl +36 -9
- package/mcp/router/README.md +0 -19
- package/mcp/router/embedder.js +0 -23
- package/mcp/router/index.js +0 -204
- package/mcp/router/matcher.js +0 -87
- package/mcp/router/package-lock.json +0 -1048
- package/mcp/router/package.json +0 -11
- package/mcp/router/skill-index.js +0 -104
- package/skills/route/SKILL.md +0 -180
|
@@ -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
|
package/skills/commit/SKILL.md
CHANGED
|
@@ -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
|
|
@@ -36,7 +36,17 @@ If `--inline` is absent **and** `JENGA_COMMIT_INLINE` is not set (or is not `1`)
|
|
|
36
36
|
|
|
37
37
|
If no epic, task, or story has been implemented, exit with the message: "No implementation to commit."
|
|
38
38
|
|
|
39
|
-
1. **Reconcile first** — Invoke the `/reconcile` skill before any other action, so the board is never committed in a drifted state.
|
|
39
|
+
1. **Reconcile first, scoped to this commit** — Invoke the `/reconcile` skill before any other action, so the board is never committed in a drifted state.
|
|
40
|
+
- **Determine the scope to pass** (in order):
|
|
41
|
+
1. If `/commit` was invoked with an explicit epic/story/task id argument (e.g. `/commit E46`, `/commit E17_S04_T02`), use that id.
|
|
42
|
+
2. Otherwise, if the calling context already identifies a single task/story/epic just completed (e.g. a developer or tester agent's sender object naming `task_id`/`story_id`/`epic_id`, or a `/do` invocation for one task), use that id.
|
|
43
|
+
3. Otherwise, derive it from what's staged: run `git diff --cached --name-only` and filter to paths under `project/board/epics/`, `project/board/stories/`, and `project/board/tasks/`. Extract the id each matched filename encodes.
|
|
44
|
+
- If every extracted id shares a single common story-or-narrower ancestor (all belong to one story's own file plus any of its own tasks' files, or are all exactly one task's own file, or are all exactly one epic's own file with nothing narrower staged), use that single most-specific id (story/task id if present, else the epic id) as the scope.
|
|
45
|
+
- If matched ids span more than one distinct story, or more than one distinct epic, this case does not resolve — do not guess between them; fall through to case 4.
|
|
46
|
+
- **If no board files at all are staged, skip `/reconcile` entirely — do not fall through to case 4.** A commit that touches no board file cannot itself commit board drift, so there is nothing for a pre-commit reconcile to protect against; running a full unscoped scan here would be pure waste, not a safety net. This is distinct from the multi-item case immediately above, which still falls through to case 4 since board files genuinely are changing there.
|
|
47
|
+
4. Otherwise — no argument, no sender context, and staged board files span more than one distinct story or epic — fall back to unscoped `/reconcile` (full board), unchanged from prior behavior.
|
|
48
|
+
- **Invoke** `/reconcile <scope>` when a scope was determined in cases 1-3, unscoped `/reconcile` on the case-4 fallback, or skip the reconcile step entirely per case 3's no-board-files-staged outcome. Do not re-derive or duplicate `/reconcile`'s own default-scope-to-epic expansion here — passing a bare story or task id through is sufficient; `/reconcile` itself resolves that to the containing epic.
|
|
49
|
+
- **If `/reconcile` was skipped** (case 3's no-board-files-staged outcome) — continue silently to the next step, exactly as the no-drift outcome below.
|
|
40
50
|
- **If reconcile detects and corrects drift** — inform the user what changed (e.g. demoted/promoted statuses, merged orphaned worktrees, cleaned `todo.md` entries) before proceeding.
|
|
41
51
|
- **If reconcile finds no drift** — continue silently to the next step.
|
|
42
52
|
|
package/skills/continue/SKILL.md
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: j:dev-done
|
|
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
|
+
keywords:
|
|
5
|
+
- dev done
|
|
6
|
+
- commit and sync
|
|
7
|
+
- commit and mirror
|
|
8
|
+
- done syncing
|
|
9
|
+
examples:
|
|
10
|
+
- "dev-done E42_S04_T01"
|
|
11
|
+
- "commit this and sync the mirrors"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Dev-Done — Commit, then Sync the Mirrors
|
|
15
|
+
|
|
16
|
+
Chains `/commit <scope-id>` and `/self-sync`, the same "convenience shortcut" pattern `skills/lgtm/SKILL.md`
|
|
17
|
+
uses for `/commit` + `/continue` — applied here to the commit -> mirror-sync sequence instead of the
|
|
18
|
+
commit -> next-task sequence. Useful right after implementing a root-level framework change
|
|
19
|
+
(`skills/`, `agents/`, `hooks/`, `scripts/`, `templates/`, `settings.json`), so the `.claude/` and
|
|
20
|
+
`.agents/` mirrors never sit stale waiting on a manual `/self-sync` call.
|
|
21
|
+
|
|
22
|
+
The deterministic decision of whether `/commit` halted early (nothing to commit) or completed
|
|
23
|
+
normally lives in `skills/dev-done/scripts/classify-commit-outcome.sh`, not inline here — see that
|
|
24
|
+
script's header for the exact contract.
|
|
25
|
+
|
|
26
|
+
## Instructions
|
|
27
|
+
|
|
28
|
+
1. Invoke the `/commit` skill with whatever scope-id argument `/dev-done` itself was given (e.g.
|
|
29
|
+
`/dev-done E42_S04_T01` invokes `/commit E42_S04_T01`) — the same EST scope-id argument contract
|
|
30
|
+
`/commit` already accepts (epic, story, or task id; see `skills/commit/SKILL.md`). Capture its full
|
|
31
|
+
output text.
|
|
32
|
+
|
|
33
|
+
2. Pass the captured output to the classifier script:
|
|
34
|
+
```
|
|
35
|
+
skills/dev-done/scripts/classify-commit-outcome.sh <<< "$COMMIT_OUTPUT"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
3. If the script exits `1` (HALT): print its stdout — the exact message `No implementation to
|
|
39
|
+
commit.` — to the user, and stop. Do **not** invoke `/self-sync`.
|
|
40
|
+
|
|
41
|
+
4. If the script exits `0` (PROCEED): invoke the `/self-sync` skill and wait for it to finish. This
|
|
42
|
+
happens regardless of whether `/commit` reported drift or doc-sync findings along the way — those
|
|
43
|
+
are informational, not blocking (see `skills/commit/SKILL.md`).
|
|
44
|
+
|
|
45
|
+
5. Report both steps' output to the user in sequence — the commit result first, then the self-sync
|
|
46
|
+
summary.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ---------------------------------------------------------------------------
|
|
3
|
+
# skills/dev-done/scripts/classify-commit-outcome.sh
|
|
4
|
+
#
|
|
5
|
+
# Deterministic classifier backing `/dev-done` (story E42_S04, task
|
|
6
|
+
# E42_S04_T01). `/dev-done` chains `/commit <scope-id>` then `/self-sync`,
|
|
7
|
+
# but must NOT proceed to `/self-sync` when `/commit` halted early because
|
|
8
|
+
# there was nothing to commit. Per CLAUDE.md's "Skill Implementation
|
|
9
|
+
# Principle — Scripts Over Inline Logic", that halt-vs-proceed check is a
|
|
10
|
+
# deterministic, repeatable text match — it does not belong as inline
|
|
11
|
+
# conditional logic in `skills/dev-done/SKILL.md`, so it lives here instead,
|
|
12
|
+
# the same way `skills/self-sync/SKILL.md` delegates its own filesystem work
|
|
13
|
+
# to `skills/self-sync/scripts/run.js` rather than inlining it.
|
|
14
|
+
#
|
|
15
|
+
# `/commit` and `/self-sync` are themselves agent-executed skills, not plain
|
|
16
|
+
# executables — this script never shells out to invoke either one. It only
|
|
17
|
+
# classifies text that the calling agent already captured from `/commit`'s
|
|
18
|
+
# output. Invoking `/commit` and `/self-sync` remains `SKILL.md`'s job.
|
|
19
|
+
#
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# WHAT IT MATCHES
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# `skills/commit/SKILL.md`'s Instructions section states, verbatim:
|
|
24
|
+
#
|
|
25
|
+
# "If no epic, task, or story has been implemented, exit with the message:
|
|
26
|
+
# "No implementation to commit.""
|
|
27
|
+
#
|
|
28
|
+
# That exact sentence — "No implementation to commit." — is the ONLY halt
|
|
29
|
+
# signal this script recognizes. If a future edit to `skills/commit/SKILL.md`
|
|
30
|
+
# changes that wording, this script's match must be updated to match, or it
|
|
31
|
+
# will silently stop recognizing the halt case (see Dependencies & Risks in
|
|
32
|
+
# the task's execution plan).
|
|
33
|
+
#
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# INVOCATION
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# classify-commit-outcome.sh [<captured-commit-output>]
|
|
38
|
+
#
|
|
39
|
+
# The text `/commit` produced is passed either as the single argument, or
|
|
40
|
+
# (when no argument is given) read from stdin in full, e.g.:
|
|
41
|
+
#
|
|
42
|
+
# skills/dev-done/scripts/classify-commit-outcome.sh <<< "$COMMIT_OUTPUT"
|
|
43
|
+
# printf '%s' "$COMMIT_OUTPUT" | skills/dev-done/scripts/classify-commit-outcome.sh
|
|
44
|
+
#
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# OUTPUT CONTRACT (stdout, single line, nothing else)
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# HALT case stdout is the exact literal message "No implementation to
|
|
49
|
+
# commit." — the calling agent relays this stdout verbatim
|
|
50
|
+
# to the user and does NOT invoke `/self-sync`.
|
|
51
|
+
# PROCEED case stdout is the literal token "PROCEED" — the calling
|
|
52
|
+
# agent invokes `/self-sync` next.
|
|
53
|
+
#
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# EXIT CODES
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# 0 PROCEED — `/commit` completed normally (regardless of whether it
|
|
58
|
+
# reported drift or doc-sync findings along the way); stdout is
|
|
59
|
+
# "PROCEED".
|
|
60
|
+
# 1 HALT — `/commit` halted early with the exact "No implementation to
|
|
61
|
+
# commit." message; stdout is that exact message.
|
|
62
|
+
# 2 Usage/environment error (e.g. no input at all — neither an argument
|
|
63
|
+
# nor anything on stdin); nothing meaningful on stdout, an error on
|
|
64
|
+
# stderr.
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
set -euo pipefail
|
|
68
|
+
|
|
69
|
+
HALT_MESSAGE="No implementation to commit."
|
|
70
|
+
|
|
71
|
+
if [ "$#" -gt 1 ]; then
|
|
72
|
+
echo "Usage: $(basename "$0") [<captured-commit-output>]" >&2
|
|
73
|
+
echo " (or pipe the captured output on stdin with no argument)" >&2
|
|
74
|
+
exit 2
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [ "$#" -eq 1 ]; then
|
|
78
|
+
INPUT="$1"
|
|
79
|
+
else
|
|
80
|
+
# No argument — read the full captured output from stdin.
|
|
81
|
+
if [ -t 0 ]; then
|
|
82
|
+
echo "ERROR: no argument given and stdin is a terminal — nothing to classify" >&2
|
|
83
|
+
exit 2
|
|
84
|
+
fi
|
|
85
|
+
INPUT="$(cat)"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [ -z "$INPUT" ]; then
|
|
89
|
+
echo "ERROR: no commit output provided to classify" >&2
|
|
90
|
+
exit 2
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Trim leading/trailing whitespace from the whole input for the exact-match case.
|
|
94
|
+
TRIMMED="$(printf '%s' "$INPUT" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
95
|
+
|
|
96
|
+
if [ "$TRIMMED" = "$HALT_MESSAGE" ]; then
|
|
97
|
+
printf '%s\n' "$HALT_MESSAGE"
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Also match the halt message appearing as its own standalone line anywhere
|
|
102
|
+
# in a longer captured response (e.g. wrapped in surrounding agent prose),
|
|
103
|
+
# since the calling agent's captured "output text" is free-form, not a
|
|
104
|
+
# guaranteed exact-equals string.
|
|
105
|
+
while IFS= read -r line; do
|
|
106
|
+
LINE_TRIMMED="$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
107
|
+
if [ "$LINE_TRIMMED" = "$HALT_MESSAGE" ]; then
|
|
108
|
+
printf '%s\n' "$HALT_MESSAGE"
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
done <<< "$INPUT"
|
|
112
|
+
|
|
113
|
+
printf 'PROCEED\n'
|
|
114
|
+
exit 0
|
|
@@ -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
|