@jenga-ai/agent 1.0.1 → 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.
Files changed (38) hide show
  1. package/README.md +3 -4
  2. package/agents/scrum-master.md +75 -0
  3. package/mcp/router/embedder.js +1 -1
  4. package/mcp/training_runner/index.js +239 -0
  5. package/mcp/training_runner/package-lock.json +1065 -0
  6. package/mcp/training_runner/package.json +15 -0
  7. package/package.json +14 -16
  8. package/skills/close-story/SKILL.md +203 -0
  9. package/skills/close-story/scripts/check-story-closeable.sh +195 -0
  10. package/skills/close-story/scripts/compute-scope-divergence.sh +128 -0
  11. package/skills/close-story/scripts/extract-diff-stats.sh +48 -0
  12. package/skills/close-story/scripts/extract-task-diff-stats.sh +97 -0
  13. package/skills/close-story/scripts/update-task-frontmatter.sh +103 -0
  14. package/skills/commit/SKILL.md +18 -0
  15. package/skills/distribute/CONFIG_SCHEMA.md +90 -0
  16. package/skills/distribute/SKILL.md +173 -0
  17. package/skills/distribute/scripts/check-version.sh +74 -0
  18. package/skills/distribute/scripts/commit-version-bump.sh +108 -0
  19. package/skills/distribute/scripts/distribute-changes.sh +381 -0
  20. package/skills/do/SKILL.md +314 -0
  21. package/skills/do/assets/intent-vs-diff-prompt.md +69 -0
  22. package/skills/doc/assets/path-objectives.yaml +13 -0
  23. package/skills/init/SKILL.md +4 -3
  24. package/skills/init/assets/strategy_stub_template.md +38 -0
  25. package/skills/init/scripts/init.sh +6 -1
  26. package/skills/jenga/SKILL.md +51 -2
  27. package/skills/strategy/SKILL.md +312 -0
  28. package/templates/SCRUM_BOARD_SCHEMA.md +49 -0
  29. package/skills/train/SKILL.md +0 -116
  30. package/skills/train/assets/dashboard-templates/classifiers.html +0 -106
  31. package/skills/train/assets/dashboard-templates/nlp.html +0 -102
  32. package/skills/train/assets/dashboard-templates/transformers.html +0 -98
  33. package/skills/train/assets/results-parsers/__init__.py +0 -9
  34. package/skills/train/assets/results-parsers/classifiers.py +0 -84
  35. package/skills/train/assets/results-parsers/nlp.py +0 -88
  36. package/skills/train/assets/results-parsers/reporter.py +0 -154
  37. package/skills/train/assets/results-parsers/transformers.py +0 -120
  38. 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
@@ -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