@jenga-ai/agent 1.0.1 → 1.1.1
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 +10 -7
- package/agents/developer.md +82 -2
- package/agents/scrum-master.md +215 -21
- package/agents/tester.md +90 -8
- package/hooks/on_session_end.sh +171 -20
- package/mcp/router/embedder.js +1 -1
- package/mcp/training_runner/index.js +239 -0
- package/mcp/training_runner/package-lock.json +1065 -0
- package/mcp/training_runner/package.json +15 -0
- package/package.json +14 -16
- package/scripts/check-permission-level.sh +107 -0
- package/scripts/check-publicignore-match.sh +122 -0
- package/scripts/check-worktree-liveness.sh +193 -0
- package/scripts/generate-rapport-manifest.sh +43 -0
- package/scripts/idea_manager.sh +47 -0
- package/scripts/install-worktree-commit-guard.sh +134 -0
- package/scripts/jenga-permission-level-switch.sh +109 -0
- package/scripts/smoke-harness.sh +139 -0
- package/scripts/validate-board.sh +62 -0
- package/scripts/with-lock.sh +158 -0
- package/scripts/worktree-remove-guard.sh +204 -0
- package/skills/clearify/SKILL.md +52 -0
- package/skills/close-story/SKILL.md +203 -0
- package/skills/close-story/scripts/check-story-closeable.sh +195 -0
- package/skills/close-story/scripts/compute-scope-divergence.sh +128 -0
- package/skills/close-story/scripts/extract-diff-stats.sh +48 -0
- package/skills/close-story/scripts/extract-task-diff-stats.sh +97 -0
- package/skills/close-story/scripts/update-task-frontmatter.sh +103 -0
- package/skills/commit/SKILL.md +30 -3
- package/skills/distribute/CONFIG_SCHEMA.md +148 -0
- package/skills/distribute/SKILL.md +173 -0
- package/skills/distribute/scripts/check-version.sh +74 -0
- package/skills/distribute/scripts/commit-version-bump.sh +108 -0
- package/skills/distribute/scripts/distribute-changes.sh +381 -0
- package/skills/do/SKILL.md +352 -1
- package/skills/do/assets/intent-vs-diff-prompt.md +69 -0
- package/skills/doc/assets/path-objectives.yaml +13 -0
- package/skills/doc-sync/SKILL.md +16 -0
- package/skills/doc-sync/assets/doc_targets.md +11 -0
- package/skills/idea/SKILL.md +56 -0
- package/skills/idea/assets/idea_handoff_template.md +26 -0
- package/skills/idea/assets/idea_template.md +3 -0
- package/skills/init/SKILL.md +101 -7
- package/skills/init/assets/directory_structure.txt +1 -0
- package/skills/init/assets/strategy_stub_template.md +38 -0
- package/skills/init/assets/workflow_template.json +1 -1
- package/skills/init/scripts/apply-project-visibility.sh +176 -0
- package/skills/init/scripts/detect-existing-codebase.sh +166 -0
- package/skills/init/scripts/init.sh +35 -1
- package/skills/jenga/SKILL.md +206 -14
- package/skills/jenga/scripts/board-scan.sh +238 -0
- package/skills/jenga/scripts/cascade-resolve.sh +297 -0
- package/skills/jenga/scripts/render-confirmation.sh +679 -0
- package/skills/jenga/scripts/render-picker.sh +439 -0
- package/skills/jenga/scripts/resolve-id.sh +367 -0
- package/skills/jenga-permission-level/SKILL.md +81 -0
- package/skills/proceed/SKILL.md +1 -1
- package/skills/publish/SKILL.md +8 -5
- package/skills/publish/assets/ci-contract.md +2 -2
- package/skills/publish/assets/ownership-matrix.md +1 -1
- package/skills/publish/scripts/finalize_changelog.sh +115 -0
- package/skills/publish/scripts/generate_release_notes.sh +475 -28
- package/skills/publish/scripts/npm_ci_pipeline.sh +44 -6
- package/skills/publish/scripts/publish_deploy.sh +38 -8
- package/skills/publish/scripts/run_gates.sh +2 -2
- package/skills/reconcile/SKILL.md +117 -5
- package/skills/reconcile/scripts/detect-unlinked-code.sh +741 -0
- package/skills/skillify/assets/init-new/assets/directory_structure.txt +5 -1
- package/skills/spinoff/SKILL.md +12 -7
- package/skills/todo/SKILL.md +2 -0
- package/skills/uncharted/SKILL.md +711 -0
- package/skills/uncharted/assets/SEGMENT_PROPOSAL_TEMPLATE.md +129 -0
- package/skills/uncharted/assets/UNDERSTANDING_DOC_TEMPLATE.md +160 -0
- package/skills/uncharted/scripts/apply-subsystem-cap.sh +573 -0
- package/skills/uncharted/scripts/detect-dependencies.sh +732 -0
- package/skills/uncharted/scripts/detect-tests.sh +553 -0
- package/skills/uncharted/scripts/discover-subsystems.sh +1029 -0
- package/skills/uncharted/scripts/enumerate-target.sh +470 -0
- package/skills/uncharted/scripts/import-source.sh +517 -0
- package/skills/uncharted/scripts/inspect-provenance.sh +573 -0
- package/skills/uncharted/scripts/resolve-segment-target.sh +640 -0
- package/skills/uncharted/scripts/run-engine.sh +655 -0
- package/skills/uncharted/scripts/validate-proposed-items.sh +125 -0
- package/skills/uncharted/scripts/write-backfilled-epics.sh +498 -0
- package/skills/wtf/SKILL.md +20 -0
- package/templates/CHANGELOG_TEMPLATE.md +13 -0
- package/templates/PROBLEM_RAPPORT_TEMPLATE.md +4 -1
- package/templates/SCRUM_BOARD_SCHEMA.md +206 -10
- package/templates/permission-levels/README.md +73 -0
- package/templates/permission-levels/level-1-locked.json +71 -0
- package/templates/permission-levels/level-2-guarded.json +64 -0
- package/templates/permission-levels/level-3-standard.json +62 -0
- package/templates/permission-levels/level-4-elevated.json +60 -0
- package/templates/permission-levels/level-5-unrestricted.json +58 -0
- package/skills/convert/SKILL.md +0 -124
- package/skills/convert/convert_cli.py +0 -235
- package/skills/convert/tests/sample.csv +0 -4
- package/skills/convert/tests/sample.json +0 -5
- package/skills/convert/tests/sample.jsonl +0 -3
- package/skills/convert/tests/sample.yaml +0 -18
- package/skills/convert/tests/sample_obj.csv +0 -2
- package/skills/convert/tests/sample_obj.json +0 -9
- package/skills/mirror-public/SKILL.md +0 -237
- package/skills/mirror-public/assets/config.json +0 -5
- package/skills/mirror-public/scripts/mirror.sh +0 -374
- package/skills/self-sync/SKILL.md +0 -73
- package/skills/self-sync/scripts/run.js +0 -136
- package/skills/train/SKILL.md +0 -116
- package/skills/train/assets/dashboard-templates/classifiers.html +0 -106
- package/skills/train/assets/dashboard-templates/nlp.html +0 -102
- package/skills/train/assets/dashboard-templates/transformers.html +0 -98
- package/skills/train/assets/results-parsers/__init__.py +0 -9
- package/skills/train/assets/results-parsers/classifiers.py +0 -84
- package/skills/train/assets/results-parsers/nlp.py +0 -88
- package/skills/train/assets/results-parsers/reporter.py +0 -154
- package/skills/train/assets/results-parsers/transformers.py +0 -120
- package/skills/train/train_cli.py +0 -786
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# compute-scope-divergence.sh — Compute scope_divergence_flag for a task
|
|
3
|
+
#
|
|
4
|
+
# Usage: compute-scope-divergence.sh <execution_scope> <actual_files_changed> <actual_lines_delta>
|
|
5
|
+
#
|
|
6
|
+
# Reads project/configs/scope-thresholds.json (resolved from git root) and compares
|
|
7
|
+
# the supplied diff stats against the thresholds that apply to <execution_scope>.
|
|
8
|
+
#
|
|
9
|
+
# Scope logic:
|
|
10
|
+
# inline — divergence if actual_files_changed > inline_max_files
|
|
11
|
+
# OR actual_lines_delta > inline_max_lines
|
|
12
|
+
# story — divergence if actual_files_changed > story_max_files
|
|
13
|
+
# task — no automatic threshold: always false
|
|
14
|
+
# epic — no automatic threshold: always false
|
|
15
|
+
# (any other value) — treated as task; always false
|
|
16
|
+
#
|
|
17
|
+
# Prints "true" or "false" on stdout and exits 0.
|
|
18
|
+
# If the config file is missing or cannot be parsed, prints "false" (non-blocking),
|
|
19
|
+
# emits a warning to stderr, and exits 0.
|
|
20
|
+
#
|
|
21
|
+
# Exit codes:
|
|
22
|
+
# 0 — success (including graceful degradation when config is absent/malformed)
|
|
23
|
+
# 1 — invalid argument count
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Argument validation
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
if [[ $# -ne 3 ]]; then
|
|
32
|
+
echo "Usage: $(basename "$0") <execution_scope> <actual_files_changed> <actual_lines_delta>" >&2
|
|
33
|
+
echo " Example: $(basename "$0") inline 2 35" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
EXECUTION_SCOPE="${1}"
|
|
38
|
+
ACTUAL_FILES="${2}"
|
|
39
|
+
ACTUAL_LINES="${3}"
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Short-circuit: task and epic scopes never flag divergence
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
if [[ "$EXECUTION_SCOPE" == "task" || "$EXECUTION_SCOPE" == "epic" ]]; then
|
|
46
|
+
echo "false"
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# Locate the config file (relative to git root)
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
|
55
|
+
|
|
56
|
+
if [[ -z "$GIT_ROOT" ]]; then
|
|
57
|
+
echo "WARNING: Not inside a git repository; cannot locate scope-thresholds.json. Skipping divergence flag." >&2
|
|
58
|
+
echo "false"
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
CONFIG_FILE="${GIT_ROOT}/project/configs/scope-thresholds.json"
|
|
63
|
+
|
|
64
|
+
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
65
|
+
echo "WARNING: scope-thresholds.json not found at '${CONFIG_FILE}'. Skipping divergence flag." >&2
|
|
66
|
+
echo "false"
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
# Parse thresholds (jq preferred, plain shell fallback)
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
INLINE_MAX_FILES=""
|
|
75
|
+
INLINE_MAX_LINES=""
|
|
76
|
+
STORY_MAX_FILES=""
|
|
77
|
+
|
|
78
|
+
if command -v jq >/dev/null 2>&1; then
|
|
79
|
+
INLINE_MAX_FILES=$(jq -r '.inline_max_files // empty' "$CONFIG_FILE" 2>/dev/null || true)
|
|
80
|
+
INLINE_MAX_LINES=$(jq -r '.inline_max_lines // empty' "$CONFIG_FILE" 2>/dev/null || true)
|
|
81
|
+
STORY_MAX_FILES=$(jq -r '.story_max_files // empty' "$CONFIG_FILE" 2>/dev/null || true)
|
|
82
|
+
else
|
|
83
|
+
# Plain shell fallback: grep for the numeric value on the matching key line
|
|
84
|
+
INLINE_MAX_FILES=$(grep '"inline_max_files"' "$CONFIG_FILE" | grep -o '[0-9][0-9]*' | head -1 || true)
|
|
85
|
+
INLINE_MAX_LINES=$(grep '"inline_max_lines"' "$CONFIG_FILE" | grep -o '[0-9][0-9]*' | head -1 || true)
|
|
86
|
+
STORY_MAX_FILES=$(grep '"story_max_files"' "$CONFIG_FILE" | grep -o '[0-9][0-9]*' | head -1 || true)
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Validate that all required thresholds were parsed
|
|
90
|
+
if [[ -z "$INLINE_MAX_FILES" || -z "$INLINE_MAX_LINES" || -z "$STORY_MAX_FILES" ]]; then
|
|
91
|
+
echo "WARNING: scope-thresholds.json is malformed or missing required keys (inline_max_files, inline_max_lines, story_max_files). Skipping divergence flag." >&2
|
|
92
|
+
echo "false"
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Ensure values are integers
|
|
97
|
+
if ! [[ "$INLINE_MAX_FILES" =~ ^[0-9]+$ && "$INLINE_MAX_LINES" =~ ^[0-9]+$ && "$STORY_MAX_FILES" =~ ^[0-9]+$ ]]; then
|
|
98
|
+
echo "WARNING: Non-integer threshold value found in scope-thresholds.json. Skipping divergence flag." >&2
|
|
99
|
+
echo "false"
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
# Apply per-scope divergence logic
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
case "$EXECUTION_SCOPE" in
|
|
108
|
+
inline)
|
|
109
|
+
if [[ "$ACTUAL_FILES" -gt "$INLINE_MAX_FILES" ]] || [[ "$ACTUAL_LINES" -gt "$INLINE_MAX_LINES" ]]; then
|
|
110
|
+
echo "true"
|
|
111
|
+
else
|
|
112
|
+
echo "false"
|
|
113
|
+
fi
|
|
114
|
+
;;
|
|
115
|
+
story)
|
|
116
|
+
if [[ "$ACTUAL_FILES" -gt "$STORY_MAX_FILES" ]]; then
|
|
117
|
+
echo "true"
|
|
118
|
+
else
|
|
119
|
+
echo "false"
|
|
120
|
+
fi
|
|
121
|
+
;;
|
|
122
|
+
*)
|
|
123
|
+
# Unknown scope — treat conservatively as no divergence
|
|
124
|
+
echo "false"
|
|
125
|
+
;;
|
|
126
|
+
esac
|
|
127
|
+
|
|
128
|
+
exit 0
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
STORY_ID="${1:-}"
|
|
5
|
+
DATE_STARTED="${2:-}"
|
|
6
|
+
DATE_COMPLETED="${3:-}"
|
|
7
|
+
|
|
8
|
+
if [[ -z "$STORY_ID" ]]; then
|
|
9
|
+
echo "Usage: extract-diff-stats.sh <story-id> [<date_started> <date_completed>]" >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Primary query — EST pattern match
|
|
14
|
+
# Collect all --stat output for commits matching the story ID pattern
|
|
15
|
+
STAT_OUTPUT=$(git log --all --stat --no-merges --grep="$STORY_ID" --pretty=format:"COMMIT:%H %s" 2>/dev/null || true)
|
|
16
|
+
|
|
17
|
+
# Parse insertions and deletions from --stat lines
|
|
18
|
+
# --stat lines look like: " 3 files changed, 45 insertions(+), 12 deletions(-)"
|
|
19
|
+
# Using portable grep (BSD and GNU compatible) instead of grep -oP
|
|
20
|
+
DIFF_ADDED=$(echo "$STAT_OUTPUT" | (grep -o '[0-9]* insertion' || true) | awk '{sum+=$1} END {print sum+0}')
|
|
21
|
+
DIFF_REMOVED=$(echo "$STAT_OUTPUT" | (grep -o '[0-9]* deletion' || true) | awk '{sum+=$1} END {print sum+0}')
|
|
22
|
+
COMMITS_MATCHED=$(git log --all --no-merges --grep="$STORY_ID" --pretty=format:"%H" 2>/dev/null | wc -l | tr -d ' ')
|
|
23
|
+
|
|
24
|
+
# Secondary query — date-window unmatched count
|
|
25
|
+
if [[ -n "$DATE_STARTED" && -n "$DATE_COMPLETED" ]]; then
|
|
26
|
+
ALL_IN_WINDOW=$(git log --all --no-merges \
|
|
27
|
+
--since="$DATE_STARTED" \
|
|
28
|
+
--until="${DATE_COMPLETED}T23:59:59" \
|
|
29
|
+
--pretty=format:"%H" 2>/dev/null || true)
|
|
30
|
+
|
|
31
|
+
MATCHED_HASHES=$(git log --all --no-merges --grep="$STORY_ID" --pretty=format:"%H" 2>/dev/null || true)
|
|
32
|
+
|
|
33
|
+
COMMITS_UNMATCHED=0
|
|
34
|
+
while IFS= read -r hash; do
|
|
35
|
+
[[ -z "$hash" ]] && continue
|
|
36
|
+
if ! echo "$MATCHED_HASHES" | grep -q "^$hash$"; then
|
|
37
|
+
COMMITS_UNMATCHED=$((COMMITS_UNMATCHED + 1))
|
|
38
|
+
fi
|
|
39
|
+
done <<< "$ALL_IN_WINDOW"
|
|
40
|
+
else
|
|
41
|
+
COMMITS_UNMATCHED="unknown"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Output structured results
|
|
45
|
+
echo "diff_added: $DIFF_ADDED"
|
|
46
|
+
echo "diff_removed: $DIFF_REMOVED"
|
|
47
|
+
echo "commits_matched: $COMMITS_MATCHED"
|
|
48
|
+
echo "commits_unmatched_in_window: $COMMITS_UNMATCHED"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# extract-task-diff-stats.sh — Extract actual git diff stats for a single task
|
|
3
|
+
#
|
|
4
|
+
# Usage: extract-task-diff-stats.sh <task-id>
|
|
5
|
+
#
|
|
6
|
+
# Finds all commits whose message contains <task-id> (EST naming convention,
|
|
7
|
+
# e.g. E32_S06_T01), aggregates the file count and net line delta across all
|
|
8
|
+
# matched commits, and prints:
|
|
9
|
+
#
|
|
10
|
+
# actual_files_changed: <N>
|
|
11
|
+
# actual_lines_delta: <N>
|
|
12
|
+
#
|
|
13
|
+
# For bundle tasks (a single commit covers multiple task IDs), each task that
|
|
14
|
+
# matches the commit receives the full bundle stats — this is the "full credit"
|
|
15
|
+
# attribution model. See skills/close-story/SKILL.md for the rationale.
|
|
16
|
+
#
|
|
17
|
+
# Exit codes:
|
|
18
|
+
# 0 — success (even when no commits are found; outputs 0 values)
|
|
19
|
+
# 1 — missing or invalid argument
|
|
20
|
+
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Argument validation
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
TASK_ID="${1:-}"
|
|
28
|
+
|
|
29
|
+
if [[ -z "$TASK_ID" ]]; then
|
|
30
|
+
echo "Usage: $(basename "$0") <task-id>" >&2
|
|
31
|
+
echo " Example: $(basename "$0") E32_S06_T01" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Validate EST format: E##_S##_T##
|
|
36
|
+
if [[ ! "$TASK_ID" =~ ^E[0-9]{2}_S[0-9]{2}_T[0-9]{2}$ ]]; then
|
|
37
|
+
echo "ERROR: Invalid task ID format '$TASK_ID'. Expected E##_S##_T## (e.g. E32_S06_T01)" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Find commits matching this task ID in the message
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
MATCHED_SHAS=$(git log --all --no-merges --grep="$TASK_ID" --pretty=format:"%H" 2>/dev/null || true)
|
|
46
|
+
|
|
47
|
+
if [[ -z "$MATCHED_SHAS" ]]; then
|
|
48
|
+
# No commits found — task may predate EST tagging or use a non-standard message
|
|
49
|
+
echo "actual_files_changed: 0"
|
|
50
|
+
echo "actual_lines_delta: 0"
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# Aggregate diff stats across all matched commits
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
TOTAL_FILES=0
|
|
59
|
+
TOTAL_INSERTIONS=0
|
|
60
|
+
TOTAL_DELETIONS=0
|
|
61
|
+
|
|
62
|
+
while IFS= read -r sha; do
|
|
63
|
+
[[ -z "$sha" ]] && continue
|
|
64
|
+
|
|
65
|
+
# Get the diff stat summary line for this commit
|
|
66
|
+
# git diff --stat <sha>~1..<sha> outputs individual file lines plus a summary:
|
|
67
|
+
# N files changed, M insertions(+), K deletions(-)
|
|
68
|
+
# We only need the last (summary) line.
|
|
69
|
+
STAT_OUTPUT=$(git diff --stat "${sha}~1..${sha}" 2>/dev/null || true)
|
|
70
|
+
|
|
71
|
+
if [[ -z "$STAT_OUTPUT" ]]; then
|
|
72
|
+
# Commit with no parent (initial commit) or empty diff — skip
|
|
73
|
+
continue
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Parse files changed from the summary line
|
|
77
|
+
# The summary line contains "N file(s) changed"
|
|
78
|
+
FILES_THIS=$(echo "$STAT_OUTPUT" | (grep -o '[0-9][0-9]* file' || true) | awk '{sum+=$1} END {print sum+0}')
|
|
79
|
+
# Parse insertions
|
|
80
|
+
INS_THIS=$(echo "$STAT_OUTPUT" | (grep -o '[0-9][0-9]* insertion' || true) | awk '{sum+=$1} END {print sum+0}')
|
|
81
|
+
# Parse deletions
|
|
82
|
+
DEL_THIS=$(echo "$STAT_OUTPUT" | (grep -o '[0-9][0-9]* deletion' || true) | awk '{sum+=$1} END {print sum+0}')
|
|
83
|
+
|
|
84
|
+
TOTAL_FILES=$((TOTAL_FILES + FILES_THIS))
|
|
85
|
+
TOTAL_INSERTIONS=$((TOTAL_INSERTIONS + INS_THIS))
|
|
86
|
+
TOTAL_DELETIONS=$((TOTAL_DELETIONS + DEL_THIS))
|
|
87
|
+
|
|
88
|
+
done <<< "$MATCHED_SHAS"
|
|
89
|
+
|
|
90
|
+
TOTAL_DELTA=$((TOTAL_INSERTIONS + TOTAL_DELETIONS))
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# Output structured results
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
echo "actual_files_changed: $TOTAL_FILES"
|
|
97
|
+
echo "actual_lines_delta: $TOTAL_DELTA"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# update-task-frontmatter.sh — Write or update a YAML key in task/story frontmatter
|
|
3
|
+
#
|
|
4
|
+
# Usage: update-task-frontmatter.sh <file-path> <key> <value>
|
|
5
|
+
#
|
|
6
|
+
# If the key already exists in the frontmatter block (between the first pair
|
|
7
|
+
# of --- delimiters), its value is replaced in-place.
|
|
8
|
+
# If the key does not exist, it is appended just before the closing ---.
|
|
9
|
+
#
|
|
10
|
+
# Works with both GNU and BSD sed (macOS-compatible — uses a temp file instead
|
|
11
|
+
# of sed -i '').
|
|
12
|
+
#
|
|
13
|
+
# Exit codes:
|
|
14
|
+
# 0 — success
|
|
15
|
+
# 1 — argument error or file not found
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# Argument validation
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
FILE_PATH="${1:-}"
|
|
24
|
+
KEY="${2:-}"
|
|
25
|
+
VALUE="${3:-}"
|
|
26
|
+
|
|
27
|
+
if [[ -z "$FILE_PATH" || -z "$KEY" || -z "$VALUE" ]]; then
|
|
28
|
+
echo "Usage: $(basename "$0") <file-path> <key> <value>" >&2
|
|
29
|
+
echo " Example: $(basename "$0") project/board/tasks/E32_S06_T01_my-task.md actual_files_changed 5" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
if [[ ! -f "$FILE_PATH" ]]; then
|
|
34
|
+
echo "ERROR: File not found: '$FILE_PATH'" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Process frontmatter
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
# Strategy:
|
|
43
|
+
# 1. Read the file line by line.
|
|
44
|
+
# 2. Track whether we are inside the frontmatter block (between --- delimiters).
|
|
45
|
+
# 3. If the key exists in frontmatter, replace the line.
|
|
46
|
+
# 4. If we reach the closing --- without having replaced the key, insert the
|
|
47
|
+
# key:value line immediately before the closing ---.
|
|
48
|
+
# 5. Write the result to a temp file and move it back.
|
|
49
|
+
|
|
50
|
+
TMPFILE=$(mktemp)
|
|
51
|
+
trap 'rm -f "$TMPFILE"' EXIT
|
|
52
|
+
|
|
53
|
+
in_frontmatter=0
|
|
54
|
+
frontmatter_open=0 # set to 1 after the opening ---
|
|
55
|
+
key_replaced=0
|
|
56
|
+
closing_found=0
|
|
57
|
+
|
|
58
|
+
while IFS= read -r line; do
|
|
59
|
+
# Detect frontmatter boundaries
|
|
60
|
+
if [[ "$line" == "---" ]]; then
|
|
61
|
+
if [[ $frontmatter_open -eq 0 ]]; then
|
|
62
|
+
# Opening ---
|
|
63
|
+
frontmatter_open=1
|
|
64
|
+
in_frontmatter=1
|
|
65
|
+
echo "$line" >> "$TMPFILE"
|
|
66
|
+
continue
|
|
67
|
+
elif [[ $in_frontmatter -eq 1 ]]; then
|
|
68
|
+
# Closing --- — if key was not found yet, insert it now
|
|
69
|
+
if [[ $key_replaced -eq 0 ]]; then
|
|
70
|
+
echo "${KEY}: ${VALUE}" >> "$TMPFILE"
|
|
71
|
+
key_replaced=1
|
|
72
|
+
fi
|
|
73
|
+
in_frontmatter=0
|
|
74
|
+
closing_found=1
|
|
75
|
+
echo "$line" >> "$TMPFILE"
|
|
76
|
+
continue
|
|
77
|
+
fi
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Replace existing key in frontmatter
|
|
81
|
+
if [[ $in_frontmatter -eq 1 && $key_replaced -eq 0 ]]; then
|
|
82
|
+
# Match lines starting with exactly this key (not a key that begins with the same prefix)
|
|
83
|
+
if [[ "$line" =~ ^${KEY}:[[:space:]]* ]]; then
|
|
84
|
+
echo "${KEY}: ${VALUE}" >> "$TMPFILE"
|
|
85
|
+
key_replaced=1
|
|
86
|
+
continue
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
echo "$line" >> "$TMPFILE"
|
|
91
|
+
done < "$FILE_PATH"
|
|
92
|
+
|
|
93
|
+
# Edge case: file had no frontmatter closing --- (malformed file)
|
|
94
|
+
if [[ $key_replaced -eq 0 && $closing_found -eq 0 ]]; then
|
|
95
|
+
echo "WARNING: No frontmatter closing --- found in '$FILE_PATH'. Key not written." >&2
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Move the temp file into place
|
|
100
|
+
mv "$TMPFILE" "$FILE_PATH"
|
|
101
|
+
trap - EXIT
|
|
102
|
+
|
|
103
|
+
exit 0
|
package/skills/commit/SKILL.md
CHANGED
|
@@ -14,16 +14,43 @@ examples:
|
|
|
14
14
|
|
|
15
15
|
# Commit — Commit Completed Work
|
|
16
16
|
|
|
17
|
+
## Inline Mode (called by /do for inline-scoped tasks)
|
|
18
|
+
|
|
19
|
+
When invoked with the `--inline` flag OR when the environment variable `JENGA_COMMIT_INLINE=1` is set, execute inline mode:
|
|
20
|
+
|
|
21
|
+
1. **Skip** the reconcile step, the `/doc-sync` scan, and the user-action prerequisites check (steps 1-3 in normal mode). No `/reconcile` invocation, `/doc-sync` invocation, or `_INSTRUCTIONS.md` lookup is performed — inline tasks are single, already-scoped-small changes that reconcile and doc-sync would add overhead to, not risk, disproportionate to the size of the change.
|
|
22
|
+
2. **Skip** any worktree merge logic — inline tasks execute in the main session with no dedicated worktree to merge.
|
|
23
|
+
3. Stage all changed files relevant to the task (use `git add -A` or specific files if a list was provided by the caller).
|
|
24
|
+
4. Commit using the EST naming convention:
|
|
25
|
+
```
|
|
26
|
+
task(<E##_S##_T##>): <short description of what was done>
|
|
27
|
+
```
|
|
28
|
+
The task ID (`E##_S##_T##`) must be taken from the context provided by `/do` — do not inspect task frontmatter independently.
|
|
29
|
+
5. **Exit** — do not check for the next epic and do not emit "All Done!" in inline mode. `/do` manages the loop and next-epic detection.
|
|
30
|
+
|
|
31
|
+
If `--inline` is absent **and** `JENGA_COMMIT_INLINE` is not set (or is not `1`), ignore this section entirely and proceed with normal mode below.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
17
35
|
## Instructions
|
|
18
36
|
|
|
19
37
|
If no epic, task, or story has been implemented, exit with the message: "No implementation to commit."
|
|
20
38
|
|
|
21
|
-
1. **
|
|
39
|
+
1. **Reconcile first** — Invoke the `/reconcile` skill before any other action, so the board is never committed in a drifted state.
|
|
40
|
+
- **If reconcile detects and corrects drift** — inform the user what changed (e.g. demoted/promoted statuses, merged orphaned worktrees, cleaned `todo.md` entries) before proceeding.
|
|
41
|
+
- **If reconcile finds no drift** — continue silently to the next step.
|
|
42
|
+
|
|
43
|
+
2. **Doc-sync scan, scoped to this change (report-only, non-blocking)** — Invoke `/doc-sync` scoped via its `source:` argument to the files actually changed by the work being committed — never a full-repo scan. Determine the changed-file list from the work being committed (e.g. `git diff --name-only HEAD` combined with untracked files from `git status --porcelain`, or the task's known changed-file set when commit context already identifies them) and pass it as `source:` so doc-sync only analyses what this commit touches.
|
|
44
|
+
- **Design decision — report-only, not blocking:** doc-sync's own step 5 ("report findings, ask before applying") is a human-approval gate. When invoked from `/commit`, doc-sync runs only through its own step 5 (report the drift findings) and explicitly does **not** proceed to its step 6 (apply updates) as part of this flow — `/commit` never surfaces doc-sync's apply-confirmation prompt mid-commit. *Rationale: nesting a second approval gate inside an already-in-flight commit either stalls a flow the user expected to complete in one shot, or trains the user to reflexively decline the nested prompt just to get their commit through. Report-only surfaces documentation drift at the cheapest possible moment to notice it — right when the change is fresh — without forcing an apply/skip decision under commit pressure; the user reviews the findings and runs `/doc-sync` standalone afterward if they want to apply them.* This introduces no change to `/doc-sync`'s own step 5 approval semantics and no new auto-apply flag — `/commit` simply never invites it past step 5.
|
|
45
|
+
- **If doc-sync reports drift findings** — surface them to the user as part of the commit output (informational), then continue to the next step regardless of the findings.
|
|
46
|
+
- **If doc-sync reports no drift** — continue silently to the next step.
|
|
47
|
+
|
|
48
|
+
3. **Verify user-action prerequisites** — Check whether an `_INSTRUCTIONS.md` file exists for this task at `project/instructions/<E##_S##_T##>_INSTRUCTIONS.md`. If the task has out-of-scope prerequisites but no instructions file was created, create one now using `assets/user_instructions_template.md`. If one already exists, surface it to the user as a reminder. (The developer should have created this file during task intake — this is a final safety check.)
|
|
22
49
|
|
|
23
|
-
|
|
50
|
+
4. **Commit** using the following format:
|
|
24
51
|
- **Epic:** `epic(<Epic Title>): <MAX_50_CHAR_SUMMARY>`
|
|
25
52
|
- **Task/Story:** `story(<Epic Title>_<Story Title>): <MAX_50_CHAR_SUMMARY>`
|
|
26
53
|
|
|
27
54
|
**Fallback: Group changes logically** — prefer one commit per coherent unit of work, but don't force splits. When in doubt, keep it together.
|
|
28
55
|
|
|
29
|
-
|
|
56
|
+
5. **Check for next epic** — If a new epic is to be started, inform the user that a new conversation should be initiated. If there are no subsequent epics left, show the message: "All Done! 🎉"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# jenga.config.json — Schema Reference
|
|
2
|
+
|
|
3
|
+
This document is the canonical reference for the `jenga.config.json` file written into **consuming projects** during framework distribution. The file is created and maintained by `distribute-changes.sh`, with the `project_files_visibility` field written by `skills/init/scripts/apply-project-visibility.sh` during `/init`; it should not be edited by hand.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
`jenga.config.json` lives at the root of a consuming project and tracks which version of the JengaAgent framework is currently installed there, where the framework files were placed, and when the last distribution occurred. It is read by the distribution script on subsequent runs to determine the target directory and detect whether an upgrade is needed.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File location
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
<project-root>/jenga.config.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Example
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"project_name": "my-project",
|
|
26
|
+
"target_dir": ".agents",
|
|
27
|
+
"version": "2.3.1",
|
|
28
|
+
"updated_at": "2026-08-11",
|
|
29
|
+
"last_distributed": "2026-08-11T10:00:00Z",
|
|
30
|
+
"source": "private",
|
|
31
|
+
"project_files_visibility": "visible"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Field reference
|
|
38
|
+
|
|
39
|
+
| Field | Type | Required | Default | Description |
|
|
40
|
+
|---|---|---|---|---|
|
|
41
|
+
| `project_name` | string | yes | — | Human-readable identifier for the consuming project. Must match the `name` field of the corresponding entry in the monorepo's `distribute.config.json`. |
|
|
42
|
+
| `target_dir` | string | yes | `.agents` | The directory under the project root where framework files are copied. `distribute-changes.sh` reads this field to resolve the destination path on every run. Change this only if the consuming project uses a non-standard layout. |
|
|
43
|
+
| `version` | string | yes | — | The JengaAgent semantic version currently installed in this project (e.g. `"2.3.1"`). Compared against the `version` field in the monorepo's `package.json` to determine whether an upgrade is required. |
|
|
44
|
+
| `updated_at` | string (ISO 8601 date) | yes | — | Date of the last successful distribution, in `YYYY-MM-DD` format. Does **not** include a time component. |
|
|
45
|
+
| `last_distributed` | string (ISO 8601 datetime) | yes | — | Full UTC timestamp of the last successful distribution, in `YYYY-MM-DDTHH:MM:SSZ` format. Provides more precision than `updated_at` and is useful for audit and ordering purposes. |
|
|
46
|
+
| `source` | string | yes | `"private"` | Distribution channel. Always `"private"` for projects that receive updates via the filesystem distribution mechanism. Distinguishes these projects from any future npm-installed consumers. Do not change this value manually. |
|
|
47
|
+
| `project_files_visibility` | string (enum) | no | `"visible"` | How JengaAgent's own working files appear in the consuming project. Exactly one of `visible` or `ignored` — no other value is accepted. Written by `/init`, not by distribution. See [Project files visibility](#project-files-visibility) below. |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Project files visibility
|
|
52
|
+
|
|
53
|
+
`project_files_visibility` controls how JengaAgent's own working files — the `project/` tree containing the scrum board, `todo.md`, `queue/`, `rapports/`, and `logs/` — appear in a consuming project.
|
|
54
|
+
|
|
55
|
+
This is distinct from `target_dir`. `target_dir` governs where the **distributed framework files** (skill and agent definitions) land; `project_files_visibility` governs the **working tree** that accumulates as the framework is used.
|
|
56
|
+
|
|
57
|
+
### Allowed values
|
|
58
|
+
|
|
59
|
+
Exactly two values are accepted. Any other value is rejected with a non-zero exit code.
|
|
60
|
+
|
|
61
|
+
| Value | On-disk effect |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `visible` | Working files stay at `project/`, tracked and visible in directory listings. Nothing on disk is changed. |
|
|
64
|
+
| `ignored` | Working files stay at `project/`, but `project/` is appended to the project's `.gitignore`, so they exist on disk and are never committed. |
|
|
65
|
+
|
|
66
|
+
#### Withdrawn: `hidden`
|
|
67
|
+
|
|
68
|
+
A third value, `hidden` (dot-prefixing `project/` to `.project/`, following the
|
|
69
|
+
same convention already used for `.agents/` and `.claude/`), was implemented
|
|
70
|
+
and tester-verified to produce the correct on-disk layout, but was withdrawn
|
|
71
|
+
before release because it is functionally broken at runtime:
|
|
72
|
+
|
|
73
|
+
- `scripts/board_resolver.sh` hardcodes `project/configs/workflow.json` as its
|
|
74
|
+
config path. It never locates the rewritten `.project/configs/workflow.json`,
|
|
75
|
+
silently falls back to a default that no longer exists, and exits `0` —
|
|
76
|
+
a silent wrong answer rather than a hard failure.
|
|
77
|
+
- `hooks/on_session_end.sh` hardcodes and unconditionally creates
|
|
78
|
+
`project/rapports/problems`, `project/queue`, and `project/logs`. Under
|
|
79
|
+
`hidden` mode this recreates a shadow `project/` tree on the very next
|
|
80
|
+
session end, splitting runtime state across `project/` and `.project/` —
|
|
81
|
+
the clutter the mode was meant to remove reappears, and some agent output
|
|
82
|
+
(e.g. queue triggers) is written to the tree the board no longer lives in.
|
|
83
|
+
|
|
84
|
+
Full findings: `project/rapports/problems/E31_S05_T01-hidden-mode-path-resolution-gaps.md`.
|
|
85
|
+
|
|
86
|
+
`hidden` is not offered by the `/init` prompt and is not accepted by
|
|
87
|
+
`apply-project-visibility.sh`. Reintroducing it requires fixing both hardcoded
|
|
88
|
+
paths above (routing them through `workflow.json` instead) — tracked as a
|
|
89
|
+
follow-up `/todo` item rather than built as part of this field.
|
|
90
|
+
|
|
91
|
+
### Default
|
|
92
|
+
|
|
93
|
+
The default is **`visible`**, used whenever `/init` runs non-interactively or the user is not prompted.
|
|
94
|
+
|
|
95
|
+
`visible` is the only value that is a genuine no-op on disk, so an unattended run can never silently relocate a user's directories or mutate their `.gitignore`. It also matches the behaviour of every project scaffolded before this field existed, making it backward-compatible for any consuming project whose `jenga.config.json` predates the field — an absent field is read as `visible`.
|
|
96
|
+
|
|
97
|
+
### Who writes it
|
|
98
|
+
|
|
99
|
+
The field is written during `/init` by `skills/init/scripts/apply-project-visibility.sh`, which also performs the corresponding on-disk change. The script merges the field into any existing `jenga.config.json` rather than overwriting the file, since `/init` normally runs before the first `/distribute` has created it.
|
|
100
|
+
|
|
101
|
+
Changing the value after the initial `/init` is not currently supported — there is no toggle or migration path. Re-running the applier with a different mode is not a supported upgrade route.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## First distribution
|
|
106
|
+
|
|
107
|
+
When `distribute-changes.sh` runs against a project for the first time and no `jenga.config.json` exists in the project root, the script creates the file from scratch. All six fields are populated using:
|
|
108
|
+
|
|
109
|
+
- `project_name` — taken from the matching entry in `distribute.config.json`
|
|
110
|
+
- `target_dir` — taken from the matching entry in `distribute.config.json` (falls back to `.agents` if absent)
|
|
111
|
+
- `version` — read from `package.json` in the monorepo at the time of distribution
|
|
112
|
+
- `updated_at` — today's date (`YYYY-MM-DD`)
|
|
113
|
+
- `last_distributed` — current UTC datetime (`YYYY-MM-DDTHH:MM:SSZ`)
|
|
114
|
+
- `source` — hardcoded to `"private"`
|
|
115
|
+
|
|
116
|
+
The directory referenced by `target_dir` is created if it does not already exist.
|
|
117
|
+
|
|
118
|
+
> **Known gap:** `distribute-changes.sh` rebuilds `jenga.config.json` from scratch on every run and emits only the six fields above, so a `project_files_visibility` value written by `/init` is dropped by the next `/distribute`. Making the rebuild preserve fields it does not own is tracked as follow-up work; until then, treat the on-disk layout (not the config field) as the source of truth for which mode a project is in.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Atomic write mechanism
|
|
123
|
+
|
|
124
|
+
To prevent a consuming project from reading a partially-written `jenga.config.json` if distribution is interrupted (e.g. by a signal or disk error), the script uses an atomic write pattern:
|
|
125
|
+
|
|
126
|
+
1. The updated JSON is written to a temporary file in the same directory as the target (e.g. `jenga.config.json.tmp`).
|
|
127
|
+
2. The temporary file is renamed over the target with a single `mv` call.
|
|
128
|
+
|
|
129
|
+
Because rename is atomic on POSIX filesystems, a reader will always see either the previous complete file or the new complete file — never a half-written intermediate state.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## The `active` flag in `distribute.config.json`
|
|
134
|
+
|
|
135
|
+
`distribute.config.json` (in the monorepo, not in consuming projects) may include an `active` field on each target entry:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"targets": [
|
|
140
|
+
{ "name": "my-project", "path": "../my-project", "active": false }
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- When `active` is `true` (or absent, which defaults to active), the target is distributed normally.
|
|
146
|
+
- When `active` is `false`, distribution is **skipped** for that target and a warning is printed to stdout. The distribution run continues to process remaining targets — an inactive entry is not treated as an error.
|
|
147
|
+
|
|
148
|
+
Use `active: false` to temporarily pause distribution to a project without removing its entry from the config.
|