@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,166 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ---------------------------------------------------------------------------
|
|
3
|
+
# skills/init/scripts/detect-existing-codebase.sh
|
|
4
|
+
#
|
|
5
|
+
# Deterministic front half of the /init "detect existing project state" step.
|
|
6
|
+
# /init currently always scaffolds as though the target directory were empty.
|
|
7
|
+
# That is wrong whenever a directory already carries a Jenga scaffold, or
|
|
8
|
+
# already carries substantial application code that predates Jenga entirely
|
|
9
|
+
# -- in the second case, scaffolding fresh produces an empty board and a stub
|
|
10
|
+
# PROJECT_SUMMARY.md that actively misrepresent the project.
|
|
11
|
+
#
|
|
12
|
+
# This script answers exactly one question -- "what kind of directory is
|
|
13
|
+
# this?" -- and nothing else. It never scaffolds, never prompts, never runs
|
|
14
|
+
# /uncharted, and never modifies anything on disk. Deciding what to DO with
|
|
15
|
+
# the verdict is agent judgement and lives in skills/init/SKILL.md.
|
|
16
|
+
#
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# VERDICTS -- exactly one is printed on stdout, nothing else
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# empty The target is empty, or contains only boilerplate
|
|
21
|
+
# that does not count as "there is a project here":
|
|
22
|
+
# .git (the directory itself, pruned entirely -- not
|
|
23
|
+
# merely excluded by name), .gitignore, and top-level
|
|
24
|
+
# README*/LICENSE*/LICENCE* files (case-insensitive),
|
|
25
|
+
# plus anything git itself reports as ignored. A repo
|
|
26
|
+
# containing only a README is empty for this purpose.
|
|
27
|
+
# already-scaffolded The target already carries a Jenga scaffold: any one
|
|
28
|
+
# of project/board/, project/PROJECT_SUMMARY.md, or
|
|
29
|
+
# project/configs/workflow.json is present. Checked
|
|
30
|
+
# BEFORE the emptiness scan, so a scaffolded project
|
|
31
|
+
# that also happens to look sparse is never misreported
|
|
32
|
+
# as "empty".
|
|
33
|
+
# existing-codebase The target has real content (source files, configs,
|
|
34
|
+
# docs beyond the boilerplate list above, etc.) and is
|
|
35
|
+
# not already Jenga-scaffolded. This is the case /init
|
|
36
|
+
# currently mishandles: scaffolding fresh here silently
|
|
37
|
+
# discards the fact that pre-existing code exists.
|
|
38
|
+
#
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# NOTES ON GITIGNORE HANDLING
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# /init runs this check BEFORE `git init` in the fresh-scaffold path, so the
|
|
43
|
+
# target directory is very often not a git work tree yet. Gitignore-based
|
|
44
|
+
# filtering is therefore best-effort: it only runs when the target already
|
|
45
|
+
# has a `.git` directory AND `git` is on PATH. A standalone `.gitignore` file
|
|
46
|
+
# with no repository behind it still counts as boilerplate via the name-based
|
|
47
|
+
# exclusion below -- it just cannot hide any OTHER file, because there is no
|
|
48
|
+
# git to ask "is this ignored".
|
|
49
|
+
#
|
|
50
|
+
# README/LICENSE exclusion applies to TOP-LEVEL entries only. A directory
|
|
51
|
+
# whose only content is a buried `docs/README.md` two levels down is real
|
|
52
|
+
# structure, not boilerplate, and must not be reported as empty.
|
|
53
|
+
#
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# Usage
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# detect-existing-codebase.sh [target-dir]
|
|
58
|
+
#
|
|
59
|
+
# target-dir Directory to classify. Default: current directory.
|
|
60
|
+
#
|
|
61
|
+
# Options:
|
|
62
|
+
# -h, --help Show this help and exit 0.
|
|
63
|
+
#
|
|
64
|
+
# Exit codes:
|
|
65
|
+
# 0 A verdict was printed on stdout.
|
|
66
|
+
# 1 Usage error (unknown option, more than one positional argument).
|
|
67
|
+
# 2 target-dir does not exist or is not a directory.
|
|
68
|
+
#
|
|
69
|
+
# Requires: bash, find. git is used opportunistically for gitignore
|
|
70
|
+
# filtering when the target is already a git work tree; its absence never
|
|
71
|
+
# causes a failure.
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
set -euo pipefail
|
|
75
|
+
|
|
76
|
+
SELF="$(basename "$0")"
|
|
77
|
+
|
|
78
|
+
usage() {
|
|
79
|
+
sed -n '/^# Usage$/,/^# Requires:/p' "$0" | sed -e 's/^# \{0,1\}//' -e '/^-\{10,\}$/d'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
die_usage() {
|
|
83
|
+
printf '%s: error: %s\n' "$SELF" "$*" >&2
|
|
84
|
+
echo >&2
|
|
85
|
+
usage >&2
|
|
86
|
+
exit 1
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
TARGET_DIR="."
|
|
90
|
+
HAVE_TARGET=0
|
|
91
|
+
|
|
92
|
+
while [ "$#" -gt 0 ]; do
|
|
93
|
+
case "$1" in
|
|
94
|
+
-h|--help) usage; exit 0 ;;
|
|
95
|
+
--)
|
|
96
|
+
shift
|
|
97
|
+
[ "$#" -le 1 ] || die_usage "at most one target directory is allowed"
|
|
98
|
+
if [ "$#" -eq 1 ]; then TARGET_DIR="$1"; HAVE_TARGET=1; fi
|
|
99
|
+
shift $#
|
|
100
|
+
;;
|
|
101
|
+
-*)
|
|
102
|
+
die_usage "unknown option \"$1\"" ;;
|
|
103
|
+
*)
|
|
104
|
+
[ "$HAVE_TARGET" -eq 0 ] || die_usage "at most one target directory is allowed (got \"$TARGET_DIR\" and \"$1\")"
|
|
105
|
+
TARGET_DIR="$1"; HAVE_TARGET=1; shift ;;
|
|
106
|
+
esac
|
|
107
|
+
done
|
|
108
|
+
|
|
109
|
+
[ -d "$TARGET_DIR" ] || { printf '%s: error: not a directory: %s\n' "$SELF" "$TARGET_DIR" >&2; exit 2; }
|
|
110
|
+
|
|
111
|
+
TARGET_DIR="$(cd -- "$TARGET_DIR" && pwd -P)"
|
|
112
|
+
|
|
113
|
+
# --- already-scaffolded: checked first, independent of emptiness -----------
|
|
114
|
+
if [ -d "$TARGET_DIR/project/board" ] \
|
|
115
|
+
|| [ -f "$TARGET_DIR/project/PROJECT_SUMMARY.md" ] \
|
|
116
|
+
|| [ -f "$TARGET_DIR/project/configs/workflow.json" ]; then
|
|
117
|
+
echo "already-scaffolded"
|
|
118
|
+
exit 0
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# --- enumerate candidate files, pruning .git entirely -----------------------
|
|
122
|
+
ALL_FILES=$(mktemp "${TMPDIR:-/tmp}/detect-existing-codebase.XXXXXX")
|
|
123
|
+
IGNORED_FILES=$(mktemp "${TMPDIR:-/tmp}/detect-existing-codebase-ignored.XXXXXX")
|
|
124
|
+
cleanup() { rm -f "$ALL_FILES" "$IGNORED_FILES"; }
|
|
125
|
+
trap cleanup EXIT
|
|
126
|
+
|
|
127
|
+
find "$TARGET_DIR" -path "$TARGET_DIR/.git" -prune -o -type f -print > "$ALL_FILES"
|
|
128
|
+
|
|
129
|
+
# --- best-effort gitignore filtering ----------------------------------------
|
|
130
|
+
: > "$IGNORED_FILES"
|
|
131
|
+
if [ -d "$TARGET_DIR/.git" ] && command -v git >/dev/null 2>&1; then
|
|
132
|
+
git -C "$TARGET_DIR" check-ignore --stdin < "$ALL_FILES" > "$IGNORED_FILES" 2>/dev/null || true
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# --- classify each candidate -------------------------------------------------
|
|
136
|
+
SOURCE_FOUND=0
|
|
137
|
+
while IFS= read -r f; do
|
|
138
|
+
[ -n "$f" ] || continue
|
|
139
|
+
|
|
140
|
+
if [ -s "$IGNORED_FILES" ] && grep -Fxq -- "$f" "$IGNORED_FILES"; then
|
|
141
|
+
continue
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
rel="${f#"$TARGET_DIR"/}"
|
|
145
|
+
base="$(basename -- "$f")"
|
|
146
|
+
dir_rel="$(dirname -- "$rel")"
|
|
147
|
+
|
|
148
|
+
if [ "$dir_rel" = "." ]; then
|
|
149
|
+
if [ "$base" = ".gitignore" ]; then
|
|
150
|
+
continue
|
|
151
|
+
fi
|
|
152
|
+
base_upper="$(printf '%s' "$base" | tr '[:lower:]' '[:upper:]')"
|
|
153
|
+
case "$base_upper" in
|
|
154
|
+
README*|LICENSE*|LICENCE*) continue ;;
|
|
155
|
+
esac
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
SOURCE_FOUND=1
|
|
159
|
+
break
|
|
160
|
+
done < "$ALL_FILES"
|
|
161
|
+
|
|
162
|
+
if [ "$SOURCE_FOUND" -eq 1 ]; then
|
|
163
|
+
echo "existing-codebase"
|
|
164
|
+
else
|
|
165
|
+
echo "empty"
|
|
166
|
+
fi
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
ASSETS_DIR="$SCRIPT_DIR/../assets"
|
|
6
|
+
VISIBILITY_SCRIPT="$SCRIPT_DIR/apply-project-visibility.sh"
|
|
7
|
+
|
|
8
|
+
# ─── Resolve the package root that owns templates/ and lib/ ──────────────────
|
|
9
|
+
# postinstall.js mirrors only skills/ and agents/ into .claude/ and .agents/ —
|
|
10
|
+
# templates/ and lib/ are never copied there, so a script running from a
|
|
11
|
+
# mirrored copy (.claude/skills/init/scripts/ or .agents/skills/init/scripts/)
|
|
12
|
+
# cannot reach its siblings via a fixed ../../../ climb the way it can in this
|
|
13
|
+
# monorepo checkout, where init.sh actually lives at skills/init/scripts/ with
|
|
14
|
+
# templates/ and lib/ three levels up. Consumers instead have them inside the
|
|
15
|
+
# installed npm package.
|
|
16
|
+
if [[ -d "$SCRIPT_DIR/../../../templates" ]]; then
|
|
17
|
+
PKG_ROOT="$SCRIPT_DIR/../../.."
|
|
18
|
+
elif [[ -d "$PWD/node_modules/@jenga-ai/agent/templates" ]]; then
|
|
19
|
+
PKG_ROOT="$PWD/node_modules/@jenga-ai/agent"
|
|
20
|
+
else
|
|
21
|
+
echo "Error: could not locate the jenga-agent package root (templates/ not found via monorepo checkout or node_modules/@jenga-ai/agent)." >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# ─── 0. Resolve project_files_visibility ─────────────────────────────────────
|
|
26
|
+
# Defaults to `visible` — the only value that touches nothing on disk — so an
|
|
27
|
+
# unattended run can never silently relocate directories or edit .gitignore.
|
|
28
|
+
VISIBILITY="${JENGA_PROJECT_FILES_VISIBILITY:-visible}"
|
|
29
|
+
|
|
30
|
+
while [[ $# -gt 0 ]]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
--visibility) VISIBILITY="${2:-}"; shift 2 ;;
|
|
33
|
+
--visibility=*) VISIBILITY="${1#*=}"; shift ;;
|
|
34
|
+
*) echo "Unknown argument: $1" >&2
|
|
35
|
+
echo "Usage: $(basename "$0") [--visibility <visible|ignored>]" >&2
|
|
36
|
+
exit 1 ;;
|
|
37
|
+
esac
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
# Validate before scaffolding so a typo cannot leave a half-initialised project.
|
|
41
|
+
bash "$VISIBILITY_SCRIPT" --check-only "$VISIBILITY"
|
|
42
|
+
|
|
43
|
+
# ─── 1. Initialize git repository ────────────────────────────────────────────
|
|
44
|
+
echo "→ Initializing git repository..."
|
|
45
|
+
git init
|
|
46
|
+
|
|
47
|
+
# ─── 2. Create .gitignore ─────────────────────────────────────────────────────
|
|
48
|
+
echo "→ Copying .gitignore from template..."
|
|
49
|
+
cp "$ASSETS_DIR/.gitignore_template" .gitignore
|
|
50
|
+
|
|
51
|
+
# ─── 3. Scaffold directory structure ──────────────────────────────────────────
|
|
52
|
+
echo "→ Scaffolding directory structure..."
|
|
53
|
+
while IFS= read -r dir || [[ -n "$dir" ]]; do
|
|
54
|
+
[[ -z "$dir" || "$dir" == \#* ]] && continue
|
|
55
|
+
mkdir -p "$dir"
|
|
56
|
+
done < "$ASSETS_DIR/directory_structure.txt"
|
|
57
|
+
|
|
58
|
+
# ─── 4. Create project/PROJECT_SUMMARY.md ────────────────────────────────────
|
|
59
|
+
echo "→ Copying PROJECT_SUMMARY.md from template..."
|
|
60
|
+
cp "$ASSETS_DIR/PROJECT_SUMMARY_template.md" project/PROJECT_SUMMARY.md
|
|
61
|
+
|
|
62
|
+
# ─── 5. Create project/configs/workflow.json ─────────────────────────────────
|
|
63
|
+
echo "→ Copying workflow.json from template..."
|
|
64
|
+
cp "$ASSETS_DIR/workflow_template.json" project/configs/workflow.json
|
|
65
|
+
|
|
66
|
+
# ─── 6. Create project/configs/test-config.json stub ─────────────────────────
|
|
67
|
+
echo "→ Copying test-config.json from template..."
|
|
68
|
+
cp "$ASSETS_DIR/test-config_template.json" project/configs/test-config.json
|
|
69
|
+
|
|
70
|
+
# ─── 6.5. Create project/configs/scope-thresholds.json ───────────────────────
|
|
71
|
+
# Consumed by skills/jenga (Phase 0) and skills/do (Step 0); both halt if it's
|
|
72
|
+
# missing, so it must exist immediately after scaffold.
|
|
73
|
+
echo "→ Copying scope-thresholds.json from template..."
|
|
74
|
+
cp "$ASSETS_DIR/scope-thresholds_template.json" project/configs/scope-thresholds.json
|
|
75
|
+
|
|
76
|
+
# ─── 7. Create project/data/baselines.json ───────────────────────────────────
|
|
77
|
+
echo "→ Creating baselines.json..."
|
|
78
|
+
echo '{}' > project/data/baselines.json
|
|
79
|
+
|
|
80
|
+
# ─── 8. Create project/logs/events.json ──────────────────────────────────────
|
|
81
|
+
echo "→ Creating events.json..."
|
|
82
|
+
echo '[]' > project/logs/events.json
|
|
83
|
+
|
|
84
|
+
# ─── 9. Create docs/STRATEGY.md ──────────────────────────────────────────────
|
|
85
|
+
echo "→ Creating docs/STRATEGY.md (strategic brief for investors, partners, and the product team)..."
|
|
86
|
+
mkdir -p docs
|
|
87
|
+
cp "$ASSETS_DIR/strategy_stub_template.md" docs/STRATEGY.md
|
|
88
|
+
|
|
89
|
+
# ─── 10. Create CHANGELOG.md ──────────────────────────────────────────────────
|
|
90
|
+
echo "→ Creating CHANGELOG.md from template..."
|
|
91
|
+
cp "$PKG_ROOT/templates/CHANGELOG_TEMPLATE.md" CHANGELOG.md
|
|
92
|
+
|
|
93
|
+
# ─── 11. Apply project_files_visibility ──────────────────────────────────────
|
|
94
|
+
# Runs before the commit so the .gitignore entry (ignored) is captured in the
|
|
95
|
+
# initial commit.
|
|
96
|
+
echo "→ Applying project files visibility ($VISIBILITY)..."
|
|
97
|
+
bash "$VISIBILITY_SCRIPT" "$VISIBILITY" "$PWD"
|
|
98
|
+
|
|
99
|
+
# ─── 12. Generate CLAUDE.md / AGENTS.md ──────────────────────────────────────
|
|
100
|
+
# Unconditional — never gated on agentTarget (E41_S04). Applies the J-
|
|
101
|
+
# collision rule and idempotent managed-block updates; see
|
|
102
|
+
# lib/generate-agent-context.js (shared with the published `jenga init` CLI).
|
|
103
|
+
echo "→ Generating CLAUDE.md / AGENTS.md..."
|
|
104
|
+
if command -v node >/dev/null 2>&1; then
|
|
105
|
+
node "$PKG_ROOT/lib/generate-agent-context.js" "$PWD"
|
|
106
|
+
else
|
|
107
|
+
echo " Warning: node not found — skipped CLAUDE.md/AGENTS.md generation." >&2
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# ─── 13. Initial commit ──────────────────────────────────────────────────────
|
|
111
|
+
echo "→ Staging and committing scaffolded files..."
|
|
112
|
+
git add -A
|
|
113
|
+
git commit -m "init: scaffold project structure and workflow config"
|
|
114
|
+
|
|
115
|
+
echo ""
|
|
116
|
+
echo "✓ Project scaffold complete."
|
package/skills/jbp/SKILL.md
CHANGED
package/skills/jenga/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: jenga
|
|
2
|
+
name: j:jenga
|
|
3
3
|
description: Interactive-by-default board orchestrator with a fully automated escape hatch. Bare `/jenga` renders a picker and confirmation tree before scoping the run; `/jenga <ids>` resolves an explicit fuzzy-ID scope and confirms it; `/jenga *` reproduces the original zero-prompt behavior — decomposing any unbroken Epics into Stories, any unbroken Stories into Tasks, queuing all unqueued Tasks into todo.md, then executing every eligible item with no user prompts — until the board is fully started.
|
|
4
4
|
keywords:
|
|
5
5
|
- jenga
|
|
@@ -97,12 +97,18 @@
|
|
|
97
97
|
# ENTIRE turn is rejected as an error turn (state left untouched) — there
|
|
98
98
|
# is no partial application of the valid numbers in a mixed-validity reply.
|
|
99
99
|
#
|
|
100
|
-
# Toggling
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
100
|
+
# Toggling CASCADES downward (E45_S02_T03): checking or unchecking a
|
|
101
|
+
# story applies the same resulting state to all of its tasks; checking or
|
|
102
|
+
# unchecking an epic applies the same resulting state to all of its
|
|
103
|
+
# stories and their tasks. This is a deliberate, explicit reversal of the
|
|
104
|
+
# original non-cascading design (see this story's 2026-08-27 close-out) —
|
|
105
|
+
# the user hit a real case where unchecking a parent epic left its child
|
|
106
|
+
# story/tasks checked, requiring a manual follow-up uncheck command.
|
|
107
|
+
# Cascade is STRICTLY DOWNWARD (parent -> descendants) only: checking a
|
|
108
|
+
# leaf task does NOT imply its ancestors should also be checked, so a
|
|
109
|
+
# checked child under an unchecked parent is a valid (if visually
|
|
110
|
+
# inconsistent) state. Toggling a leaf task (no children) behaves exactly
|
|
111
|
+
# as it always has — no descendants to cascade into.
|
|
106
112
|
#
|
|
107
113
|
# ---------------------------------------------------------------------------
|
|
108
114
|
# OUTPUT CONTRACT (deliberately NOT uniform JSON — see rationale below)
|
|
@@ -165,8 +171,15 @@
|
|
|
165
171
|
# STATE FILE
|
|
166
172
|
# ---------------------------------------------------------------------------
|
|
167
173
|
# Created under `mktemp -t jenga-confirm-XXXXXX.json`. Stores:
|
|
168
|
-
# - `numbering`: displayed number -> {item, level, checked} —
|
|
169
|
-
#
|
|
174
|
+
# - `numbering`: displayed number -> {item, level, checked, children} —
|
|
175
|
+
# `children` is a fixed list of the item's direct descendant numbers
|
|
176
|
+
# (a story's task numbers; an epic's story numbers), computed once at
|
|
177
|
+
# build time so continue-mode can cascade a toggle without
|
|
178
|
+
# re-deriving parent/child relationships from `item_id`/`epic_id`/
|
|
179
|
+
# `story_id` at toggle time. `children` is always `[]` for a leaf
|
|
180
|
+
# task. The only fields that mutate across turns are `checked` values
|
|
181
|
+
# (potentially many at once, via cascade — see TOGGLE COMMAND GRAMMAR
|
|
182
|
+
# above).
|
|
170
183
|
# - `layout`: the fixed print order (item lines + header markers),
|
|
171
184
|
# computed once at start. Continue mode never recomputes tree shape —
|
|
172
185
|
# it only re-renders from `layout` + the live `checked` flags in
|
|
@@ -305,15 +318,17 @@ def pluralize_task(n):
|
|
|
305
318
|
return f"{n} task{'' if n == 1 else 's'}"
|
|
306
319
|
|
|
307
320
|
|
|
308
|
-
def add_item(item, level, indent):
|
|
321
|
+
def add_item(item, level, indent, parent_number=None):
|
|
309
322
|
global counter
|
|
310
323
|
n = str(counter)
|
|
311
324
|
counter += 1
|
|
312
325
|
suffix = ""
|
|
313
326
|
if item.get("id") in undecomposed_ids:
|
|
314
327
|
suffix = " [needs decomposition -- no children on the board yet]"
|
|
315
|
-
numbering[n] = {"item": item, "level": level, "checked": True}
|
|
328
|
+
numbering[n] = {"item": item, "level": level, "checked": True, "children": []}
|
|
316
329
|
layout.append({"kind": "item", "number": n, "level": level, "indent": indent, "suffix": suffix})
|
|
330
|
+
if parent_number is not None:
|
|
331
|
+
numbering[parent_number]["children"].append(n)
|
|
317
332
|
return n
|
|
318
333
|
|
|
319
334
|
|
|
@@ -328,7 +343,7 @@ def append_suffix(extra):
|
|
|
328
343
|
|
|
329
344
|
|
|
330
345
|
for e in epics:
|
|
331
|
-
add_item(e, "epic", INDENT["epic"])
|
|
346
|
+
e_num = add_item(e, "epic", INDENT["epic"])
|
|
332
347
|
child_stories = stories_by_epic.get(e["id"], [])
|
|
333
348
|
story_count = len(child_stories)
|
|
334
349
|
task_count = sum(len(tasks_by_story.get(s["id"], [])) for s in child_stories)
|
|
@@ -340,23 +355,23 @@ for e in epics:
|
|
|
340
355
|
append_suffix(", ".join(parts))
|
|
341
356
|
|
|
342
357
|
for s in child_stories:
|
|
343
|
-
add_item(s, "story", INDENT["story"])
|
|
358
|
+
s_num = add_item(s, "story", INDENT["story"], parent_number=e_num)
|
|
344
359
|
child_tasks = tasks_by_story.get(s["id"], [])
|
|
345
360
|
if child_tasks:
|
|
346
361
|
append_suffix(pluralize_task(len(child_tasks)))
|
|
347
362
|
for t in child_tasks:
|
|
348
|
-
add_item(t, "task", INDENT["task"])
|
|
363
|
+
add_item(t, "task", INDENT["task"], parent_number=s_num)
|
|
349
364
|
|
|
350
365
|
if orphan_stories:
|
|
351
366
|
layout.append({"kind": "header", "text": ""})
|
|
352
367
|
layout.append({"kind": "header", "text": "-- Directly Selected Stories (no epic in this selection) --"})
|
|
353
368
|
for s in orphan_stories:
|
|
354
|
-
add_item(s, "story", INDENT["story"])
|
|
369
|
+
s_num = add_item(s, "story", INDENT["story"])
|
|
355
370
|
child_tasks = tasks_by_story.get(s["id"], [])
|
|
356
371
|
if child_tasks:
|
|
357
372
|
append_suffix(pluralize_task(len(child_tasks)))
|
|
358
373
|
for t in child_tasks:
|
|
359
|
-
add_item(t, "task", INDENT["task"])
|
|
374
|
+
add_item(t, "task", INDENT["task"], parent_number=s_num)
|
|
360
375
|
|
|
361
376
|
if orphan_tasks:
|
|
362
377
|
layout.append({"kind": "header", "text": ""})
|
|
@@ -644,18 +659,40 @@ if invalid:
|
|
|
644
659
|
print(f"STATE_FILE: {state_file_path}", file=sys.stderr)
|
|
645
660
|
sys.exit(1)
|
|
646
661
|
|
|
662
|
+
def apply_cascade(num, value):
|
|
663
|
+
"""Set `num`'s checked state to `value`, then recurse into every
|
|
664
|
+
number in its `children` list (a story's tasks; an epic's stories,
|
|
665
|
+
which recurse further into their own tasks). Strictly downward —
|
|
666
|
+
never touches a parent. A leaf task has an empty `children` list, so
|
|
667
|
+
this degenerates to exactly the old single-flag assignment for the
|
|
668
|
+
non-cascading base case."""
|
|
669
|
+
numbering[num]["checked"] = value
|
|
670
|
+
for child in numbering[num].get("children", []):
|
|
671
|
+
apply_cascade(child, value)
|
|
672
|
+
|
|
673
|
+
|
|
647
674
|
# All-or-nothing application, de-duplicated (first-seen wins on dupes).
|
|
675
|
+
# Resulting values are computed from PRE-mutation state in a first pass,
|
|
676
|
+
# then applied (with cascade) in a second pass -- this avoids a
|
|
677
|
+
# parent-then-child (or child-then-parent) double-toggle artifact when a
|
|
678
|
+
# single reply lists both a parent and one of its own descendants, e.g.
|
|
679
|
+
# bare-number toggle "5,6" where 6 is a child of 5.
|
|
648
680
|
seen = set()
|
|
681
|
+
pending = []
|
|
649
682
|
for tok in valid_tokens:
|
|
650
683
|
if tok in seen:
|
|
651
684
|
continue
|
|
652
685
|
seen.add(tok)
|
|
653
686
|
if action == "check":
|
|
654
|
-
|
|
687
|
+
value = True
|
|
655
688
|
elif action == "uncheck":
|
|
656
|
-
|
|
689
|
+
value = False
|
|
657
690
|
else:
|
|
658
|
-
|
|
691
|
+
value = not numbering[tok]["checked"]
|
|
692
|
+
pending.append((tok, value))
|
|
693
|
+
|
|
694
|
+
for tok, value in pending:
|
|
695
|
+
apply_cascade(tok, value)
|
|
659
696
|
|
|
660
697
|
state["numbering"] = numbering
|
|
661
698
|
with open(state_file_path, "w", encoding="utf-8") as f:
|
package/skills/lgtm/SKILL.md
CHANGED
package/skills/pi-plan/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: pi-plan
|
|
2
|
+
name: j:pi-plan
|
|
3
3
|
description: Define or expand project Epics in PROJECT_SUMMARY.md. Use this at the start of a project to establish its foundation, AND whenever the user wants to add major new features, plan a significant new area of work, or make epic-level changes to an existing project. Trigger whenever you hear things like "new feature area", "big change", "new epic", "expand the project", "add a major capability", or "plan a new phase" — even mid-project.
|
|
4
4
|
keywords:
|
|
5
5
|
- epic
|
package/skills/proceed/SKILL.md
CHANGED
package/skills/publish/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: publish
|
|
2
|
+
name: j:publish
|
|
3
3
|
description: Configure, validate, and orchestrate scaffolded release workflows through a single `/publish` entry point with bounded sub-commands.
|
|
4
4
|
keywords:
|
|
5
5
|
- publish
|
|
@@ -9,6 +9,8 @@ keywords:
|
|
|
9
9
|
- npm
|
|
10
10
|
- registry
|
|
11
11
|
- release notes
|
|
12
|
+
- staged publishing
|
|
13
|
+
- stage
|
|
12
14
|
examples:
|
|
13
15
|
- "publish setup --target staging-appstore"
|
|
14
16
|
- "publish setup --type mobile-ios"
|
|
@@ -21,6 +23,9 @@ examples:
|
|
|
21
23
|
- "publish deploy --target my-droplet --dry-run"
|
|
22
24
|
- "publish history --limit 5"
|
|
23
25
|
- "publish release-notes --target staging-appstore"
|
|
26
|
+
- "test a release before publishing"
|
|
27
|
+
- "publish stage --target npm-registry --dry-run"
|
|
28
|
+
- "approve a staged npm release"
|
|
24
29
|
metadata:
|
|
25
30
|
scope: multi-target-v2
|
|
26
31
|
primary_target: multi
|
|
@@ -76,6 +81,7 @@ If config or env validation fails, the skill exits with code `4` and does not co
|
|
|
76
81
|
|---|---|---|---|
|
|
77
82
|
| `/publish setup` | Prepare or refresh target configuration | `skills/publish/scripts/setup_wizard.sh` | Supported types: `mobile-ios`, `npm`, `npm-ci`, `droplet` |
|
|
78
83
|
| `/publish deploy` | Run the full 11-step deploy orchestration | `skills/publish/scripts/publish_deploy.sh` | Dispatches to the adapter for the target's `type` (`mobile-ios`, `npm`, `npm-ci`, or `droplet`); `--dry-run` is honoured end-to-end |
|
|
84
|
+
| `/publish stage` | Stage an npm release into npm's staged-publishing area, smoke-test it in isolation, then approve or reject it | `skills/publish/scripts/npm_stage_pipeline.sh` (the `publish` sub-command) and `skills/publish/scripts/npm_stage_inspect.sh` (`list`, `view`, `download`, `test`, `approve`, `reject`) | Supported for `npm` and `npm-ci` target types only |
|
|
79
85
|
| `/publish history` | Read the canonical publish ledger | `skills/publish/scripts/show_history.sh` | Target-agnostic; filter by `--target <name>` |
|
|
80
86
|
| `/publish release-notes` | Merge new release notes into the standing `CHANGELOG.md` (or a standalone draft via `--output`) without publishing | `skills/publish/scripts/generate_release_notes.sh` | Target-agnostic |
|
|
81
87
|
|
|
@@ -170,6 +176,66 @@ Example invocations:
|
|
|
170
176
|
|
|
171
177
|
Use `--dry-run` to rehearse a release, validate that gates pass, and confirm the generated release notes without publishing.
|
|
172
178
|
|
|
179
|
+
### `/publish stage`
|
|
180
|
+
|
|
181
|
+
Staged publishing for `npm`/`npm-ci` targets only. Not available for
|
|
182
|
+
`mobile-ios` or `droplet` — those adapters have no staging area to dispatch
|
|
183
|
+
to. The lifecycle is **stage → test → approve**, with `reject` as the
|
|
184
|
+
discard path at any point after staging:
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
staged ──▶ stage_tested ──▶ approved (approve requires 2FA; refuses without
|
|
188
|
+
│ │ a passing test unless --force <reason>)
|
|
189
|
+
│ │
|
|
190
|
+
└─────────────┴──▶ rejected (discard path — available from any staged state)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Two registry constraints trip people up and are worth stating plainly:
|
|
194
|
+
|
|
195
|
+
- **The package must already exist on the registry.** npm's staged-publishing
|
|
196
|
+
workflow only applies to a package that has had at least one normal,
|
|
197
|
+
non-staged publish already — `validate_npm_stage_env.sh` checks this
|
|
198
|
+
up front and exits `4` if it doesn't hold.
|
|
199
|
+
- **Approval requires 2FA; staging does not.** `npm stage publish` (the
|
|
200
|
+
`stage` sub-command below) can run non-interactively in CI. `npm stage
|
|
201
|
+
approve` cannot — npmjs.com requires an interactive one-time-password
|
|
202
|
+
step for approval, so it is always a human action even when staging was
|
|
203
|
+
automated (see the npm-ci adapter's OIDC staging section for the CI-stage,
|
|
204
|
+
human-approve split).
|
|
205
|
+
|
|
206
|
+
All seven sub-commands:
|
|
207
|
+
|
|
208
|
+
```text
|
|
209
|
+
/publish stage publish <target> <path-to-publish.json> [--dry-run] [--non-interactive] [--otp <otp>]
|
|
210
|
+
/publish stage test <stage-id> [--keep] [--config <path>] [--dry-run] [--json]
|
|
211
|
+
/publish stage list [<package-spec>] [--config <path>] [--dry-run] [--json]
|
|
212
|
+
/publish stage view <stage-id> [--config <path>] [--dry-run] [--json]
|
|
213
|
+
/publish stage download <stage-id> [--out <dir>] [--config <path>] [--dry-run] [--json]
|
|
214
|
+
/publish stage approve <stage-id> [--otp <otp>] [--force <reason>] [--config <path>] [--dry-run] [--json]
|
|
215
|
+
/publish stage reject <stage-id> [--config <path>] [--dry-run] [--json]
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Implementation:
|
|
219
|
+
|
|
220
|
+
- `publish` → `bash skills/publish/scripts/npm_stage_pipeline.sh <target> <path-to-publish.json> [--dry-run] [--non-interactive] [--otp <otp>]` — six ordered phases (validate, gates, pack, stage, capture, ledger); writes a `staged` ledger entry on success.
|
|
221
|
+
- `test`, `list`, `view`, `download`, `approve`, `reject` → `bash skills/publish/scripts/npm_stage_inspect.sh <sub> [args] [--config <path>] [--dry-run] [--json]`
|
|
222
|
+
- `test` downloads the exact staged tarball into an isolated scratch directory outside the repo, installs it, runs the target's `npm.stage.smoke_cmd` (or the documented default check), and writes a `stage_tested` ledger entry (`pass`/`fail`).
|
|
223
|
+
- `approve` refuses to run unless a passing `test` is on record for that exact stage id, unless `--force <reason>` is given; writes an `approved` ledger entry.
|
|
224
|
+
- `reject` is the discard path omitted from npm's own staged-publishing docs page — documented here so it stays discoverable; writes a `rejected` ledger entry.
|
|
225
|
+
|
|
226
|
+
Example invocations:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
/publish stage publish npm-registry ./publish.json --dry-run
|
|
230
|
+
/publish stage test <stage-id>
|
|
231
|
+
/publish stage approve <stage-id>
|
|
232
|
+
/publish stage reject <stage-id>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Use `/publish stage` to rehearse and smoke-test a release before it goes
|
|
236
|
+
live — the point of staging is that a bad tarball is caught in isolation
|
|
237
|
+
instead of being caught by users after `npm publish`.
|
|
238
|
+
|
|
173
239
|
### `/publish history`
|
|
174
240
|
|
|
175
241
|
```text
|
|
@@ -99,11 +99,33 @@ Run the adapter phases in this exact order:
|
|
|
99
99
|
```yaml
|
|
100
100
|
permissions:
|
|
101
101
|
id-token: write
|
|
102
|
-
contents:
|
|
102
|
+
contents: write
|
|
103
103
|
```
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
(`contents: write` — bumped from `read` — is required so the `publish` and
|
|
105
|
+
`stage` jobs can push the tags described below; this is the CI's own token
|
|
106
|
+
gaining write access to tag *its own* repository, nothing else, and no new
|
|
107
|
+
secret or credential is introduced) and runs `npm publish --provenance` to
|
|
108
|
+
attach a build provenance attestation to the published package. In
|
|
109
|
+
`--dry-run` mode, print the rendered YAML to stdout and exit 0 without
|
|
110
|
+
writing any file.
|
|
111
|
+
|
|
112
|
+
Each job also gains a final step, gated on `if: success()` so a failed
|
|
113
|
+
publish/stage never creates or moves a tag:
|
|
114
|
+
- **`publish` job — "Tag prod release and remove stage tag":** reads the
|
|
115
|
+
version from `package.json`, tags the checked-out commit `v<version>`
|
|
116
|
+
(no force — this tag is immutable; a genuine collision fails loudly
|
|
117
|
+
rather than silently moving a prod tag), pushes it, then best-effort
|
|
118
|
+
deletes the corresponding `v<version>-stage` tag both locally and on the
|
|
119
|
+
remote (tolerating its absence, e.g. a prod publish that skipped
|
|
120
|
+
staging).
|
|
121
|
+
- **`stage` job — "Tag stage release":** reads the version the same way
|
|
122
|
+
and tags the checked-out commit `v<version>-stage` — a **mutable**
|
|
123
|
+
pointer, force-created (`git tag -f`) and force-pushed, so re-staging the
|
|
124
|
+
same version moves the tag rather than failing.
|
|
125
|
+
|
|
126
|
+
Both new steps configure a `github-actions[bot]` git identity
|
|
127
|
+
(`user.name`/`user.email`) before tagging, since no prior step in the
|
|
128
|
+
workflow sets one.
|
|
107
129
|
|
|
108
130
|
3. **`commit-workflow`** — write the generated YAML to `<workflow_path>`
|
|
109
131
|
(creating the `.github/workflows/` directory if needed) and commit it with:
|
|
@@ -190,6 +212,40 @@ A successful non-dry-run produces:
|
|
|
190
212
|
- A history entry in `project/logs/publish-history.json` written by
|
|
191
213
|
`publish_deploy.sh` with `platform_state: "triggered"`
|
|
192
214
|
|
|
215
|
+
## Staged Publishing
|
|
216
|
+
|
|
217
|
+
This target type also supports **staged publishing** via `/publish stage` —
|
|
218
|
+
npm's pre-publish staging area — with an OIDC-specific split between the
|
|
219
|
+
automated and human halves of the flow:
|
|
220
|
+
|
|
221
|
+
- **The actual `npm stage publish --provenance` call always runs inside
|
|
222
|
+
GitHub Actions, never on a local machine.** For `npm-ci` targets, phase 4
|
|
223
|
+
of `npm_stage_pipeline.sh` (run locally via `/publish stage publish`)
|
|
224
|
+
dispatches the `stage` job of the target's generated workflow (the same
|
|
225
|
+
`<workflow_path>` / OIDC Trusted Publisher link `npm_ci_pipeline.sh` uses
|
|
226
|
+
for `/publish deploy`, generated with a `mode: publish | stage`
|
|
227
|
+
`workflow_dispatch` input) via
|
|
228
|
+
`gh workflow run <workflow_filename> --repo <github_repo> -f mode=stage`,
|
|
229
|
+
then blocks on `gh run watch` until that run finishes. The `stage` job
|
|
230
|
+
itself runs a plain `npm stage publish --provenance --access <access>
|
|
231
|
+
--tag <dist_tag>` step, authorised through the same Trusted Publisher OIDC
|
|
232
|
+
link described above — no `NPM_TOKEN` is needed to stage, exactly as none
|
|
233
|
+
is needed to publish. Local `--dry-run` only prints the `gh workflow run`
|
|
234
|
+
command that would be dispatched; it never triggers a workflow run.
|
|
235
|
+
- **Approval is always a human, out-of-CI step.** `npm stage approve`
|
|
236
|
+
requires an interactive npm 2FA one-time password — there is no OIDC
|
|
237
|
+
equivalent for approval. A human runs
|
|
238
|
+
`bash skills/publish/scripts/npm_stage_inspect.sh test <stage-id>` (or
|
|
239
|
+
relies on the automated CI-staged test result) and then
|
|
240
|
+
`bash skills/publish/scripts/npm_stage_inspect.sh approve <stage-id> --otp <otp>`
|
|
241
|
+
from their own machine. `reject` (same script) is available to either
|
|
242
|
+
side to discard a staged candidate.
|
|
243
|
+
|
|
244
|
+
Same registry-existence precondition as a normal `npm-ci` deploy: staged
|
|
245
|
+
publishing only applies to a package that has already had at least one
|
|
246
|
+
non-staged publish (`validate_npm_stage_env.sh` checks this up front, exit
|
|
247
|
+
`4` if not).
|
|
248
|
+
|
|
193
249
|
## Post-deploy manual steps
|
|
194
250
|
|
|
195
251
|
After a successful deploy trigger, the adapter prints the workflow run URL.
|