@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.
Files changed (117) hide show
  1. package/README.md +10 -7
  2. package/agents/developer.md +82 -2
  3. package/agents/scrum-master.md +215 -21
  4. package/agents/tester.md +90 -8
  5. package/hooks/on_session_end.sh +171 -20
  6. package/mcp/router/embedder.js +1 -1
  7. package/mcp/training_runner/index.js +239 -0
  8. package/mcp/training_runner/package-lock.json +1065 -0
  9. package/mcp/training_runner/package.json +15 -0
  10. package/package.json +14 -16
  11. package/scripts/check-permission-level.sh +107 -0
  12. package/scripts/check-publicignore-match.sh +122 -0
  13. package/scripts/check-worktree-liveness.sh +193 -0
  14. package/scripts/generate-rapport-manifest.sh +43 -0
  15. package/scripts/idea_manager.sh +47 -0
  16. package/scripts/install-worktree-commit-guard.sh +134 -0
  17. package/scripts/jenga-permission-level-switch.sh +109 -0
  18. package/scripts/smoke-harness.sh +139 -0
  19. package/scripts/validate-board.sh +62 -0
  20. package/scripts/with-lock.sh +158 -0
  21. package/scripts/worktree-remove-guard.sh +204 -0
  22. package/skills/clearify/SKILL.md +52 -0
  23. package/skills/close-story/SKILL.md +203 -0
  24. package/skills/close-story/scripts/check-story-closeable.sh +195 -0
  25. package/skills/close-story/scripts/compute-scope-divergence.sh +128 -0
  26. package/skills/close-story/scripts/extract-diff-stats.sh +48 -0
  27. package/skills/close-story/scripts/extract-task-diff-stats.sh +97 -0
  28. package/skills/close-story/scripts/update-task-frontmatter.sh +103 -0
  29. package/skills/commit/SKILL.md +30 -3
  30. package/skills/distribute/CONFIG_SCHEMA.md +148 -0
  31. package/skills/distribute/SKILL.md +173 -0
  32. package/skills/distribute/scripts/check-version.sh +74 -0
  33. package/skills/distribute/scripts/commit-version-bump.sh +108 -0
  34. package/skills/distribute/scripts/distribute-changes.sh +381 -0
  35. package/skills/do/SKILL.md +352 -1
  36. package/skills/do/assets/intent-vs-diff-prompt.md +69 -0
  37. package/skills/doc/assets/path-objectives.yaml +13 -0
  38. package/skills/doc-sync/SKILL.md +16 -0
  39. package/skills/doc-sync/assets/doc_targets.md +11 -0
  40. package/skills/idea/SKILL.md +56 -0
  41. package/skills/idea/assets/idea_handoff_template.md +26 -0
  42. package/skills/idea/assets/idea_template.md +3 -0
  43. package/skills/init/SKILL.md +101 -7
  44. package/skills/init/assets/directory_structure.txt +1 -0
  45. package/skills/init/assets/strategy_stub_template.md +38 -0
  46. package/skills/init/assets/workflow_template.json +1 -1
  47. package/skills/init/scripts/apply-project-visibility.sh +176 -0
  48. package/skills/init/scripts/detect-existing-codebase.sh +166 -0
  49. package/skills/init/scripts/init.sh +35 -1
  50. package/skills/jenga/SKILL.md +206 -14
  51. package/skills/jenga/scripts/board-scan.sh +238 -0
  52. package/skills/jenga/scripts/cascade-resolve.sh +297 -0
  53. package/skills/jenga/scripts/render-confirmation.sh +679 -0
  54. package/skills/jenga/scripts/render-picker.sh +439 -0
  55. package/skills/jenga/scripts/resolve-id.sh +367 -0
  56. package/skills/jenga-permission-level/SKILL.md +81 -0
  57. package/skills/proceed/SKILL.md +1 -1
  58. package/skills/publish/SKILL.md +8 -5
  59. package/skills/publish/assets/ci-contract.md +2 -2
  60. package/skills/publish/assets/ownership-matrix.md +1 -1
  61. package/skills/publish/scripts/finalize_changelog.sh +115 -0
  62. package/skills/publish/scripts/generate_release_notes.sh +475 -28
  63. package/skills/publish/scripts/npm_ci_pipeline.sh +44 -6
  64. package/skills/publish/scripts/publish_deploy.sh +38 -8
  65. package/skills/publish/scripts/run_gates.sh +2 -2
  66. package/skills/reconcile/SKILL.md +117 -5
  67. package/skills/reconcile/scripts/detect-unlinked-code.sh +741 -0
  68. package/skills/skillify/assets/init-new/assets/directory_structure.txt +5 -1
  69. package/skills/spinoff/SKILL.md +12 -7
  70. package/skills/todo/SKILL.md +2 -0
  71. package/skills/uncharted/SKILL.md +711 -0
  72. package/skills/uncharted/assets/SEGMENT_PROPOSAL_TEMPLATE.md +129 -0
  73. package/skills/uncharted/assets/UNDERSTANDING_DOC_TEMPLATE.md +160 -0
  74. package/skills/uncharted/scripts/apply-subsystem-cap.sh +573 -0
  75. package/skills/uncharted/scripts/detect-dependencies.sh +732 -0
  76. package/skills/uncharted/scripts/detect-tests.sh +553 -0
  77. package/skills/uncharted/scripts/discover-subsystems.sh +1029 -0
  78. package/skills/uncharted/scripts/enumerate-target.sh +470 -0
  79. package/skills/uncharted/scripts/import-source.sh +517 -0
  80. package/skills/uncharted/scripts/inspect-provenance.sh +573 -0
  81. package/skills/uncharted/scripts/resolve-segment-target.sh +640 -0
  82. package/skills/uncharted/scripts/run-engine.sh +655 -0
  83. package/skills/uncharted/scripts/validate-proposed-items.sh +125 -0
  84. package/skills/uncharted/scripts/write-backfilled-epics.sh +498 -0
  85. package/skills/wtf/SKILL.md +20 -0
  86. package/templates/CHANGELOG_TEMPLATE.md +13 -0
  87. package/templates/PROBLEM_RAPPORT_TEMPLATE.md +4 -1
  88. package/templates/SCRUM_BOARD_SCHEMA.md +206 -10
  89. package/templates/permission-levels/README.md +73 -0
  90. package/templates/permission-levels/level-1-locked.json +71 -0
  91. package/templates/permission-levels/level-2-guarded.json +64 -0
  92. package/templates/permission-levels/level-3-standard.json +62 -0
  93. package/templates/permission-levels/level-4-elevated.json +60 -0
  94. package/templates/permission-levels/level-5-unrestricted.json +58 -0
  95. package/skills/convert/SKILL.md +0 -124
  96. package/skills/convert/convert_cli.py +0 -235
  97. package/skills/convert/tests/sample.csv +0 -4
  98. package/skills/convert/tests/sample.json +0 -5
  99. package/skills/convert/tests/sample.jsonl +0 -3
  100. package/skills/convert/tests/sample.yaml +0 -18
  101. package/skills/convert/tests/sample_obj.csv +0 -2
  102. package/skills/convert/tests/sample_obj.json +0 -9
  103. package/skills/mirror-public/SKILL.md +0 -237
  104. package/skills/mirror-public/assets/config.json +0 -5
  105. package/skills/mirror-public/scripts/mirror.sh +0 -374
  106. package/skills/self-sync/SKILL.md +0 -73
  107. package/skills/self-sync/scripts/run.js +0 -136
  108. package/skills/train/SKILL.md +0 -116
  109. package/skills/train/assets/dashboard-templates/classifiers.html +0 -106
  110. package/skills/train/assets/dashboard-templates/nlp.html +0 -102
  111. package/skills/train/assets/dashboard-templates/transformers.html +0 -98
  112. package/skills/train/assets/results-parsers/__init__.py +0 -9
  113. package/skills/train/assets/results-parsers/classifiers.py +0 -84
  114. package/skills/train/assets/results-parsers/nlp.py +0 -88
  115. package/skills/train/assets/results-parsers/reporter.py +0 -154
  116. package/skills/train/assets/results-parsers/transformers.py +0 -120
  117. 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
@@ -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. **Verify user-action prerequisites** — Check whether an `_INSTRUCTIONS.md` file exists for this task at `project/board/tasks/<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.)
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
- 2. **Commit** using the following format:
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
- 3. **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! 🎉"
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.