@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.
- package/LICENSE +201 -0
- package/README.md +340 -0
- package/agents/ai_engineer.md +113 -0
- package/agents/developer.md +236 -0
- package/agents/scrum-master.md +349 -0
- package/agents/scrutiny-agent.md +137 -0
- package/agents/solution-assessor.md +185 -0
- package/agents/tester.md +339 -0
- package/bin/jenga.js +70 -0
- package/hooks/copilot_session_end.sh +29 -0
- package/hooks/on_session_end.sh +238 -0
- package/hooks/prompt_router.sh +11 -0
- package/hooks/prompt_router_helper.js +52 -0
- package/hooks/session_end_helper.js +29 -0
- package/hooks/session_end_watcher.sh +24 -0
- package/lib/commands/attach.js +47 -0
- package/lib/commands/init.js +207 -0
- package/lib/commands/start.js +16 -0
- package/lib/commands/status.js +53 -0
- package/lib/config-schema.js +72 -0
- package/lib/inject-settings.js +61 -0
- package/lib/mirror.js +244 -0
- package/lib/resolve-project-dir.sh +47 -0
- package/mcp/execute-ticket/index.js +10 -0
- package/mcp/execute-ticket/package.json +5 -0
- package/mcp/help/index.js +79 -0
- package/mcp/help/package.json +14 -0
- package/mcp/router/README.md +19 -0
- package/mcp/router/embedder.js +23 -0
- package/mcp/router/index.js +204 -0
- package/mcp/router/matcher.js +87 -0
- package/mcp/router/package-lock.json +1048 -0
- package/mcp/router/package.json +11 -0
- package/mcp/router/skill-index.js +104 -0
- package/package.json +47 -0
- package/scripts/board_resolver.sh +46 -0
- package/scripts/e25_s01_extract_board_graph.py +292 -0
- package/scripts/e25_s01_generate_synthetic_board.py +90 -0
- package/scripts/measurement-10x.json +50 -0
- package/scripts/measurement-10x.txt +4 -0
- package/scripts/measurement-real.json +50 -0
- package/scripts/measurement-real.txt +4 -0
- package/scripts/postinstall.js +165 -0
- package/scripts/todo_cleanup.sh +22 -0
- package/scripts/todo_manager.sh +86 -0
- package/scripts/validate-board.sh +190 -0
- package/scripts/validate-story-format.sh +53 -0
- package/skills/brainstorm/SKILL.md +47 -0
- package/skills/btw/SKILL.md +42 -0
- package/skills/commit/SKILL.md +29 -0
- package/skills/commit/assets/user_instructions_template.md +22 -0
- package/skills/continue/SKILL.md +29 -0
- package/skills/convert/SKILL.md +124 -0
- package/skills/convert/convert_cli.py +235 -0
- package/skills/convert/tests/sample.csv +4 -0
- package/skills/convert/tests/sample.json +5 -0
- package/skills/convert/tests/sample.jsonl +3 -0
- package/skills/convert/tests/sample.yaml +18 -0
- package/skills/convert/tests/sample_obj.csv +2 -0
- package/skills/convert/tests/sample_obj.json +9 -0
- package/skills/deep-dive/SKILL.md +167 -0
- package/skills/do/SKILL.md +88 -0
- package/skills/do/assets/sender_template.json +12 -0
- package/skills/doc/SKILL.md +314 -0
- package/skills/doc/assets/path-objectives.yaml +38 -0
- package/skills/doc-sync/SKILL.md +167 -0
- package/skills/doc-sync/assets/default_excludes.txt +21 -0
- package/skills/doc-sync/assets/doc_targets.md +14 -0
- package/skills/dooo/SKILL.md +60 -0
- package/skills/error/SKILL.md +29 -0
- package/skills/evaluate/SKILL.md +45 -0
- package/skills/evaluate/assets/evaluation_invokation_template.yml +3 -0
- package/skills/evaluate/assets/evaluation_rapport_template.md +24 -0
- package/skills/examplify/SKILL.md +42 -0
- package/skills/help/SKILL.md +36 -0
- package/skills/improve/SKILL.md +55 -0
- package/skills/index/scripts/board-index +4 -0
- package/skills/index/scripts/board_index.py +615 -0
- package/skills/index/scripts/smoke_test.sh +86 -0
- package/skills/init/SKILL.md +44 -0
- package/skills/init/assets/.gitignore_template +15 -0
- package/skills/init/assets/PROJECT_SUMMARY_template.md +13 -0
- package/skills/init/assets/directory_structure.txt +13 -0
- package/skills/init/assets/test-config_template.json +4 -0
- package/skills/init/assets/workflow_template.json +30 -0
- package/skills/init/scripts/init.sh +48 -0
- package/skills/jbp/SKILL.md +25 -0
- package/skills/jenga/SKILL.md +68 -0
- package/skills/lgtm/SKILL.md +21 -0
- package/skills/mirror-public/SKILL.md +237 -0
- package/skills/mirror-public/assets/config.json +5 -0
- package/skills/mirror-public/scripts/mirror.sh +374 -0
- package/skills/pi-plan/SKILL.md +62 -0
- package/skills/pi-plan/assets/epic.json +7 -0
- package/skills/pi-plan/assets/story_template.md +18 -0
- package/skills/proceed/SKILL.md +29 -0
- package/skills/publish/SKILL.md +351 -0
- package/skills/publish/adapters/droplet.md +200 -0
- package/skills/publish/adapters/mobile-ios.md +114 -0
- package/skills/publish/adapters/npm-ci.md +223 -0
- package/skills/publish/adapters/npm.md +121 -0
- package/skills/publish/assets/ExportOptions.plist.template +19 -0
- package/skills/publish/assets/ci-contract.md +111 -0
- package/skills/publish/assets/ownership-matrix.md +17 -0
- package/skills/publish/assets/publish.example.json +85 -0
- package/skills/publish/assets/publish.example.npm-ci.json +40 -0
- package/skills/publish/assets/publish.example.npm.json +41 -0
- package/skills/publish/assets/secrets-guide.md +104 -0
- package/skills/publish/schemas/fixtures/npm-ci-minimal.json +17 -0
- package/skills/publish/schemas/fixtures/npm-ci-with-empty-secrets.json +18 -0
- package/skills/publish/schemas/fixtures/npm-ci-with-workflow-path.json +18 -0
- package/skills/publish/schemas/publish.schema.json +428 -0
- package/skills/publish/scripts/check_target_config.sh +96 -0
- package/skills/publish/scripts/droplet_pipeline.sh +208 -0
- package/skills/publish/scripts/generate_release_notes.sh +200 -0
- package/skills/publish/scripts/ios_pipeline.sh +486 -0
- package/skills/publish/scripts/npm_ci_pipeline.sh +225 -0
- package/skills/publish/scripts/npm_pipeline.sh +249 -0
- package/skills/publish/scripts/publish_common.sh +253 -0
- package/skills/publish/scripts/publish_deploy.sh +538 -0
- package/skills/publish/scripts/reconcile_tags.sh +135 -0
- package/skills/publish/scripts/run_gates.sh +616 -0
- package/skills/publish/scripts/setup_wizard.sh +394 -0
- package/skills/publish/scripts/show_history.sh +95 -0
- package/skills/publish/scripts/suggest_semver_bump.sh +105 -0
- package/skills/publish/scripts/validate_config.sh +163 -0
- package/skills/publish/scripts/validate_droplet_env.sh +45 -0
- package/skills/publish/scripts/validate_ios_env.sh +68 -0
- package/skills/publish/scripts/validate_npm_ci_env.sh +71 -0
- package/skills/publish/scripts/validate_npm_env.sh +22 -0
- package/skills/publish/scripts/write_ledger_entry.sh +126 -0
- package/skills/publish/wizards/droplet.md +275 -0
- package/skills/publish/wizards/mobile-ios.md +157 -0
- package/skills/publish/wizards/npm-ci.md +240 -0
- package/skills/publish/wizards/npm.md +224 -0
- package/skills/reconcile/SKILL.md +93 -0
- package/skills/reconcile/assets/report_format.md +44 -0
- package/skills/reconcile-origin/SKILL.md +75 -0
- package/skills/reconcile-origin/scripts/reconcile-origin.sh +372 -0
- package/skills/redo/SKILL.md +70 -0
- package/skills/route/SKILL.md +180 -0
- package/skills/self-sync/SKILL.md +73 -0
- package/skills/self-sync/scripts/run.js +136 -0
- package/skills/skillify/SKILL.md +68 -0
- package/skills/skillify/assets/init-new/SKILL.md +35 -0
- package/skills/skillify/assets/init-new/assets/.gitignore_template +15 -0
- package/skills/skillify/assets/init-new/assets/PROJECT_SUMMARY_template.md +13 -0
- package/skills/skillify/assets/init-new/assets/directory_structure.txt +10 -0
- package/skills/skillify/assets/init-new/assets/test-config_template.json +4 -0
- package/skills/skillify/assets/init-new/assets/workflow_template.json +17 -0
- package/skills/skillify/assets/init-new/scripts/init.sh +48 -0
- package/skills/skillify/assets/init-old/SKILL.md +124 -0
- package/skills/spinoff/SKILL.md +48 -0
- package/skills/status/SKILL.md +33 -0
- package/skills/status/assets/output_format.md +41 -0
- package/skills/todo/SKILL.md +46 -0
- package/skills/todo/assets/todo_handoff_template.md +22 -0
- package/skills/todo/assets/todo_template.md +3 -0
- package/skills/train/SKILL.md +116 -0
- package/skills/train/assets/dashboard-templates/classifiers.html +106 -0
- package/skills/train/assets/dashboard-templates/nlp.html +102 -0
- package/skills/train/assets/dashboard-templates/transformers.html +98 -0
- package/skills/train/assets/results-parsers/__init__.py +9 -0
- package/skills/train/assets/results-parsers/classifiers.py +84 -0
- package/skills/train/assets/results-parsers/nlp.py +88 -0
- package/skills/train/assets/results-parsers/reporter.py +154 -0
- package/skills/train/assets/results-parsers/transformers.py +120 -0
- package/skills/train/train_cli.py +786 -0
- package/templates/EXECUTION_PLAN_TEMPLATE.md +43 -0
- package/templates/EXECUTION_SUMMARY_TEMPLATE.md +50 -0
- package/templates/JENGA_CONFIG_TEMPLATE.json +23 -0
- package/templates/PROBLEM_RAPPORT_TEMPLATE.md +88 -0
- package/templates/SCRUM_BOARD_SCHEMA.md +311 -0
- package/templates/SKILL.md +16 -0
- package/templates/SKILL_TEMPLATE.md +28 -0
- package/templates/USER_INSTRUCTIONS_TEMPLATE.md +22 -0
- 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
|
+
}
|