@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
package/bin/jenga.js ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * jenga — Jenga Agent CLI
4
+ * Usage: jenga <command> [options]
5
+ */
6
+ import { readFileSync } from "fs";
7
+ import { join, dirname } from "path";
8
+ import { fileURLToPath } from "url";
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8"));
12
+
13
+ const [,, cmd, ...args] = process.argv;
14
+
15
+ const USAGE = `
16
+ jenga — Jenga Agent CLI v${pkg.version}
17
+
18
+ Usage:
19
+ jenga init Run the setup wizard (creates jenga.cli.json, registers hook)
20
+ jenga attach Write MCP config for this project so new sessions route through Jenga
21
+ jenga status Show router status and active session
22
+
23
+ Options:
24
+ --version, -v Print version
25
+ --help, -h Print this help message
26
+ `.trim();
27
+
28
+ async function main() {
29
+ if (!cmd || cmd === "--help" || cmd === "-h") {
30
+ console.log(USAGE);
31
+ process.exit(0);
32
+ }
33
+
34
+ if (cmd === "--version" || cmd === "-v") {
35
+ console.log(pkg.version);
36
+ process.exit(0);
37
+ }
38
+
39
+ switch (cmd) {
40
+ case "init": {
41
+ const { runInit } = await import("../lib/commands/init.js");
42
+ await runInit(args);
43
+ break;
44
+ }
45
+ case "start": {
46
+ const { runStart } = await import("../lib/commands/start.js");
47
+ await runStart(args);
48
+ break;
49
+ }
50
+ case "attach": {
51
+ const { runAttach } = await import("../lib/commands/attach.js");
52
+ await runAttach(args);
53
+ break;
54
+ }
55
+ case "status": {
56
+ const { runStatus } = await import("../lib/commands/status.js");
57
+ await runStatus(args);
58
+ break;
59
+ }
60
+ default:
61
+ console.error(`Unknown command: ${cmd}\n`);
62
+ console.log(USAGE);
63
+ process.exit(1);
64
+ }
65
+ }
66
+
67
+ main().catch(err => {
68
+ console.error(err.message);
69
+ process.exit(1);
70
+ });
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+ # hooks/copilot_session_end.sh
3
+ #
4
+ # Copilot CLI session-end entry point.
5
+ #
6
+ # GitHub Copilot CLI does not fire a native SessionEnd hook. Call this script
7
+ # manually at the end of a Copilot session or as a post-step in any skill
8
+ # that completes a significant pipeline stage (e.g. commit, lgtm).
9
+ #
10
+ # This wrapper:
11
+ # 1. Sources lib/resolve-project-dir.sh to export JENGA_PROJECT_DIR,
12
+ # JENGA_AGENT_TYPE, and JENGA_SESSION_ID (idempotent — safe to call
13
+ # even if a parent script already sourced the resolver).
14
+ # 2. Delegates to hooks/on_session_end.sh for all shared cleanup logic:
15
+ # queue routing, rapport detection, handoff file processing, and
16
+ # todo cleanup.
17
+ #
18
+ # Usage:
19
+ # bash hooks/copilot_session_end.sh
20
+ #
21
+ # Environment (optional — resolver provides defaults):
22
+ # JENGA_PROJECT_DIR — override project root (default: git root or pwd)
23
+ # JENGA_AGENT_TYPE — override agent type (default: "generic")
24
+ # JENGA_SESSION_ID — override session ID (default: uuidgen / timestamp)
25
+
26
+ # shellcheck source=lib/resolve-project-dir.sh
27
+ source "$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh"
28
+
29
+ exec bash "$JENGA_PROJECT_DIR/hooks/on_session_end.sh" "$@"
@@ -0,0 +1,238 @@
1
+ #!/bin/bash
2
+ # .claude/hooks/on_session_end.sh
3
+ # Triggered on SessionEnd by all agents (scrum-master, developer, tester).
4
+ #
5
+ # Responsibilities:
6
+ # 1. Log a sender event to events.json
7
+ # 2. Detect new problem rapports using a manifest (not a timestamp)
8
+ # and write trigger payloads to the scrum master queue
9
+ # 3. Write a status-review trigger to the scrum master queue
10
+ # 4. Read .session_handoff.json (if present) and route the assignment
11
+ # to the correct next agent queue, then delete the handoff file
12
+ #
13
+ # Pipeline routing (section 4):
14
+ # scrum-master planning_complete → developer_triggers.jsonl
15
+ # developer implementation_complete → tester_triggers.jsonl
16
+ # tester passed / passed_with_remarks → scrum_triggers.jsonl (story_rollup)
17
+ # tester failed / error → developer_triggers.jsonl (rework) +
18
+ # scrum_triggers.jsonl (status_review)
19
+ #
20
+ # NOTE: Claude Code hooks cannot spawn a named agent session directly.
21
+ # Instead, we write structured trigger payloads to the appropriate queue
22
+ # files. Each agent reads its own queue at the start of its next session.
23
+
24
+ # shellcheck source=lib/resolve-project-dir.sh
25
+ source "$(git rev-parse --show-toplevel)/lib/resolve-project-dir.sh"
26
+
27
+ PROJECT_DIR="$JENGA_PROJECT_DIR"
28
+ RAPPORT_DIR="$PROJECT_DIR/project/rapports/problems"
29
+ MANIFEST="$PROJECT_DIR/.claude/hooks/.rapport_manifest.json"
30
+ QUEUE_DIR="$PROJECT_DIR/project/queue"
31
+ QUEUE_FILE="$QUEUE_DIR/scrum_triggers.jsonl"
32
+ DEV_QUEUE="$QUEUE_DIR/developer_triggers.jsonl"
33
+ TESTER_QUEUE="$QUEUE_DIR/tester_triggers.jsonl"
34
+ HANDOFF_FILE="$QUEUE_DIR/.session_handoff.json"
35
+ EVENTS_LOG="$PROJECT_DIR/project/logs/events.json"
36
+ AGENT="${JENGA_AGENT_TYPE:-unknown}"
37
+ SESSION_ID="${JENGA_SESSION_ID:-}"
38
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
39
+
40
+ mkdir -p "$PROJECT_DIR/project/logs" "$QUEUE_DIR"
41
+
42
+ # --- 1. Log sender object ---
43
+
44
+ SENDER=$(jq -n \
45
+ --arg agent "$AGENT" \
46
+ --arg session_id "$SESSION_ID" \
47
+ --arg date "$TIMESTAMP" \
48
+ '{
49
+ sender: {
50
+ agent: $agent,
51
+ session_id: $session_id,
52
+ task_id: "",
53
+ story_id: "",
54
+ epic_id: "",
55
+ date: $date,
56
+ paths: [],
57
+ worktree: ""
58
+ }
59
+ }')
60
+
61
+ if [ -f "$EVENTS_LOG" ]; then
62
+ jq --argjson entry "$SENDER" '. += [$entry]' "$EVENTS_LOG" > /tmp/events_tmp.json \
63
+ && mv /tmp/events_tmp.json "$EVENTS_LOG"
64
+ else
65
+ echo "[$SENDER]" > "$EVENTS_LOG"
66
+ fi
67
+
68
+ # --- 2. Manifest-based rapport detection ---
69
+ # Use a JSON array of known filenames instead of a mtime sentinel file.
70
+ # This prevents silently missing rapports written before the session ends
71
+ # but after the sentinel was last touched.
72
+
73
+ if [ ! -f "$MANIFEST" ]; then
74
+ echo "[]" > "$MANIFEST"
75
+ fi
76
+
77
+ if [ -d "$RAPPORT_DIR" ]; then
78
+ # Collect current rapport files (exclude .IGNORE.md files — already resolved)
79
+ # Results are stored in a bash array to avoid word-splitting on paths.
80
+ mapfile -t CURRENT_FILES < <(find "$RAPPORT_DIR" -name "*.md" ! -name "*.IGNORE.md" 2>/dev/null | sort)
81
+
82
+ # Identify new files not present in the manifest
83
+ NEW_FILES=()
84
+ for file in "${CURRENT_FILES[@]}"; do
85
+ known=$(jq --arg f "$file" 'index($f) != null' "$MANIFEST" 2>/dev/null)
86
+ if [ "$known" != "true" ]; then
87
+ NEW_FILES+=("$file")
88
+ fi
89
+ done
90
+
91
+ if [ "${#NEW_FILES[@]}" -gt 0 ]; then
92
+ echo "New rapport(s) detected: ${#NEW_FILES[@]} file(s). Writing trigger to queue."
93
+
94
+ # Build a safe JSON array of file paths — never interpolate paths into strings
95
+ FILES_JSON=$(printf '%s\n' "${NEW_FILES[@]}" | jq -R . | jq -s .)
96
+
97
+ TRIGGER=$(jq -n \
98
+ --arg type "rapport_review" \
99
+ --arg agent "$AGENT" \
100
+ --arg session_id "$SESSION_ID" \
101
+ --arg date "$TIMESTAMP" \
102
+ --argjson files "$FILES_JSON" \
103
+ '{
104
+ type: $type,
105
+ sender: { agent: $agent, session_id: $session_id, date: $date },
106
+ rapport_files: $files,
107
+ message: "New problem rapport(s) detected. Review each file and either create a backlog item or set the affected task/story/epic status to Failed with a rapport reference. Report back to the user with a summary."
108
+ }')
109
+
110
+ echo "$TRIGGER" >> "$QUEUE_FILE"
111
+
112
+ # Update the manifest to include all current files
113
+ printf '%s\n' "${CURRENT_FILES[@]}" | jq -R . | jq -s . > "$MANIFEST"
114
+ fi
115
+ fi
116
+
117
+ # --- 3. Status-review trigger ---
118
+
119
+ TRIGGER=$(jq -n \
120
+ --arg type "status_review" \
121
+ --arg agent "$AGENT" \
122
+ --arg session_id "$SESSION_ID" \
123
+ --arg date "$TIMESTAMP" \
124
+ '{
125
+ type: $type,
126
+ sender: { agent: $agent, session_id: $session_id, date: $date },
127
+ message: "A session has just ended. Review the scrum board and update the status of any tasks, stories, or epics where status may have changed based on recent activity. Check story and epic rollup. Report back to the user with a summary of what changed."
128
+ }')
129
+
130
+ echo "$TRIGGER" >> "$QUEUE_FILE"
131
+
132
+ # --- 4. Session handoff routing ---
133
+ # Each agent writes .session_handoff.json before its session ends.
134
+ # This section reads that file and routes the work to the correct next
135
+ # agent queue so the pipeline continues automatically.
136
+
137
+ if [ -f "$HANDOFF_FILE" ]; then
138
+ HANDOFF_AGENT=$(jq -r '.agent // empty' "$HANDOFF_FILE" 2>/dev/null)
139
+ HANDOFF_STATUS=$(jq -r '.status // empty' "$HANDOFF_FILE" 2>/dev/null)
140
+
141
+ case "$HANDOFF_AGENT" in
142
+
143
+ scrum-master)
144
+ # Planning phase complete — forward tasks to developer queue
145
+ if [ "$HANDOFF_STATUS" = "planning_complete" ]; then
146
+ TRIGGER=$(jq -n \
147
+ --slurpfile h "$HANDOFF_FILE" \
148
+ --arg type "implementation_assignment" \
149
+ --arg date "$TIMESTAMP" \
150
+ '{
151
+ type: $type,
152
+ date: $date,
153
+ sender: { agent: "scrum-master", session_id: $h[0].session_id, date: $date },
154
+ task_ids: ($h[0].task_ids // []),
155
+ story_id: ($h[0].story_id // ""),
156
+ epic_id: ($h[0].epic_id // ""),
157
+ message: "Scrum master planning complete. Implement the assigned tasks."
158
+ }')
159
+ echo "$TRIGGER" >> "$DEV_QUEUE"
160
+ echo "[on_session_end] scrum-master → developer queue: implementation_assignment"
161
+ fi
162
+ ;;
163
+
164
+ developer)
165
+ # Implementation complete — forward to tester queue
166
+ if [ "$HANDOFF_STATUS" = "implementation_complete" ]; then
167
+ TRIGGER=$(jq -n \
168
+ --slurpfile h "$HANDOFF_FILE" \
169
+ --arg type "test_assignment" \
170
+ --arg date "$TIMESTAMP" \
171
+ '{
172
+ type: $type,
173
+ date: $date,
174
+ sender: { agent: "developer", session_id: $h[0].session_id, date: $date },
175
+ task_id: ($h[0].task_id // ""),
176
+ story_id: ($h[0].story_id // ""),
177
+ epic_id: ($h[0].epic_id // ""),
178
+ worktree: ($h[0].worktree // ""),
179
+ paths: ($h[0].paths // []),
180
+ message: "Developer session complete. Test the implementation in the assigned worktree."
181
+ }')
182
+ echo "$TRIGGER" >> "$TESTER_QUEUE"
183
+ echo "[on_session_end] developer → tester queue: test_assignment"
184
+ fi
185
+ ;;
186
+
187
+ tester)
188
+ # Tester session complete — always write story_rollup to scrum-master
189
+ ROLLUP=$(jq -n \
190
+ --slurpfile h "$HANDOFF_FILE" \
191
+ --arg type "story_rollup" \
192
+ --arg date "$TIMESTAMP" \
193
+ '{
194
+ type: $type,
195
+ date: $date,
196
+ sender: { agent: "tester", session_id: $h[0].session_id, date: $date },
197
+ task_id: ($h[0].task_id // ""),
198
+ story_id: ($h[0].story_id // ""),
199
+ epic_id: ($h[0].epic_id // ""),
200
+ test_status: ($h[0].status // "unknown"),
201
+ rapport_file: ($h[0].rapport_file // ""),
202
+ message: ("Tester session complete with status: " + ($h[0].status // "unknown") + ". Check rollup and update board accordingly.")
203
+ }')
204
+ echo "$ROLLUP" >> "$QUEUE_FILE"
205
+ echo "[on_session_end] tester → scrum-master queue: story_rollup (status=$HANDOFF_STATUS)"
206
+
207
+ # Tests failed — also route back to developer for rework
208
+ if [ "$HANDOFF_STATUS" = "failed" ] || [ "$HANDOFF_STATUS" = "error" ]; then
209
+ REWORK=$(jq -n \
210
+ --slurpfile h "$HANDOFF_FILE" \
211
+ --arg type "rework_assignment" \
212
+ --arg date "$TIMESTAMP" \
213
+ '{
214
+ type: $type,
215
+ date: $date,
216
+ sender: { agent: "tester", session_id: $h[0].session_id, date: $date },
217
+ task_id: ($h[0].task_id // ""),
218
+ story_id: ($h[0].story_id // ""),
219
+ epic_id: ($h[0].epic_id // ""),
220
+ worktree: ($h[0].worktree // ""),
221
+ rapport_file: ($h[0].rapport_file // ""),
222
+ message: "Tests failed. Address the findings in the rapport and re-implement before calling the tester again."
223
+ }')
224
+ echo "$REWORK" >> "$DEV_QUEUE"
225
+ echo "[on_session_end] tester (failed) → developer queue: rework_assignment"
226
+ fi
227
+ ;;
228
+
229
+ esac
230
+
231
+ # Consume the handoff file — it is single-use
232
+ rm -f "$HANDOFF_FILE"
233
+ fi
234
+
235
+ # --- 5. Todo cleanup ---
236
+ # Remove project/todo.md if it is effectively empty (only blanks, # Todo, and HTML comments).
237
+ # Runs unconditionally on every session end regardless of agent type.
238
+ bash "$PROJECT_DIR/scripts/todo_cleanup.sh"
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ # hooks/prompt_router.sh
3
+ # Claude Code UserPromptSubmit hook — routes prompts through the Jenga Router.
4
+ # Reads JSON from stdin, routes through Jenga Router, outputs JSON to stdout.
5
+ # Falls back to passthrough if the router is unreachable.
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ HELPER="$SCRIPT_DIR/prompt_router_helper.js"
9
+
10
+ # Pass stdin through the Node.js helper
11
+ exec node "$HELPER"
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ // hooks/prompt_router_helper.js
3
+ // Companion helper for prompt_router.sh — implements Claude Code UserPromptSubmit logic.
4
+ // Reads JSON payload from stdin, checks if the Jenga Router is running,
5
+ // and either routes the prompt or passes it through unchanged.
6
+
7
+ "use strict";
8
+ const { readFileSync, existsSync } = require("fs");
9
+ const { join } = require("path");
10
+
11
+ const projectRoot = join(__dirname, "..");
12
+
13
+ // Read stdin (JSON payload from Claude Code: {"prompt": "..."})
14
+ let input;
15
+ try {
16
+ const raw = readFileSync("/dev/stdin", "utf8").trim();
17
+ input = JSON.parse(raw);
18
+ } catch {
19
+ process.stdout.write("{}");
20
+ process.exit(0);
21
+ }
22
+
23
+ const prompt = input.prompt || "";
24
+
25
+ if (!prompt) {
26
+ process.stdout.write(JSON.stringify(input));
27
+ process.exit(0);
28
+ }
29
+
30
+ // Check if the Jenga Router is running via its PID file
31
+ const pidFile = join(projectRoot, "mcp", "router", ".pid");
32
+ if (!existsSync(pidFile)) {
33
+ // Router not running — passthrough
34
+ process.stdout.write(JSON.stringify(input));
35
+ process.exit(0);
36
+ }
37
+
38
+ let pid;
39
+ try {
40
+ pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10);
41
+ process.kill(pid, 0); // Signal 0 just checks if the process is alive
42
+ } catch {
43
+ // Router not responsive — passthrough
44
+ process.stdout.write(JSON.stringify(input));
45
+ process.exit(0);
46
+ }
47
+
48
+ // Router is alive — call it with a ≤ 500ms timeout.
49
+ // timeout enforced by calling code (e.g. AbortController / Promise.race in future router client)
50
+ // Router client integration TBD; passthrough until wired up.
51
+ process.stdout.write(JSON.stringify(input));
52
+ process.exit(0);
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ // hooks/session_end_helper.js
3
+ // Detects and strips [JENGA:SESSION_END:<skill>] from Claude output.
4
+
5
+ import { readFileSync } from "fs";
6
+
7
+ const SIGNAL_RE = /\[JENGA:SESSION_END:([a-zA-Z0-9_-]+)\]/;
8
+
9
+ const input = readFileSync("/dev/stdin", "utf8");
10
+ const lines = input.split("\n");
11
+ const filteredLines = [];
12
+ let detectedSkill = null;
13
+
14
+ for (const line of lines) {
15
+ const match = line.match(SIGNAL_RE);
16
+ if (match) {
17
+ detectedSkill = match[1];
18
+ // Skip this line (strip it from output)
19
+ } else {
20
+ filteredLines.push(line);
21
+ }
22
+ }
23
+
24
+ if (detectedSkill) {
25
+ process.stderr.write(`[jenga-router] Session ended: ${detectedSkill}\n`);
26
+ // TODO: Call router end_session MCP tool (E11_S04)
27
+ }
28
+
29
+ process.stdout.write(filteredLines.join("\n"));
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ # hooks/session_end_watcher.sh
3
+ # Detects [JENGA:SESSION_END:<skill>] in output, signals router, strips marker.
4
+
5
+ SIGNAL_PATTERN='\[JENGA:SESSION_END:[a-zA-Z0-9_-]+\]'
6
+
7
+ # Read all input
8
+ INPUT=$(cat)
9
+
10
+ # Check for signal
11
+ SIGNAL_LINE=$(echo "$INPUT" | grep -oE "$SIGNAL_PATTERN" | head -1)
12
+
13
+ if [ -n "$SIGNAL_LINE" ]; then
14
+ # Extract skill name
15
+ SKILL=$(echo "$SIGNAL_LINE" | sed 's/\[JENGA:SESSION_END:\(.*\)\]/\1/')
16
+
17
+ # TODO: Call end_session on router via MCP (wired in E11_S04)
18
+ echo "[jenga-router] Session ended: $SKILL" >&2
19
+
20
+ # Strip the signal line from output
21
+ echo "$INPUT" | grep -vE "$SIGNAL_PATTERN"
22
+ else
23
+ echo "$INPUT"
24
+ fi
@@ -0,0 +1,47 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
2
+ import { join, dirname, resolve } from "path";
3
+ import { createRequire } from "module";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const packageRoot = join(__dirname, "..", "..");
8
+
9
+ export async function runAttach(args) {
10
+ const cwd = process.cwd();
11
+ const configPath = join(cwd, "jenga.cli.json");
12
+
13
+ if (!existsSync(configPath)) {
14
+ console.error("No jenga.cli.json found. Run `jenga init` first.");
15
+ process.exit(1);
16
+ }
17
+
18
+ // Resolve absolute path to the jenga router script
19
+ const routerPath = resolve(packageRoot, "mcp", "router", "index.js");
20
+
21
+ // Read or initialise .claude/settings.json
22
+ const settingsDir = join(cwd, ".claude");
23
+ const settingsPath = join(settingsDir, "settings.json");
24
+ let settings = {};
25
+ if (existsSync(settingsPath)) {
26
+ try {
27
+ settings = JSON.parse(readFileSync(settingsPath, "utf8"));
28
+ } catch (e) {
29
+ console.error(`Failed to parse .claude/settings.json: ${e.message}`);
30
+ process.exit(1);
31
+ }
32
+ }
33
+
34
+ // Merge jenga mcpServers entry (overwrite if already present)
35
+ if (!settings.mcpServers) settings.mcpServers = {};
36
+ settings.mcpServers["jenga"] = {
37
+ type: "stdio",
38
+ command: "node",
39
+ args: [routerPath],
40
+ };
41
+
42
+ mkdirSync(settingsDir, { recursive: true });
43
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
44
+
45
+ console.log("Attached. Open a new session in this project to start routing through Jenga.");
46
+ console.log(" ✓ .claude/settings.json updated");
47
+ }