@jenga-ai/agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +340 -0
  3. package/agents/ai_engineer.md +113 -0
  4. package/agents/developer.md +236 -0
  5. package/agents/scrum-master.md +349 -0
  6. package/agents/scrutiny-agent.md +137 -0
  7. package/agents/solution-assessor.md +185 -0
  8. package/agents/tester.md +339 -0
  9. package/bin/jenga.js +70 -0
  10. package/hooks/copilot_session_end.sh +29 -0
  11. package/hooks/on_session_end.sh +238 -0
  12. package/hooks/prompt_router.sh +11 -0
  13. package/hooks/prompt_router_helper.js +52 -0
  14. package/hooks/session_end_helper.js +29 -0
  15. package/hooks/session_end_watcher.sh +24 -0
  16. package/lib/commands/attach.js +47 -0
  17. package/lib/commands/init.js +207 -0
  18. package/lib/commands/start.js +16 -0
  19. package/lib/commands/status.js +53 -0
  20. package/lib/config-schema.js +72 -0
  21. package/lib/inject-settings.js +61 -0
  22. package/lib/mirror.js +244 -0
  23. package/lib/resolve-project-dir.sh +47 -0
  24. package/mcp/execute-ticket/index.js +10 -0
  25. package/mcp/execute-ticket/package.json +5 -0
  26. package/mcp/help/index.js +79 -0
  27. package/mcp/help/package.json +14 -0
  28. package/mcp/router/README.md +19 -0
  29. package/mcp/router/embedder.js +23 -0
  30. package/mcp/router/index.js +204 -0
  31. package/mcp/router/matcher.js +87 -0
  32. package/mcp/router/package-lock.json +1048 -0
  33. package/mcp/router/package.json +11 -0
  34. package/mcp/router/skill-index.js +104 -0
  35. package/package.json +47 -0
  36. package/scripts/board_resolver.sh +46 -0
  37. package/scripts/e25_s01_extract_board_graph.py +292 -0
  38. package/scripts/e25_s01_generate_synthetic_board.py +90 -0
  39. package/scripts/measurement-10x.json +50 -0
  40. package/scripts/measurement-10x.txt +4 -0
  41. package/scripts/measurement-real.json +50 -0
  42. package/scripts/measurement-real.txt +4 -0
  43. package/scripts/postinstall.js +165 -0
  44. package/scripts/todo_cleanup.sh +22 -0
  45. package/scripts/todo_manager.sh +86 -0
  46. package/scripts/validate-board.sh +190 -0
  47. package/scripts/validate-story-format.sh +53 -0
  48. package/skills/brainstorm/SKILL.md +47 -0
  49. package/skills/btw/SKILL.md +42 -0
  50. package/skills/commit/SKILL.md +29 -0
  51. package/skills/commit/assets/user_instructions_template.md +22 -0
  52. package/skills/continue/SKILL.md +29 -0
  53. package/skills/convert/SKILL.md +124 -0
  54. package/skills/convert/convert_cli.py +235 -0
  55. package/skills/convert/tests/sample.csv +4 -0
  56. package/skills/convert/tests/sample.json +5 -0
  57. package/skills/convert/tests/sample.jsonl +3 -0
  58. package/skills/convert/tests/sample.yaml +18 -0
  59. package/skills/convert/tests/sample_obj.csv +2 -0
  60. package/skills/convert/tests/sample_obj.json +9 -0
  61. package/skills/deep-dive/SKILL.md +167 -0
  62. package/skills/do/SKILL.md +88 -0
  63. package/skills/do/assets/sender_template.json +12 -0
  64. package/skills/doc/SKILL.md +314 -0
  65. package/skills/doc/assets/path-objectives.yaml +38 -0
  66. package/skills/doc-sync/SKILL.md +167 -0
  67. package/skills/doc-sync/assets/default_excludes.txt +21 -0
  68. package/skills/doc-sync/assets/doc_targets.md +14 -0
  69. package/skills/dooo/SKILL.md +60 -0
  70. package/skills/error/SKILL.md +29 -0
  71. package/skills/evaluate/SKILL.md +45 -0
  72. package/skills/evaluate/assets/evaluation_invokation_template.yml +3 -0
  73. package/skills/evaluate/assets/evaluation_rapport_template.md +24 -0
  74. package/skills/examplify/SKILL.md +42 -0
  75. package/skills/help/SKILL.md +36 -0
  76. package/skills/improve/SKILL.md +55 -0
  77. package/skills/index/scripts/board-index +4 -0
  78. package/skills/index/scripts/board_index.py +615 -0
  79. package/skills/index/scripts/smoke_test.sh +86 -0
  80. package/skills/init/SKILL.md +44 -0
  81. package/skills/init/assets/.gitignore_template +15 -0
  82. package/skills/init/assets/PROJECT_SUMMARY_template.md +13 -0
  83. package/skills/init/assets/directory_structure.txt +13 -0
  84. package/skills/init/assets/test-config_template.json +4 -0
  85. package/skills/init/assets/workflow_template.json +30 -0
  86. package/skills/init/scripts/init.sh +48 -0
  87. package/skills/jbp/SKILL.md +25 -0
  88. package/skills/jenga/SKILL.md +68 -0
  89. package/skills/lgtm/SKILL.md +21 -0
  90. package/skills/mirror-public/SKILL.md +237 -0
  91. package/skills/mirror-public/assets/config.json +5 -0
  92. package/skills/mirror-public/scripts/mirror.sh +374 -0
  93. package/skills/pi-plan/SKILL.md +62 -0
  94. package/skills/pi-plan/assets/epic.json +7 -0
  95. package/skills/pi-plan/assets/story_template.md +18 -0
  96. package/skills/proceed/SKILL.md +29 -0
  97. package/skills/publish/SKILL.md +351 -0
  98. package/skills/publish/adapters/droplet.md +200 -0
  99. package/skills/publish/adapters/mobile-ios.md +114 -0
  100. package/skills/publish/adapters/npm-ci.md +223 -0
  101. package/skills/publish/adapters/npm.md +121 -0
  102. package/skills/publish/assets/ExportOptions.plist.template +19 -0
  103. package/skills/publish/assets/ci-contract.md +111 -0
  104. package/skills/publish/assets/ownership-matrix.md +17 -0
  105. package/skills/publish/assets/publish.example.json +85 -0
  106. package/skills/publish/assets/publish.example.npm-ci.json +40 -0
  107. package/skills/publish/assets/publish.example.npm.json +41 -0
  108. package/skills/publish/assets/secrets-guide.md +104 -0
  109. package/skills/publish/schemas/fixtures/npm-ci-minimal.json +17 -0
  110. package/skills/publish/schemas/fixtures/npm-ci-with-empty-secrets.json +18 -0
  111. package/skills/publish/schemas/fixtures/npm-ci-with-workflow-path.json +18 -0
  112. package/skills/publish/schemas/publish.schema.json +428 -0
  113. package/skills/publish/scripts/check_target_config.sh +96 -0
  114. package/skills/publish/scripts/droplet_pipeline.sh +208 -0
  115. package/skills/publish/scripts/generate_release_notes.sh +200 -0
  116. package/skills/publish/scripts/ios_pipeline.sh +486 -0
  117. package/skills/publish/scripts/npm_ci_pipeline.sh +225 -0
  118. package/skills/publish/scripts/npm_pipeline.sh +249 -0
  119. package/skills/publish/scripts/publish_common.sh +253 -0
  120. package/skills/publish/scripts/publish_deploy.sh +538 -0
  121. package/skills/publish/scripts/reconcile_tags.sh +135 -0
  122. package/skills/publish/scripts/run_gates.sh +616 -0
  123. package/skills/publish/scripts/setup_wizard.sh +394 -0
  124. package/skills/publish/scripts/show_history.sh +95 -0
  125. package/skills/publish/scripts/suggest_semver_bump.sh +105 -0
  126. package/skills/publish/scripts/validate_config.sh +163 -0
  127. package/skills/publish/scripts/validate_droplet_env.sh +45 -0
  128. package/skills/publish/scripts/validate_ios_env.sh +68 -0
  129. package/skills/publish/scripts/validate_npm_ci_env.sh +71 -0
  130. package/skills/publish/scripts/validate_npm_env.sh +22 -0
  131. package/skills/publish/scripts/write_ledger_entry.sh +126 -0
  132. package/skills/publish/wizards/droplet.md +275 -0
  133. package/skills/publish/wizards/mobile-ios.md +157 -0
  134. package/skills/publish/wizards/npm-ci.md +240 -0
  135. package/skills/publish/wizards/npm.md +224 -0
  136. package/skills/reconcile/SKILL.md +93 -0
  137. package/skills/reconcile/assets/report_format.md +44 -0
  138. package/skills/reconcile-origin/SKILL.md +75 -0
  139. package/skills/reconcile-origin/scripts/reconcile-origin.sh +372 -0
  140. package/skills/redo/SKILL.md +70 -0
  141. package/skills/route/SKILL.md +180 -0
  142. package/skills/self-sync/SKILL.md +73 -0
  143. package/skills/self-sync/scripts/run.js +136 -0
  144. package/skills/skillify/SKILL.md +68 -0
  145. package/skills/skillify/assets/init-new/SKILL.md +35 -0
  146. package/skills/skillify/assets/init-new/assets/.gitignore_template +15 -0
  147. package/skills/skillify/assets/init-new/assets/PROJECT_SUMMARY_template.md +13 -0
  148. package/skills/skillify/assets/init-new/assets/directory_structure.txt +10 -0
  149. package/skills/skillify/assets/init-new/assets/test-config_template.json +4 -0
  150. package/skills/skillify/assets/init-new/assets/workflow_template.json +17 -0
  151. package/skills/skillify/assets/init-new/scripts/init.sh +48 -0
  152. package/skills/skillify/assets/init-old/SKILL.md +124 -0
  153. package/skills/spinoff/SKILL.md +48 -0
  154. package/skills/status/SKILL.md +33 -0
  155. package/skills/status/assets/output_format.md +41 -0
  156. package/skills/todo/SKILL.md +46 -0
  157. package/skills/todo/assets/todo_handoff_template.md +22 -0
  158. package/skills/todo/assets/todo_template.md +3 -0
  159. package/skills/train/SKILL.md +116 -0
  160. package/skills/train/assets/dashboard-templates/classifiers.html +106 -0
  161. package/skills/train/assets/dashboard-templates/nlp.html +102 -0
  162. package/skills/train/assets/dashboard-templates/transformers.html +98 -0
  163. package/skills/train/assets/results-parsers/__init__.py +9 -0
  164. package/skills/train/assets/results-parsers/classifiers.py +84 -0
  165. package/skills/train/assets/results-parsers/nlp.py +88 -0
  166. package/skills/train/assets/results-parsers/reporter.py +154 -0
  167. package/skills/train/assets/results-parsers/transformers.py +120 -0
  168. package/skills/train/train_cli.py +786 -0
  169. package/templates/EXECUTION_PLAN_TEMPLATE.md +43 -0
  170. package/templates/EXECUTION_SUMMARY_TEMPLATE.md +50 -0
  171. package/templates/JENGA_CONFIG_TEMPLATE.json +23 -0
  172. package/templates/PROBLEM_RAPPORT_TEMPLATE.md +88 -0
  173. package/templates/SCRUM_BOARD_SCHEMA.md +311 -0
  174. package/templates/SKILL.md +16 -0
  175. package/templates/SKILL_TEMPLATE.md +28 -0
  176. package/templates/USER_INSTRUCTIONS_TEMPLATE.md +22 -0
  177. package/templates/copilot-instructions.md.tpl +55 -0
