@jenga-ai/agent 1.0.0 → 1.1.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/README.md +28 -10
- package/agents/scrum-master.md +75 -0
- 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 +13 -11
- 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 +18 -0
- package/skills/distribute/CONFIG_SCHEMA.md +90 -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 +314 -0
- package/skills/do/assets/intent-vs-diff-prompt.md +69 -0
- package/skills/doc/assets/path-objectives.yaml +13 -0
- package/skills/init/SKILL.md +4 -3
- package/skills/init/assets/strategy_stub_template.md +38 -0
- package/skills/init/scripts/init.sh +6 -1
- package/skills/jenga/SKILL.md +51 -2
- package/skills/strategy/SKILL.md +312 -0
- package/templates/SCRUM_BOARD_SCHEMA.md +49 -0
- 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,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,6 +14,24 @@ 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 user-action prerequisites check (step 1 in normal mode). No `_INSTRUCTIONS.md` lookup is performed.
|
|
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."
|
|
@@ -0,0 +1,90 @@
|
|
|
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`; 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
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Field reference
|
|
37
|
+
|
|
38
|
+
| Field | Type | Required | Default | Description |
|
|
39
|
+
|---|---|---|---|---|
|
|
40
|
+
| `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`. |
|
|
41
|
+
| `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. |
|
|
42
|
+
| `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. |
|
|
43
|
+
| `updated_at` | string (ISO 8601 date) | yes | — | Date of the last successful distribution, in `YYYY-MM-DD` format. Does **not** include a time component. |
|
|
44
|
+
| `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. |
|
|
45
|
+
| `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. |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## First distribution
|
|
50
|
+
|
|
51
|
+
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:
|
|
52
|
+
|
|
53
|
+
- `project_name` — taken from the matching entry in `distribute.config.json`
|
|
54
|
+
- `target_dir` — taken from the matching entry in `distribute.config.json` (falls back to `.agents` if absent)
|
|
55
|
+
- `version` — read from `package.json` in the monorepo at the time of distribution
|
|
56
|
+
- `updated_at` — today's date (`YYYY-MM-DD`)
|
|
57
|
+
- `last_distributed` — current UTC datetime (`YYYY-MM-DDTHH:MM:SSZ`)
|
|
58
|
+
- `source` — hardcoded to `"private"`
|
|
59
|
+
|
|
60
|
+
The directory referenced by `target_dir` is created if it does not already exist.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Atomic write mechanism
|
|
65
|
+
|
|
66
|
+
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:
|
|
67
|
+
|
|
68
|
+
1. The updated JSON is written to a temporary file in the same directory as the target (e.g. `jenga.config.json.tmp`).
|
|
69
|
+
2. The temporary file is renamed over the target with a single `mv` call.
|
|
70
|
+
|
|
71
|
+
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.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## The `active` flag in `distribute.config.json`
|
|
76
|
+
|
|
77
|
+
`distribute.config.json` (in the monorepo, not in consuming projects) may include an `active` field on each target entry:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"targets": [
|
|
82
|
+
{ "name": "my-project", "path": "../my-project", "active": false }
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- When `active` is `true` (or absent, which defaults to active), the target is distributed normally.
|
|
88
|
+
- 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.
|
|
89
|
+
|
|
90
|
+
Use `active: false` to temporarily pause distribution to a project without removing its entry from the config.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: distribute
|
|
3
|
+
description: Distribute JengaAgent framework files from this private monorepo to one or more consuming projects via the local filesystem. Manages release type selection, version bumping, dry-run preview, per-target file copy, and a post-distribution git commit.
|
|
4
|
+
keywords:
|
|
5
|
+
- distribute
|
|
6
|
+
- private distribution
|
|
7
|
+
- filesystem distribute
|
|
8
|
+
- framework update
|
|
9
|
+
- version bump
|
|
10
|
+
- distribute to projects
|
|
11
|
+
examples:
|
|
12
|
+
- "/distribute"
|
|
13
|
+
- "/distribute /path/to/consuming-project"
|
|
14
|
+
- "/distribute --dry-run"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Distribute
|
|
18
|
+
|
|
19
|
+
Copies JengaAgent framework files from this monorepo to all active consuming projects registered in `distribute.config.json`. Manages the full version lifecycle: release type selection, `package.json` version bump, dry-run preview, per-target file copy, and a final git commit of the version bump.
|
|
20
|
+
|
|
21
|
+
Distinct from `/self-sync` (which mirrors root → in-repo `.claude/.agents/`) and `/mirror-public` (which syncs to the public GitHub repo). Do not call either of those skills from within this flow.
|
|
22
|
+
|
|
23
|
+
## Instructions
|
|
24
|
+
|
|
25
|
+
Follow these steps in order. Do not skip any step.
|
|
26
|
+
|
|
27
|
+
### Step 1 — Path argument check
|
|
28
|
+
|
|
29
|
+
If the user passed a path argument (e.g. `/distribute /path/to/project`):
|
|
30
|
+
|
|
31
|
+
1. Verify the path exists on disk. If it does not, print an error and halt.
|
|
32
|
+
2. Read `distribute.config.json` at the repo root.
|
|
33
|
+
3. Derive `name` from the directory name of the path (e.g. `my-project` from `/home/user/my-project`).
|
|
34
|
+
4. Add a new entry to the `targets` array:
|
|
35
|
+
```json
|
|
36
|
+
{ "name": "<derived-name>", "path": "<absolute-or-relative-path>", "active": true }
|
|
37
|
+
```
|
|
38
|
+
5. Write the updated `distribute.config.json`.
|
|
39
|
+
6. Confirm the addition to the user: `Added <name> (<path>) to distribute.config.json.`
|
|
40
|
+
|
|
41
|
+
If no path argument was passed, skip this step.
|
|
42
|
+
|
|
43
|
+
### Step 2 — Release type prompt
|
|
44
|
+
|
|
45
|
+
Ask the user to choose a release type. Present all four options:
|
|
46
|
+
|
|
47
|
+
| Type | Effect |
|
|
48
|
+
|------|--------|
|
|
49
|
+
| `major` | Breaking change. Bumps major version in `package.json`. Distributes to ALL active targets. |
|
|
50
|
+
| `minor` | New feature. Bumps minor version in `package.json`. Distributes to ALL active targets. |
|
|
51
|
+
| `patch` | Bug fix. Bumps patch version in `package.json`. Distributes to ALL active targets. |
|
|
52
|
+
| `amend` | No version bump. Distributes only to targets whose `jenga.config.json` version is behind the current `package.json` version, or targets that have no `jenga.config.json`. |
|
|
53
|
+
|
|
54
|
+
Wait for the user's answer before continuing.
|
|
55
|
+
|
|
56
|
+
### Step 3 — Config validation
|
|
57
|
+
|
|
58
|
+
Read `distribute.config.json`.
|
|
59
|
+
|
|
60
|
+
For each entry in `targets`:
|
|
61
|
+
- If `active` is `false`: print `Skipping <name> — inactive.` Do not include it in the distribution run.
|
|
62
|
+
- If the `path` does not exist on disk: print `Skipping <name> — path not found: <path>.` Do not include it in the distribution run.
|
|
63
|
+
- If release type is `amend`: run `bash skills/distribute/scripts/check-version.sh <path>`.
|
|
64
|
+
- Exit 0 → include the target.
|
|
65
|
+
- Exit 1 → print `Skipping <name> — already up to date.` Do not include it in the distribution run (record it in the final report as "already up to date").
|
|
66
|
+
- Exit 2 → print `Skipping <name> — invalid path or config.` Do not include it.
|
|
67
|
+
- Otherwise (major / minor / patch): include all active, path-valid targets.
|
|
68
|
+
|
|
69
|
+
Print the final list of targets that will receive distribution. If the list is empty, print `No targets eligible for distribution.` and halt.
|
|
70
|
+
|
|
71
|
+
### Step 4 — Version bump (non-amend only)
|
|
72
|
+
|
|
73
|
+
Skip this step entirely for `amend`.
|
|
74
|
+
|
|
75
|
+
For `major`, `minor`, or `patch`:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm version <type> --no-git-tag-version
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Run this at the monorepo root. If the command fails (non-zero exit), print the error, and halt — do not proceed to distribution with an unbumped or failed version.
|
|
82
|
+
|
|
83
|
+
On success, read the new version from `package.json` and print: `Version bumped to <new-version>.`
|
|
84
|
+
|
|
85
|
+
### Step 5 — Dry-run preview
|
|
86
|
+
|
|
87
|
+
For each eligible target (from Step 3), run:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
bash skills/distribute/scripts/distribute-changes.sh <project_path> --dry-run
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Display the full output for each target. Then ask the user:
|
|
94
|
+
|
|
95
|
+
`Proceed with distribution to the above targets? [y/N]`
|
|
96
|
+
|
|
97
|
+
Wait for confirmation. If the user does not confirm, halt without making any changes. If a version bump was already applied in Step 4 and the user aborts here, inform them that `package.json` has been bumped but not committed, and they must either re-run `/distribute` or revert manually.
|
|
98
|
+
|
|
99
|
+
### Step 6 — Execute distribution
|
|
100
|
+
|
|
101
|
+
For each eligible target in sequence:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
bash skills/distribute/scripts/distribute-changes.sh <project_path>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Record the exit code and any output. If a target fails (non-zero exit), record the failure and continue to the next target. Do not abort the run on partial failure.
|
|
108
|
+
|
|
109
|
+
### Step 7 — Report results
|
|
110
|
+
|
|
111
|
+
Print a per-target summary:
|
|
112
|
+
|
|
113
|
+
**Succeeded:**
|
|
114
|
+
- List each target name and the version distributed (read from `package.json`)
|
|
115
|
+
|
|
116
|
+
**Failed:**
|
|
117
|
+
- List each target name and the error output; note that manual retry is required
|
|
118
|
+
|
|
119
|
+
**Already up to date (amend mode only):**
|
|
120
|
+
- List each target skipped due to version match
|
|
121
|
+
|
|
122
|
+
**Skipped:**
|
|
123
|
+
- List inactive or missing-path targets from Step 3
|
|
124
|
+
|
|
125
|
+
### Step 8 — Commit version bump (non-amend only, only if at least one target succeeded)
|
|
126
|
+
|
|
127
|
+
Skip this step for `amend`. Skip this step if no targets succeeded in Step 6.
|
|
128
|
+
|
|
129
|
+
Call:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
bash skills/distribute/scripts/commit-version-bump.sh
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Report the resulting commit SHA to the user.
|
|
136
|
+
|
|
137
|
+
If at least one target failed and at least one succeeded, note that the version bump commit reflects the distributed version; failed targets need manual retry and will receive the same version on retry.
|
|
138
|
+
|
|
139
|
+
### Step 9 — Do not call downstream skills
|
|
140
|
+
|
|
141
|
+
Do not invoke `/self-sync` or `/mirror-public` at any point in this flow. They are separate skills with separate responsibilities and separate triggers.
|
|
142
|
+
|
|
143
|
+
## Scripts
|
|
144
|
+
|
|
145
|
+
| Script | Purpose |
|
|
146
|
+
|--------|---------|
|
|
147
|
+
| `skills/distribute/scripts/distribute-changes.sh <path> [--dry-run]` | Copy framework files to a single consuming project |
|
|
148
|
+
| `skills/distribute/scripts/commit-version-bump.sh` | Commit the `package.json` version bump |
|
|
149
|
+
| `skills/distribute/scripts/check-version.sh <path>` | Amend mode: exits 0 if target needs update, 1 if up to date, 2 if invalid |
|
|
150
|
+
|
|
151
|
+
## Config
|
|
152
|
+
|
|
153
|
+
`distribute.config.json` at the repo root is the registry of consuming projects.
|
|
154
|
+
|
|
155
|
+
Schema reference: `skills/distribute/CONFIG_SCHEMA.md`
|
|
156
|
+
|
|
157
|
+
Each target entry:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"name": "my-project",
|
|
162
|
+
"path": "/absolute/or/relative/path/to/project",
|
|
163
|
+
"active": true
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Guard Rails
|
|
168
|
+
|
|
169
|
+
- Never call `/self-sync` or `/mirror-public` from this skill.
|
|
170
|
+
- Never inline file copy logic — all filesystem work goes through `distribute-changes.sh`.
|
|
171
|
+
- Never commit anything other than `package.json` via `commit-version-bump.sh`.
|
|
172
|
+
- Never skip the dry-run confirmation gate — even in non-interactive contexts, print the dry-run output and require explicit `y` from the user.
|
|
173
|
+
- Never proceed past Step 4 if `npm version` fails.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# check-version.sh <project_path>
|
|
3
|
+
#
|
|
4
|
+
# Compares the monorepo version (package.json) against the target project's
|
|
5
|
+
# jenga.config.json version field.
|
|
6
|
+
#
|
|
7
|
+
# Exit codes:
|
|
8
|
+
# 0 — target needs an update: jenga.config.json absent OR versions differ
|
|
9
|
+
# 1 — already up to date: versions match exactly
|
|
10
|
+
# 2 — invalid input: <project_path> missing or monorepo package.json missing
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# Resolve monorepo root from this script's own location (two levels up)
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
MONOREPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Validate arguments
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
if [[ $# -ne 1 ]]; then
|
|
24
|
+
echo "Usage: check-version.sh <project_path>" >&2
|
|
25
|
+
exit 2
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
PROJECT_PATH="$1"
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Validate paths
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
PACKAGE_JSON="${MONOREPO_ROOT}/package.json"
|
|
34
|
+
|
|
35
|
+
if [[ ! -d "$PROJECT_PATH" ]]; then
|
|
36
|
+
echo "check-version: project path does not exist: ${PROJECT_PATH}" >&2
|
|
37
|
+
exit 2
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [[ ! -f "$PACKAGE_JSON" ]]; then
|
|
41
|
+
echo "check-version: monorepo package.json not found at ${PACKAGE_JSON}" >&2
|
|
42
|
+
exit 2
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Read monorepo version
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
MONOREPO_VERSION="$(node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('${PACKAGE_JSON}','utf8')).version || '')")"
|
|
49
|
+
|
|
50
|
+
if [[ -z "$MONOREPO_VERSION" ]]; then
|
|
51
|
+
echo "check-version: could not read version from ${PACKAGE_JSON}" >&2
|
|
52
|
+
exit 2
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Read target version (absent jenga.config.json => needs update)
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
JENGA_CONFIG="${PROJECT_PATH}/jenga.config.json"
|
|
59
|
+
|
|
60
|
+
if [[ ! -f "$JENGA_CONFIG" ]]; then
|
|
61
|
+
# No config file — target needs to be initialised
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
TARGET_VERSION="$(node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('${JENGA_CONFIG}','utf8')).version || '')")"
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# Compare using exact string equality
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
if [[ "$MONOREPO_VERSION" == "$TARGET_VERSION" ]]; then
|
|
71
|
+
exit 1 # Already up to date
|
|
72
|
+
else
|
|
73
|
+
exit 0 # Needs update
|
|
74
|
+
fi
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# commit-version-bump.sh — Commit the package.json version bump as a standalone git commit.
|
|
3
|
+
#
|
|
4
|
+
# Called by the /distribute skill after a successful non-amend distribution run.
|
|
5
|
+
# No arguments. Reads the new version from package.json at the monorepo root.
|
|
6
|
+
#
|
|
7
|
+
# Behaviour:
|
|
8
|
+
# 1. Resolve monorepo root from this script's own location (two levels up from scripts/).
|
|
9
|
+
# 2. Read the version field from <monorepo_root>/package.json.
|
|
10
|
+
# 3. Stage only package.json: git add package.json.
|
|
11
|
+
# 4. If nothing changed, exit 0 with an informational message.
|
|
12
|
+
# 5. Create commit: "chore: bump version to <version>".
|
|
13
|
+
# 6. Exit 0 on success, non-zero with a descriptive message on failure.
|
|
14
|
+
#
|
|
15
|
+
# Exit codes:
|
|
16
|
+
# 0 success (commit created, or nothing to commit)
|
|
17
|
+
# 1 fatal error (package.json missing, version unreadable, git commit failed)
|
|
18
|
+
|
|
19
|
+
set -uo pipefail
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Path resolution
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
# scripts/ -> distribute/ -> skills/ -> <monorepo_root>
|
|
27
|
+
MONOREPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
28
|
+
PACKAGE_JSON="$MONOREPO_ROOT/package.json"
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Helpers
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
log_info() {
|
|
35
|
+
printf '→ %s\n' "$1"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fail() {
|
|
39
|
+
printf 'commit-version-bump: error: %s\n' "$1" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Validate prerequisites
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
if [[ ! -f "$PACKAGE_JSON" ]]; then
|
|
48
|
+
fail "package.json not found at $PACKAGE_JSON"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
52
|
+
fail "node is required to read package.json but was not found in PATH"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Read version from package.json
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
VERSION="$(node -p "require('$PACKAGE_JSON').version" 2>/dev/null)" || \
|
|
60
|
+
fail "Failed to read version from $PACKAGE_JSON"
|
|
61
|
+
|
|
62
|
+
if [[ -z "$VERSION" ]]; then
|
|
63
|
+
fail "version field is empty or missing in $PACKAGE_JSON"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
log_info "Version read from package.json: $VERSION"
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# Stage package.json
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
git -C "$MONOREPO_ROOT" add "$PACKAGE_JSON" || fail "git add failed for $PACKAGE_JSON"
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# Check whether there is anything to commit
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
if git -C "$MONOREPO_ROOT" diff --cached --quiet; then
|
|
79
|
+
log_info "Nothing to commit — package.json is already clean"
|
|
80
|
+
exit 0
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# Verify only package.json is staged (safety check)
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
STAGED_FILES="$(git -C "$MONOREPO_ROOT" diff --cached --name-only)"
|
|
88
|
+
STAGED_COUNT="$(printf '%s\n' "$STAGED_FILES" | grep -c .)"
|
|
89
|
+
|
|
90
|
+
if [[ "$STAGED_COUNT" -gt 1 ]]; then
|
|
91
|
+
fail "Unexpected staged files detected. Only package.json should be staged. Found:
|
|
92
|
+
$STAGED_FILES"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# Commit
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
COMMIT_MSG="chore: bump version to $VERSION"
|
|
100
|
+
|
|
101
|
+
log_info "Creating commit: $COMMIT_MSG"
|
|
102
|
+
|
|
103
|
+
if ! git -C "$MONOREPO_ROOT" commit -m "$COMMIT_MSG"; then
|
|
104
|
+
fail "git commit failed for version bump to $VERSION"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
log_info "Committed successfully: $COMMIT_MSG"
|
|
108
|
+
exit 0
|