@@ -0,0 +1,374 @@
1
+ #!/usr/bin/env bash
2
+ # ---------------------------------------------------------------------------
3
+ # skills/mirror-public/scripts/mirror.sh
4
+ #
5
+ # One-way sync engine for /mirror-public.
6
+ #
7
+ # Reads: skills/mirror-public/assets/config.json
8
+ # - publicRepoUrl : URL of the public downstream repo
9
+ # - defaultBranch : branch to sync (typically "main")
10
+ # - worktreePath : scratch worktree path (relative to repo root)
11
+ # .publicignore : rsync blocklist (single source of truth for
12
+ # private-only paths — see T01)
13
+ #
14
+ # Flow:
15
+ # 1. Prepare a scratch clone of <publicRepoUrl> at <worktreePath>, checked
16
+ # out to <defaultBranch> at the tip of origin/<defaultBranch>.
17
+ # 2. Safety-check origin/<defaultBranch> against the local `last-mirror-sync`
18
+ # tag in the scratch clone. Abort if the public branch has diverged
19
+ # unless --force is passed.
20
+ # 3. rsync the private repo root into the worktree, respecting
21
+ # .publicignore and explicitly excluding .git/.
22
+ # 4. If nothing changed, exit 0 idempotently.
23
+ # 5. Otherwise squash everything into a single "chore(mirror): sync from
24
+ # private at <short-sha> <UTC-timestamp>" commit with a
25
+ # `Source-Commit: <full-sha>` trailer, advance the local marker tag,
26
+ # and push origin/<defaultBranch>.
27
+ # 6. Print a summary (files-changed count, new commit SHA, remote branch,
28
+ # public URL).
29
+ #
30
+ # Flags:
31
+ # --dry-run Read-only preview mode. Prepares the scratch worktree
32
+ # (fetch+reset only — no rsync writes, no commit, no push),
33
+ # then prints two labeled sections:
34
+ # - "Files that would ship" (from rsync --dry-run)
35
+ # - "Files that would be blocked" (candidates \ shipped)
36
+ # Followed by totals and an explicit "dry run — no push" notice.
37
+ # --force Override the safety-abort when the public branch is ahead
38
+ # of the recorded last mirror.
39
+ # -h|--help Print usage and exit.
40
+ #
41
+ # Env overrides:
42
+ # MIRROR_PUBLIC_URL_OVERRIDE Swap the configured publicRepoUrl at
43
+ # runtime (used for local-remote testing).
44
+ #
45
+ # The script NEVER modifies the private repo's .git/ or working tree.
46
+ # ---------------------------------------------------------------------------
47
+
48
+ set -euo pipefail
49
+ IFS=$'\n\t'
50
+
51
+ # -----------------------------------------------------------------------------
52
+ # Helpers
53
+ # -----------------------------------------------------------------------------
54
+
55
+ die() {
56
+ printf 'mirror.sh: error: %s\n' "$*" >&2
57
+ exit 1
58
+ }
59
+
60
+ log() {
61
+ printf 'mirror.sh: %s\n' "$*"
62
+ }
63
+
64
+ usage() {
65
+ cat <<'EOF'
66
+ Usage: mirror.sh [--dry-run] [--force] [-h|--help]
67
+
68
+ Options:
69
+ --dry-run Read-only preview. Prints the exact list of files that would
70
+ ship and the list of files blocked by .publicignore, then
71
+ exits with no commit and no push.
72
+ --force Override the safety abort when the public branch has moved
73
+ since the last mirror.
74
+ -h, --help Show this message and exit.
75
+
76
+ Env:
77
+ MIRROR_PUBLIC_URL_OVERRIDE Override the configured publicRepoUrl.
78
+ EOF
79
+ }
80
+
81
+ # -----------------------------------------------------------------------------
82
+ # Locate script + repo root
83
+ # -----------------------------------------------------------------------------
84
+
85
+ SCRIPT_PATH="${BASH_SOURCE[0]}"
86
+ # Resolve symlinks to a real path so SCRIPT_DIR points at the on-disk file.
87
+ while [ -h "$SCRIPT_PATH" ]; do
88
+ LINK_TARGET="$(readlink "$SCRIPT_PATH")"
89
+ case "$LINK_TARGET" in
90
+ /*) SCRIPT_PATH="$LINK_TARGET" ;;
91
+ *) SCRIPT_PATH="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)/$LINK_TARGET" ;;
92
+ esac
93
+ done
94
+ SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
95
+ SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
96
+
97
+ REPO_ROOT="$(git -C "$SKILL_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
98
+ [ -n "$REPO_ROOT" ] || die "could not locate private repo root (git rev-parse failed from $SKILL_DIR)"
99
+
100
+ CONFIG_FILE="$SKILL_DIR/assets/config.json"
101
+ [ -f "$CONFIG_FILE" ] || die "config not found: $CONFIG_FILE"
102
+
103
+ PUBLICIGNORE="$REPO_ROOT/.publicignore"
104
+ [ -f "$PUBLICIGNORE" ] || die ".publicignore not found at $PUBLICIGNORE (should have been created in T01)"
105
+
106
+ # -----------------------------------------------------------------------------
107
+ # Parse flags
108
+ # -----------------------------------------------------------------------------
109
+
110
+ DRY_RUN=0
111
+ FORCE=0
112
+
113
+ while [ $# -gt 0 ]; do
114
+ case "$1" in
115
+ --dry-run) DRY_RUN=1 ;;
116
+ --force) FORCE=1 ;;
117
+ -h|--help) usage; exit 0 ;;
118
+ *) usage >&2; die "unknown argument: $1" ;;
119
+ esac
120
+ shift
121
+ done
122
+
123
+ # -----------------------------------------------------------------------------
124
+ # Load config (jq preferred, python3 fallback)
125
+ # -----------------------------------------------------------------------------
126
+
127
+ read_config_field() {
128
+ local field="$1"
129
+ if command -v jq >/dev/null 2>&1; then
130
+ jq -er --arg f "$field" '.[$f]' "$CONFIG_FILE" 2>/dev/null || return 1
131
+ elif command -v python3 >/dev/null 2>&1; then
132
+ python3 - "$CONFIG_FILE" "$field" <<'PY' || return 1
133
+ import json, sys
134
+ path, field = sys.argv[1], sys.argv[2]
135
+ with open(path) as fh:
136
+ data = json.load(fh)
137
+ if field not in data:
138
+ sys.exit(1)
139
+ sys.stdout.write(str(data[field]))
140
+ PY
141
+ else
142
+ die "neither jq nor python3 available to parse $CONFIG_FILE"
143
+ fi
144
+ }
145
+
146
+ PUBLIC_URL="$(read_config_field publicRepoUrl)" || die "config missing publicRepoUrl"
147
+ DEFAULT_BRANCH="$(read_config_field defaultBranch)" || die "config missing defaultBranch"
148
+ WORKTREE_REL="$(read_config_field worktreePath)" || die "config missing worktreePath"
149
+
150
+ # Env override for local-remote testing.
151
+ if [ -n "${MIRROR_PUBLIC_URL_OVERRIDE:-}" ]; then
152
+ log "using MIRROR_PUBLIC_URL_OVERRIDE=$MIRROR_PUBLIC_URL_OVERRIDE (config value ignored)"
153
+ PUBLIC_URL="$MIRROR_PUBLIC_URL_OVERRIDE"
154
+ fi
155
+
156
+ # Resolve worktree path against repo root if it's relative.
157
+ case "$WORKTREE_REL" in
158
+ /*) WORKTREE_PATH="$WORKTREE_REL" ;;
159
+ *) WORKTREE_PATH="$REPO_ROOT/$WORKTREE_REL" ;;
160
+ esac
161
+
162
+ log "repo root: $REPO_ROOT"
163
+ log "public URL: $PUBLIC_URL"
164
+ log "default branch: $DEFAULT_BRANCH"
165
+ log "worktree path: $WORKTREE_PATH"
166
+
167
+ # -----------------------------------------------------------------------------
168
+ # Prepare scratch worktree (clone if missing, fetch+reset if present)
169
+ # -----------------------------------------------------------------------------
170
+
171
+ mkdir -p "$(dirname "$WORKTREE_PATH")"
172
+
173
+ if [ ! -d "$WORKTREE_PATH/.git" ]; then
174
+ log "cloning $PUBLIC_URL -> $WORKTREE_PATH (branch: $DEFAULT_BRANCH)"
175
+ git clone --branch "$DEFAULT_BRANCH" --single-branch "$PUBLIC_URL" "$WORKTREE_PATH"
176
+ else
177
+ log "refreshing existing scratch worktree at $WORKTREE_PATH"
178
+ git -C "$WORKTREE_PATH" remote set-url origin "$PUBLIC_URL"
179
+ git -C "$WORKTREE_PATH" fetch origin "$DEFAULT_BRANCH"
180
+ git -C "$WORKTREE_PATH" checkout -B "$DEFAULT_BRANCH" "origin/$DEFAULT_BRANCH"
181
+ git -C "$WORKTREE_PATH" reset --hard "origin/$DEFAULT_BRANCH"
182
+ # Preserve the marker tag if it exists; -x would leave untracked scratch,
183
+ # but -fd is enough because we hard-reset above.
184
+ git -C "$WORKTREE_PATH" clean -fd
185
+ fi
186
+
187
+ # -----------------------------------------------------------------------------
188
+ # Safety check: last-mirror-sync tag vs origin/<branch>
189
+ # -----------------------------------------------------------------------------
190
+
191
+ MARKER_SHA="$(git -C "$WORKTREE_PATH" rev-parse -q --verify refs/tags/last-mirror-sync 2>/dev/null || true)"
192
+ REMOTE_HEAD="$(git -C "$WORKTREE_PATH" rev-parse "origin/$DEFAULT_BRANCH")"
193
+
194
+ if [ -z "$MARKER_SHA" ]; then
195
+ log "no last-mirror-sync tag found (first mirror or wiped scratch) — proceeding"
196
+ elif [ "$MARKER_SHA" = "$REMOTE_HEAD" ]; then
197
+ log "last-mirror-sync matches origin/$DEFAULT_BRANCH — safe to proceed"
198
+ else
199
+ if [ "$FORCE" -eq 1 ]; then
200
+ log "WARNING: origin/$DEFAULT_BRANCH ($REMOTE_HEAD) has moved past last-mirror-sync ($MARKER_SHA); proceeding due to --force"
201
+ else
202
+ die "origin/$DEFAULT_BRANCH ($REMOTE_HEAD) has moved past last-mirror-sync ($MARKER_SHA). The public repo has commits the mirror did not create. Re-run with --force to overwrite."
203
+ fi
204
+ fi
205
+
206
+ command -v rsync >/dev/null 2>&1 || die "rsync not installed"
207
+
208
+ # -----------------------------------------------------------------------------
209
+ # --dry-run: full read-only preview
210
+ #
211
+ # Compute + print the exact "would ship" set (from rsync --dry-run --itemize)
212
+ # and the "would be blocked" set (git-tracked/untracked candidates minus the
213
+ # ship set). Zero rsync writes, zero commits, zero pushes.
214
+ # -----------------------------------------------------------------------------
215
+
216
+ if [ "$DRY_RUN" -eq 1 ]; then
217
+ log "dry-run: computing ship + blocked sets (no rsync write, no commit, no push)"
218
+
219
+ SHIP_LIST_FILE="$(mktemp -t mirror-ship.XXXXXX)"
220
+ CANDIDATE_LIST_FILE="$(mktemp -t mirror-candidates.XXXXXX)"
221
+ BLOCKED_LIST_FILE="$(mktemp -t mirror-blocked.XXXXXX)"
222
+ # shellcheck disable=SC2064
223
+ trap "rm -f '$SHIP_LIST_FILE' '$CANDIDATE_LIST_FILE' '$BLOCKED_LIST_FILE'" EXIT
224
+
225
+ # 1. Ship list — rsync --dry-run --itemize-changes uses the SAME flag set as
226
+ # the real run below, so the emitted transfer list is exactly what the real
227
+ # run would transfer. Itemize flag format is 11 chars: YXcstpoguax
228
+ # Y (update type): >=receive, <=send, c=create local, h=hard link,
229
+ # .=no update, *=message (e.g. deleting)
230
+ # X (file type): f=file, d=directory, L=symlink, D=device, S=special
231
+ # We keep any entry that transfers/creates a file OR a symlink OR a hardlink,
232
+ # and drop directory-only entries (rsync recreates dirs implicitly) and any
233
+ # attribute-only / deletion / message lines. This matches exactly what the
234
+ # real rsync would push into the scratch worktree and therefore into the
235
+ # public remote.
236
+ rsync -a --delete \
237
+ --exclude-from="$PUBLICIGNORE" \
238
+ --exclude=".git" \
239
+ --dry-run --itemize-changes --out-format='%i %n' \
240
+ "$REPO_ROOT/" \
241
+ "$WORKTREE_PATH/" \
242
+ | awk '{
243
+ flag = $1;
244
+ first = substr(flag, 1, 1);
245
+ second = substr(flag, 2, 1);
246
+ # Skip directories — public tree recreates them implicitly.
247
+ if (second == "d") next;
248
+ # Keep only entries that transfer or create a file/symlink/hardlink.
249
+ keep = 0;
250
+ if (first == ">" || first == "<") keep = 1; # file transfer
251
+ else if (first == "c" && second == "L") keep = 1; # create symlink
252
+ else if (first == "h") keep = 1; # hard link
253
+ if (!keep) next;
254
+ # Rebuild path (everything after the flag token).
255
+ $1 = "";
256
+ sub(/^ /, "");
257
+ # Strip trailing slash just in case (should only appear on dirs, which
258
+ # we already skipped, but be defensive).
259
+ sub(/\/$/, "");
260
+ if (length($0) > 0) print $0;
261
+ }' \
262
+ | LC_ALL=C sort -u > "$SHIP_LIST_FILE"
263
+
264
+ # 2. Candidate list — every tracked file plus every untracked-not-gitignored
265
+ # file under the private repo root. This is the honest "what the maintainer
266
+ # thinks of as the repo" set.
267
+ git -C "$REPO_ROOT" ls-files -co --exclude-standard \
268
+ | LC_ALL=C sort -u > "$CANDIDATE_LIST_FILE"
269
+
270
+ # 3. Blocked = candidates \ shipped.
271
+ LC_ALL=C comm -23 "$CANDIDATE_LIST_FILE" "$SHIP_LIST_FILE" > "$BLOCKED_LIST_FILE"
272
+
273
+ SHIP_COUNT="$(wc -l < "$SHIP_LIST_FILE" | tr -d ' ')"
274
+ BLOCKED_COUNT="$(wc -l < "$BLOCKED_LIST_FILE" | tr -d ' ')"
275
+
276
+ printf '\n'
277
+ printf '=== Files that would ship (%s) ===\n' "$SHIP_COUNT"
278
+ if [ "$SHIP_COUNT" -eq 0 ]; then
279
+ printf ' (none — public tree already matches private, post-blocklist)\n'
280
+ else
281
+ cat "$SHIP_LIST_FILE"
282
+ fi
283
+
284
+ printf '\n'
285
+ printf '=== Files that would be blocked (%s) ===\n' "$BLOCKED_COUNT"
286
+ if [ "$BLOCKED_COUNT" -eq 0 ]; then
287
+ printf ' (none — .publicignore matched nothing under this repo)\n'
288
+ else
289
+ cat "$BLOCKED_LIST_FILE"
290
+ fi
291
+
292
+ printf '\n'
293
+ printf '================ mirror-public dry-run summary ================\n'
294
+ printf 'would ship : %s files\n' "$SHIP_COUNT"
295
+ printf 'would block : %s files\n' "$BLOCKED_COUNT"
296
+ printf 'public URL : %s\n' "$PUBLIC_URL"
297
+ printf 'remote branch : %s\n' "$DEFAULT_BRANCH"
298
+ printf 'dry run — no push, no commit, no remote mutation\n'
299
+ printf '===============================================================\n'
300
+
301
+ exit 0
302
+ fi
303
+
304
+ # -----------------------------------------------------------------------------
305
+ # rsync private root -> scratch worktree
306
+ # -----------------------------------------------------------------------------
307
+
308
+ log "rsync $REPO_ROOT/ -> $WORKTREE_PATH/ (excludes: .publicignore + .git)"
309
+ rsync -a --delete \
310
+ --exclude-from="$PUBLICIGNORE" \
311
+ --exclude=".git" \
312
+ "$REPO_ROOT/" \
313
+ "$WORKTREE_PATH/"
314
+
315
+ # -----------------------------------------------------------------------------
316
+ # Stage + detect changes (idempotency)
317
+ # -----------------------------------------------------------------------------
318
+
319
+ git -C "$WORKTREE_PATH" add -A
320
+
321
+ if git -C "$WORKTREE_PATH" diff --cached --quiet; then
322
+ log "nothing to mirror — public tree already matches private (post-blocklist)"
323
+ exit 0
324
+ fi
325
+
326
+ # -----------------------------------------------------------------------------
327
+ # Squash commit
328
+ # -----------------------------------------------------------------------------
329
+
330
+ PRIVATE_SHORT_SHA="$(git -C "$REPO_ROOT" rev-parse --short HEAD)"
331
+ PRIVATE_FULL_SHA="$(git -C "$REPO_ROOT" rev-parse HEAD)"
332
+ UTC_TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
333
+
334
+ COMMIT_SUBJECT="chore(mirror): sync from private at $PRIVATE_SHORT_SHA $UTC_TS"
335
+ COMMIT_TRAILER="Source-Commit: $PRIVATE_FULL_SHA"
336
+
337
+ # Ensure the scratch clone has a committer identity even in ephemeral CI/test.
338
+ if ! git -C "$WORKTREE_PATH" config user.email >/dev/null 2>&1; then
339
+ git -C "$WORKTREE_PATH" config user.email "mirror-public@jenga.local"
340
+ fi
341
+ if ! git -C "$WORKTREE_PATH" config user.name >/dev/null 2>&1; then
342
+ git -C "$WORKTREE_PATH" config user.name "jenga mirror-public"
343
+ fi
344
+
345
+ git -C "$WORKTREE_PATH" commit -m "$COMMIT_SUBJECT" -m "$COMMIT_TRAILER" >/dev/null
346
+
347
+ NEW_SHA="$(git -C "$WORKTREE_PATH" rev-parse HEAD)"
348
+
349
+ # -----------------------------------------------------------------------------
350
+ # Advance marker tag (LOCAL to scratch clone — never pushed)
351
+ # -----------------------------------------------------------------------------
352
+
353
+ git -C "$WORKTREE_PATH" tag -f last-mirror-sync HEAD >/dev/null
354
+
355
+ # -----------------------------------------------------------------------------
356
+ # Push
357
+ # -----------------------------------------------------------------------------
358
+
359
+ log "pushing $NEW_SHA -> origin/$DEFAULT_BRANCH"
360
+ git -C "$WORKTREE_PATH" push origin "$DEFAULT_BRANCH"
361
+
362
+ # -----------------------------------------------------------------------------
363
+ # Summary
364
+ # -----------------------------------------------------------------------------
365
+
366
+ FILES_CHANGED="$(git -C "$WORKTREE_PATH" diff-tree --no-commit-id --name-only -r HEAD | wc -l | tr -d ' ')"
367
+
368
+ printf '\n'
369
+ printf '================ mirror-public summary ================\n'
370
+ printf 'files changed : %s\n' "$FILES_CHANGED"
371
+ printf 'new commit : %s\n' "$NEW_SHA"
372
+ printf 'remote branch : %s\n' "$DEFAULT_BRANCH"
373
+ printf 'public URL : %s\n' "$PUBLIC_URL"
374
+ printf '=======================================================\n'
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: pi-plan
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
+ keywords:
5
+ - epic
6
+ - template
7
+ - expand
8
+ - new feature area
9
+ - project structure
10
+ examples:
11
+ - "add a new epic for X"
12
+ - "expand the project with a new feature area"
13
+ metadata:
14
+ prefered_agent: scrum-master
15
+ ---
16
+
17
+ # Pi-plan — Define and Expand Project Epics
18
+
19
+ ## When to use this skill
20
+
21
+ - **New project**: Gather project description and goals, then define the initial set of Epics.
22
+ - **Existing project**: Add one or more new Epics to an existing `PROJECT_SUMMARY.md` when the user wants to plan a major new area of work.
23
+
24
+ Detect which mode you're in by checking whether `project/PROJECT_SUMMARY.md` already exists and has Epics defined.
25
+
26
+ ---
27
+
28
+ ## Mode 1: New Project
29
+
30
+ 1. **Gather project info** — Ask the user for:
31
+ - **Description**: A high-level explanation of what the app or service does (an "elevator pitch").
32
+ - **Goals**: What specific problem(s) the project is intended to solve. Be clear about the desired outcome.
33
+
34
+ 2. **Clarify scope** — If the responses are unclear or lack sufficient detail, ask clarifying questions. The goal is a broad understanding — avoid delving into granular details unless they directly impact the core idea.
35
+
36
+ 3. **Define Epics** — Using the description and goals, define the Epics section of `project/PROJECT_SUMMARY.md`. Each Epic is a JSON object:
37
+
38
+ - **title**: Concise title for the Epic.
39
+ - **short_description**: Brief explanation of what this Epic aims to achieve.
40
+ - **date_added**: Today's date (YYYY-MM-DD).
41
+ - **date_started**: Set to `null` initially.
42
+ - **related_stories**: Empty array `[]` initially.
43
+
44
+ Example: `./assets/epic.json`.
45
+
46
+ Story files created under `project/board/stories/` should follow `./assets/story_template.md`.
47
+ ---
48
+
49
+ ## Mode 2: Existing Project — Adding New Epics
50
+
51
+ 1. **Read the existing file** — Load `project/PROJECT_SUMMARY.md` to understand the current project context and existing Epics. This prevents duplication and ensures new Epics are coherent with what's already there.
52
+
53
+ 2. **Understand the new scope** — Ask the user:
54
+ - What is the new capability, feature area, or phase they want to add?
55
+ - What problem does it solve, or what goal does it serve?
56
+ - Are there any dependencies on existing Epics?
57
+
58
+ 3. **Clarify if needed** — Same as Mode 1: ask follow-up questions if the scope is vague, but stay high-level.
59
+
60
+ 4. **Define new Epics** — Draft one or more new Epic objects using the same schema as above, then append them to the existing Epics list in `project/PROJECT_SUMMARY.md`. Do not modify existing Epics unless the user explicitly asks.
61
+
62
+ 5. **Confirm before writing** — Show the user the proposed new Epic(s) and get confirmation before updating the file.
@@ -0,0 +1,7 @@
1
+ {
2
+ "title": "Epic Title",
3
+ "short_description": "Brief explanation of the epic's purpose.",
4
+ "date_added": "YYYY-MM-DD",
5
+ "date_started": null,
6
+ "related_stories": []
7
+ }
@@ -0,0 +1,18 @@
1
+ # Story: <title>
2
+
3
+ **ID**: E##_S##
4
+ **Epic**: E## — <epic title>
5
+ **Status**: Pending
6
+ **Date Added**: YYYY-MM-DD
7
+
8
+ ## Description
9
+
10
+ <what this story aims to achieve>
11
+
12
+ ## Acceptance Criteria
13
+
14
+ - [ ] <criterion>
15
+
16
+ ## Tasks
17
+
18
+ <!-- Tasks will be added during implementation -->
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: proceed
3
+ description: Review project progress by checking epics and stories, optionally consulting PROJECT_SUMMARY.md and WARP.md, then continue executing the project plan.
4
+ keywords:
5
+ - proceed
6
+ - review progress
7
+ - check epics
8
+ - continue executing
9
+ examples:
10
+ - "proceed with the plan"
11
+ - "review progress and continue"
12
+ metadata:
13
+ prefered_agent: scrum-master
14
+ ---
15
+
16
+ # Proceed — Resume Project Execution
17
+
18
+ ## Instructions
19
+
20
+ 1. **Assess progress** — Read `project/PROJECT_SUMMARY.md`, then check `project/board/epics/`, `project/board/stories/`, and `project/board/tasks/` to determine how far the project has come and what is outstanding.
21
+
22
+ 2. **Check queues** — Review `project/queue/scrum_triggers.jsonl` for any pending triggers (rollup reviews, rapport reviews, status reviews). Process them first before deciding on next steps.
23
+
24
+ 3. **Determine the next action**:
25
+ - If there are tasks in `Pending` or `In Progress` status that have not yet been assigned to the developer, identify them.
26
+ - If outstanding tasks are ready for implementation, write a session handoff to `project/queue/.session_handoff.json` with `"status": "planning_complete"` so that `on_session_end.sh` routes them to the developer queue.
27
+ - If all tasks are complete, check for epic/story rollup and update board statuses accordingly.
28
+
29
+ 4. **Report** a clear summary to the user: what is done, what is in progress, what is next — and which agent will handle it.