mema-kit 1.1.0 → 1.2.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 +1 -0
- package/bin/cli.js +33 -4
- package/docs/guide.md +1 -0
- package/package.json +2 -1
- package/scripts/bash/check-prerequisites.sh +144 -0
- package/scripts/bash/commit-changes.sh +147 -0
- package/scripts/bash/common.sh +122 -0
- package/scripts/bash/create-feature-branch.sh +160 -0
- package/scripts/bash/create-pr.sh +184 -0
- package/skills/_memory-protocol.md +6 -0
- package/skills/mema.create-skill/SKILL.md +6 -1
- package/skills/mema.implement/SKILL.md +23 -3
- package/skills/mema.onboard/SKILL.md +35 -0
- package/skills/mema.recall/SKILL.md +8 -0
- package/skills/mema.specify/SKILL.md +14 -0
- package/templates/index.md +1 -0
- package/templates/project/structure.md +32 -0
package/README.md
CHANGED
package/bin/cli.js
CHANGED
|
@@ -10,6 +10,8 @@ const VERSION_COMMENT = `<!-- mema-kit v${VERSION} -->`;
|
|
|
10
10
|
const packageRoot = path.resolve(__dirname, '..');
|
|
11
11
|
const skillsSource = path.join(packageRoot, 'skills');
|
|
12
12
|
const targetDir = path.join(process.cwd(), '.claude', 'skills');
|
|
13
|
+
const scriptsSource = path.join(packageRoot, 'scripts', 'bash');
|
|
14
|
+
const scriptsTarget = path.join(process.cwd(), 'scripts', 'bash');
|
|
13
15
|
|
|
14
16
|
// Parse arguments
|
|
15
17
|
const args = process.argv.slice(2);
|
|
@@ -42,10 +44,13 @@ function runInstall() {
|
|
|
42
44
|
// Create target directory
|
|
43
45
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
44
46
|
|
|
45
|
-
// Copy all skills
|
|
47
|
+
// Copy all skills and bash automation scripts
|
|
46
48
|
copySkills();
|
|
49
|
+
copyScripts();
|
|
47
50
|
|
|
48
|
-
console.log('✓ mema-kit
|
|
51
|
+
console.log('✓ mema-kit installed:');
|
|
52
|
+
console.log(' .claude/skills/ — Claude Code skills');
|
|
53
|
+
console.log(' scripts/bash/ — git automation scripts');
|
|
49
54
|
console.log('');
|
|
50
55
|
console.log('Next step: Open your project in Claude Code and run /mema.onboard');
|
|
51
56
|
console.log('');
|
|
@@ -75,10 +80,13 @@ function runUpdate() {
|
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
// Copy (overwrite) all skills
|
|
83
|
+
// Copy (overwrite) all skills and bash automation scripts
|
|
79
84
|
copySkills();
|
|
85
|
+
copyScripts();
|
|
80
86
|
|
|
81
|
-
console.log(`✓ mema-kit
|
|
87
|
+
console.log(`✓ mema-kit updated to v${VERSION}:`);
|
|
88
|
+
console.log(' .claude/skills/ — skills updated');
|
|
89
|
+
console.log(' scripts/bash/ — git automation scripts updated');
|
|
82
90
|
console.log('');
|
|
83
91
|
console.log(' .mema/ and CLAUDE.md were not modified.');
|
|
84
92
|
console.log('');
|
|
@@ -107,6 +115,27 @@ function copySkills() {
|
|
|
107
115
|
}
|
|
108
116
|
}
|
|
109
117
|
|
|
118
|
+
function copyScripts() {
|
|
119
|
+
// Create the target scripts/bash/ directory if it doesn't exist
|
|
120
|
+
fs.mkdirSync(scriptsTarget, { recursive: true });
|
|
121
|
+
|
|
122
|
+
const entries = fs.readdirSync(scriptsSource, { withFileTypes: true });
|
|
123
|
+
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
// Only copy .sh files — do NOT inject version comments into bash scripts
|
|
126
|
+
if (!entry.isFile() || !entry.name.endsWith('.sh')) continue;
|
|
127
|
+
|
|
128
|
+
const sourcePath = path.join(scriptsSource, entry.name);
|
|
129
|
+
const targetPath = path.join(scriptsTarget, entry.name);
|
|
130
|
+
|
|
131
|
+
fs.writeFileSync(targetPath, fs.readFileSync(sourcePath));
|
|
132
|
+
|
|
133
|
+
// Preserve execute permissions — Node's writeFileSync does not copy the source
|
|
134
|
+
// file's mode, so scripts would be non-executable without this step
|
|
135
|
+
fs.chmodSync(targetPath, 0o755);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
110
139
|
function addVersionComment(content) {
|
|
111
140
|
// Remove any existing version comment
|
|
112
141
|
const cleaned = content.replace(/<!-- mema-kit v[\d.]+ -->\n?/, '');
|
package/docs/guide.md
CHANGED
|
@@ -188,6 +188,7 @@ Every skill reads what previous skills wrote. The index ties it all together.
|
|
|
188
188
|
├── project/ # Stable project knowledge
|
|
189
189
|
│ ├── architecture.md
|
|
190
190
|
│ ├── requirements.md
|
|
191
|
+
│ ├── structure.md # Annotated directory tree
|
|
191
192
|
│ └── decisions/
|
|
192
193
|
├── agent/ # Cross-session knowledge
|
|
193
194
|
│ ├── lessons.md
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mema-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "AI-assisted development lifecycle kit",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mema-kit": "bin/cli.js"
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"docs/",
|
|
10
10
|
"bin/",
|
|
11
11
|
"skills/",
|
|
12
|
+
"scripts/",
|
|
12
13
|
"templates/"
|
|
13
14
|
],
|
|
14
15
|
"keywords": [
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# check-prerequisites.sh — Validates environment prerequisites for mema-kit git automation.
|
|
3
|
+
#
|
|
4
|
+
# Called by git automation scripts before running to ensure git, gh, and repo
|
|
5
|
+
# state are suitable. Also useful as a standalone diagnostic tool.
|
|
6
|
+
#
|
|
7
|
+
# USAGE:
|
|
8
|
+
# check-prerequisites.sh [--need-gh] [--json]
|
|
9
|
+
#
|
|
10
|
+
# OPTIONS:
|
|
11
|
+
# --need-gh Also check that gh CLI is installed and authenticated
|
|
12
|
+
# --json Emit results as JSON; messages go to stdout
|
|
13
|
+
#
|
|
14
|
+
# MODES:
|
|
15
|
+
# Flag mode (--need-gh present): runs checks silently; exits 1 on first failure
|
|
16
|
+
# Summary mode (no --need-gh): prints pass/fail for all four checks; always exits 0
|
|
17
|
+
#
|
|
18
|
+
# OUTPUT — text mode, flag:
|
|
19
|
+
# [silent on success]
|
|
20
|
+
# [missing] Required tool not found: git
|
|
21
|
+
# [error] Not inside a git repository — run from your project root
|
|
22
|
+
# [missing] Required tool not found: gh
|
|
23
|
+
# [error] gh CLI is not authenticated — run: gh auth login
|
|
24
|
+
#
|
|
25
|
+
# OUTPUT — text mode, summary:
|
|
26
|
+
# [ok] git is available
|
|
27
|
+
# [ok] inside a git repository
|
|
28
|
+
# [ok] gh CLI is available
|
|
29
|
+
# [ok] gh CLI is authenticated
|
|
30
|
+
#
|
|
31
|
+
# EXIT CODES:
|
|
32
|
+
# 0 — all checks passed (flag mode), or summary printed (summary mode)
|
|
33
|
+
# 1 — a required check failed (flag mode only)
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Self-locate and source shared library.
|
|
37
|
+
# ${BASH_SOURCE[0]} is more reliable than $0 when invoked via symlinks or
|
|
38
|
+
# called with an explicit path from a different working directory.
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
41
|
+
source "${SCRIPT_DIR}/common.sh"
|
|
42
|
+
# common.sh activates: set -euo pipefail, git_root, output_result,
|
|
43
|
+
# check_prereq, parse_common_args
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Pass 1: parse common args.
|
|
47
|
+
# Sets OUTPUT_FORMAT=json if --json is present so all output_result calls
|
|
48
|
+
# below automatically emit JSON.
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
parse_common_args "$@"
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# Pass 2: parse script-specific args.
|
|
54
|
+
# Detect --need-gh to know whether to run gh checks and which mode to use.
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
NEED_GH=0
|
|
57
|
+
|
|
58
|
+
for arg in "$@"; do
|
|
59
|
+
case "$arg" in
|
|
60
|
+
--need-gh) NEED_GH=1 ;;
|
|
61
|
+
*) ;; # --json already handled; unknown flags silently ignored
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Mode selection.
|
|
67
|
+
# Summary mode = NEED_GH is 0 (no operational flags; --json alone doesn't count)
|
|
68
|
+
# → print pass/fail for all four checks; always exit 0
|
|
69
|
+
# Flag mode = NEED_GH is 1
|
|
70
|
+
# → run only the requested checks; silent on success; exit 1 on first failure
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
if [[ "$NEED_GH" -eq 0 ]]; then
|
|
74
|
+
|
|
75
|
+
# -------------------------------------------------------------------------
|
|
76
|
+
# SUMMARY MODE
|
|
77
|
+
# Run every check independently (no exit on failure) and print a status
|
|
78
|
+
# line for each. Useful for standalone debugging: bash check-prerequisites.sh
|
|
79
|
+
# -------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
# Check 1: git on PATH
|
|
82
|
+
if command -v git &>/dev/null; then
|
|
83
|
+
output_result "ok" "git is available"
|
|
84
|
+
else
|
|
85
|
+
output_result "missing" "git not found — install git to use mema-kit scripts" ', "check": "git"'
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# Check 2: inside a git repository
|
|
89
|
+
if git rev-parse --show-toplevel &>/dev/null 2>&1; then
|
|
90
|
+
output_result "ok" "inside a git repository"
|
|
91
|
+
else
|
|
92
|
+
output_result "error" "not inside a git repository — run from your project root" ', "check": "git-repo"'
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Check 3: gh CLI on PATH
|
|
96
|
+
if command -v gh &>/dev/null; then
|
|
97
|
+
output_result "ok" "gh CLI is available"
|
|
98
|
+
else
|
|
99
|
+
output_result "missing" "gh CLI not found — install from https://cli.github.com" ', "check": "gh"'
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Check 4: gh authenticated
|
|
103
|
+
# Guard: only attempt auth check if gh is present — avoids "command not found"
|
|
104
|
+
# noise when gh is not installed.
|
|
105
|
+
if ! command -v gh &>/dev/null; then
|
|
106
|
+
output_result "skipped" "gh CLI not installed — skipping auth check" ', "check": "gh-auth"'
|
|
107
|
+
elif gh auth status &>/dev/null 2>&1; then
|
|
108
|
+
output_result "ok" "gh CLI is authenticated"
|
|
109
|
+
else
|
|
110
|
+
output_result "error" "gh CLI is not authenticated — run: gh auth login" ', "check": "gh-auth"'
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
exit 0
|
|
114
|
+
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
# FLAG MODE
|
|
119
|
+
# Run only the checks appropriate for the requested flags.
|
|
120
|
+
# Silent on success; output_result + exit 1 on first failure.
|
|
121
|
+
# Callers use: check-prerequisites.sh --need-gh || exit 1
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
# Universal checks: git and repo state are always validated in flag mode.
|
|
125
|
+
check_prereq git || exit 1
|
|
126
|
+
|
|
127
|
+
if ! git rev-parse --show-toplevel &>/dev/null 2>&1; then
|
|
128
|
+
output_result "error" "Not inside a git repository — run from your project root" ', "check": "git-repo"'
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# gh checks: only when --need-gh was passed (always true in flag mode currently,
|
|
133
|
+
# but kept explicit for future extensibility).
|
|
134
|
+
if [[ "$NEED_GH" -eq 1 ]]; then
|
|
135
|
+
check_prereq gh || exit 1
|
|
136
|
+
|
|
137
|
+
if ! gh auth status &>/dev/null 2>&1; then
|
|
138
|
+
output_result "error" "gh CLI is not authenticated — run: gh auth login" ', "check": "gh-auth"'
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# All checks passed — exit silently.
|
|
144
|
+
exit 0
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# commit-changes.sh — Validates a Conventional Commits message, shows the staged
|
|
3
|
+
# diff, runs git commit, and pushes to the current branch.
|
|
4
|
+
#
|
|
5
|
+
# Called by /mema.implement at the end of the per-feature cycle, after all tasks
|
|
6
|
+
# are complete and the user confirms their tests pass.
|
|
7
|
+
#
|
|
8
|
+
# USAGE:
|
|
9
|
+
# commit-changes.sh [--json] <commit-message>
|
|
10
|
+
#
|
|
11
|
+
# ARGUMENTS:
|
|
12
|
+
# commit-message — Quoted Conventional Commits message (Claude generates this).
|
|
13
|
+
# Format: type(scope): description
|
|
14
|
+
# Example: feat(cli): add --json flag to output
|
|
15
|
+
#
|
|
16
|
+
# OPTIONS:
|
|
17
|
+
# --json Emit result as JSON to stdout; all other messages go to stderr.
|
|
18
|
+
#
|
|
19
|
+
# OUTPUT — text mode:
|
|
20
|
+
# [ok] Committed: abc1234 — feat(cli): add --json flag to output
|
|
21
|
+
# [error] Commit message format invalid. Expected: type(scope): description
|
|
22
|
+
# [error] Nothing is staged. Stage your changes before committing.
|
|
23
|
+
#
|
|
24
|
+
# OUTPUT — json mode:
|
|
25
|
+
# {"status": "ok", "message": "...", "committed": true, "sha": "abc1234", "branch": "feat-003-name", "commit_message": "feat: initial implementation"}
|
|
26
|
+
# {"status": "error", "message": "..."}
|
|
27
|
+
#
|
|
28
|
+
# EXIT CODES:
|
|
29
|
+
# 0 — commit and push succeeded
|
|
30
|
+
# 1 — validation failed, nothing staged, or unexpected error
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Self-locate and source shared library.
|
|
34
|
+
# ${BASH_SOURCE[0]} is more reliable than $0 when invoked via symlinks or
|
|
35
|
+
# called with an explicit path from a different working directory.
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
38
|
+
source "${SCRIPT_DIR}/common.sh"
|
|
39
|
+
# common.sh activates: set -euo pipefail, git_root, output_result,
|
|
40
|
+
# check_prereq, parse_common_args
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# Pass 1: Parse common args.
|
|
44
|
+
# parse_common_args uses a for-loop and does NOT consume "$@", so the full
|
|
45
|
+
# argument list is still available for our own parsing pass below.
|
|
46
|
+
# Sets OUTPUT_FORMAT=json if --json is present.
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
parse_common_args "$@"
|
|
49
|
+
|
|
50
|
+
# Verify git is available before doing anything else.
|
|
51
|
+
check_prereq git || exit 1
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# Pass 2: Parse script-specific args.
|
|
55
|
+
# Extracts the commit message positional arg. --json is re-encountered here
|
|
56
|
+
# and skipped — it was already handled by parse_common_args above.
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
commit_message=""
|
|
59
|
+
|
|
60
|
+
while [[ $# -gt 0 ]]; do
|
|
61
|
+
case "$1" in
|
|
62
|
+
--json)
|
|
63
|
+
# Already handled by parse_common_args; skip.
|
|
64
|
+
shift
|
|
65
|
+
;;
|
|
66
|
+
-*)
|
|
67
|
+
# Unknown flag — silently ignore to stay forward-compatible.
|
|
68
|
+
shift
|
|
69
|
+
;;
|
|
70
|
+
*)
|
|
71
|
+
# Positional argument: the commit message (take the first one found).
|
|
72
|
+
if [[ -z "$commit_message" ]]; then
|
|
73
|
+
commit_message="$1"
|
|
74
|
+
fi
|
|
75
|
+
shift
|
|
76
|
+
;;
|
|
77
|
+
esac
|
|
78
|
+
done
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# Input validation: require a non-empty commit message.
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
if [[ -z "$commit_message" ]]; then
|
|
84
|
+
echo "Error: missing required argument <commit-message>" >&2
|
|
85
|
+
echo "Usage: commit-changes.sh [--json] <commit-message>" >&2
|
|
86
|
+
echo "Example: commit-changes.sh \"feat(cli): add --json flag to output\"" >&2
|
|
87
|
+
exit 1
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Conventional Commits format validation.
|
|
92
|
+
# Pattern: type(optional-scope): description (description 1–72 chars)
|
|
93
|
+
# This check runs before any git operation so that an invalid message
|
|
94
|
+
# is rejected without touching the repository.
|
|
95
|
+
#
|
|
96
|
+
# NOTE: The regex variable must NOT be quoted in the =~ test; quoting it
|
|
97
|
+
# would cause bash to treat it as a literal string rather than a pattern.
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
COMMIT_PATTERN='^(feat|fix|docs|style|refactor|test|chore|build|ci|perf|revert)(\(.+\))?: .{1,72}$'
|
|
100
|
+
if [[ ! "$commit_message" =~ $COMMIT_PATTERN ]]; then
|
|
101
|
+
output_result "error" \
|
|
102
|
+
"Commit message format invalid. Expected: type(scope): description — e.g. feat(cli): add --json flag"
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
# Staged diff check.
|
|
108
|
+
# git diff --staged --stat shows a human-readable summary of staged files.
|
|
109
|
+
# If the output is empty, nothing is staged and we should not create an empty
|
|
110
|
+
# commit. Diagnostic output always goes to stderr so it never taints JSON stdout.
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
staged="$(git diff --staged --stat)"
|
|
113
|
+
if [[ -z "$staged" ]]; then
|
|
114
|
+
output_result "error" \
|
|
115
|
+
"Nothing is staged. Use git add to stage your changes before committing."
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
echo "Staged changes:" >&2
|
|
120
|
+
echo "$staged" >&2
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
# Commit.
|
|
124
|
+
# set -euo pipefail (inherited from common.sh) ensures any failure here exits
|
|
125
|
+
# immediately with a non-zero code rather than silently continuing.
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
git commit -m "$commit_message"
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# Push.
|
|
131
|
+
# --set-upstream is idempotent: sets the remote tracking branch on the first
|
|
132
|
+
# push, and is effectively a no-op on subsequent pushes if upstream is already
|
|
133
|
+
# set. This handles the case where create-feature-branch.sh created a local
|
|
134
|
+
# branch that has never yet been pushed to origin.
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
current_branch="$(git rev-parse --abbrev-ref HEAD)"
|
|
137
|
+
git push --set-upstream origin "$current_branch"
|
|
138
|
+
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
# Capture the short SHA immediately after commit so the output reflects the
|
|
141
|
+
# exact commit that was just created.
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
sha="$(git rev-parse --short HEAD)"
|
|
144
|
+
|
|
145
|
+
output_result "ok" \
|
|
146
|
+
"Committed: ${sha} — ${commit_message}" \
|
|
147
|
+
", \"committed\": true, \"sha\": \"${sha}\", \"branch\": \"${current_branch}\", \"commit_message\": \"${commit_message}\""
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# common.sh — Shared bash library for mema-kit git automation scripts.
|
|
3
|
+
#
|
|
4
|
+
# USAGE (from any git automation script in scripts/bash/):
|
|
5
|
+
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
# source "${SCRIPT_DIR}/common.sh"
|
|
7
|
+
#
|
|
8
|
+
# After sourcing, call parse_common_args "$@" to handle the --json flag,
|
|
9
|
+
# then use git_root, output_result, and check_prereq as needed.
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
# Double-source guard — safe to source multiple times.
|
|
13
|
+
#
|
|
14
|
+
# Uses ${COMMON_SH_LOADED:-} instead of $COMMON_SH_LOADED to avoid an
|
|
15
|
+
# "unbound variable" error from set -u on the first source, before COMMON_SH_LOADED
|
|
16
|
+
# has been defined.
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
[[ -n "${COMMON_SH_LOADED:-}" ]] && return 0
|
|
19
|
+
COMMON_SH_LOADED=1
|
|
20
|
+
|
|
21
|
+
# Strict mode: exit on any error (-e), treat unbound variables as errors (-u),
|
|
22
|
+
# and propagate pipe failures (-o pipefail). Scripts that source this file
|
|
23
|
+
# inherit these settings automatically.
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# git_root
|
|
28
|
+
#
|
|
29
|
+
# Prints the absolute path of the current git repository root to stdout.
|
|
30
|
+
# Exits non-zero with an error message to stderr if not inside a git repo.
|
|
31
|
+
#
|
|
32
|
+
# Example:
|
|
33
|
+
# root="$(git_root)" || exit 1
|
|
34
|
+
# cd "$root"
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
git_root() {
|
|
37
|
+
# 2>/dev/null suppresses git's own "not a git repository" error message
|
|
38
|
+
# so we can emit a consistent error instead.
|
|
39
|
+
git rev-parse --show-toplevel 2>/dev/null || {
|
|
40
|
+
echo "Error: not inside a git repository" >&2
|
|
41
|
+
return 1
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# output_result <status> <message> [extra_json]
|
|
47
|
+
#
|
|
48
|
+
# Prints a result in JSON or plain-text format depending on OUTPUT_FORMAT.
|
|
49
|
+
#
|
|
50
|
+
# Arguments:
|
|
51
|
+
# status — short status string (e.g. "ok", "error", "missing")
|
|
52
|
+
# message — human-readable description
|
|
53
|
+
# extra_json — optional; pre-formatted JSON fragment starting with ", "
|
|
54
|
+
# e.g. ', "tool": "gh"' is appended inside the JSON object
|
|
55
|
+
#
|
|
56
|
+
# Output format is controlled by OUTPUT_FORMAT (default: text):
|
|
57
|
+
# text → [status] message
|
|
58
|
+
# json → {"status": "…", "message": "…"[, extra_json fields]}
|
|
59
|
+
#
|
|
60
|
+
# Set OUTPUT_FORMAT=json by calling parse_common_args "$@" at the top of
|
|
61
|
+
# each script, or by setting it directly before calling output_result.
|
|
62
|
+
#
|
|
63
|
+
# Examples:
|
|
64
|
+
# output_result "ok" "Branch created: 001-my-feature"
|
|
65
|
+
# output_result "missing" "Tool not found: gh" ', "tool": "gh"'
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
output_result() {
|
|
68
|
+
local status="$1"
|
|
69
|
+
local message="$2"
|
|
70
|
+
local extra="${3:-}" # optional extra JSON fragment, e.g. ', "branch": "001-foo"'
|
|
71
|
+
|
|
72
|
+
if [[ "${OUTPUT_FORMAT:-text}" == "json" ]]; then
|
|
73
|
+
echo "{\"status\": \"${status}\", \"message\": \"${message}\"${extra}}"
|
|
74
|
+
else
|
|
75
|
+
echo "[${status}] ${message}"
|
|
76
|
+
fi
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# check_prereq <tool>
|
|
81
|
+
#
|
|
82
|
+
# Checks that <tool> is available on $PATH.
|
|
83
|
+
# Returns 0 if found; calls output_result and returns 1 if missing.
|
|
84
|
+
#
|
|
85
|
+
# Example:
|
|
86
|
+
# check_prereq git || exit 1
|
|
87
|
+
# check_prereq gh || exit 1 # non-fatal: caller may choose to degrade
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
check_prereq() {
|
|
90
|
+
local tool="$1"
|
|
91
|
+
|
|
92
|
+
if ! command -v "$tool" &>/dev/null; then
|
|
93
|
+
# ', "tool": "gh"' extends the JSON object so callers can read the tool name
|
|
94
|
+
output_result "missing" "Required tool not found: ${tool}" ", \"tool\": \"${tool}\""
|
|
95
|
+
return 1
|
|
96
|
+
fi
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
# parse_common_args "$@"
|
|
101
|
+
#
|
|
102
|
+
# Parses arguments shared across all git automation scripts.
|
|
103
|
+
# Currently handles:
|
|
104
|
+
# --json sets OUTPUT_FORMAT=json so output_result emits JSON
|
|
105
|
+
#
|
|
106
|
+
# NOTE: Call this at the top of each script, before using output_result,
|
|
107
|
+
# so that the output format is set correctly for all subsequent calls.
|
|
108
|
+
# Unknown flags are silently ignored — scripts can extend this with their
|
|
109
|
+
# own argument parsing after calling parse_common_args.
|
|
110
|
+
#
|
|
111
|
+
# Example:
|
|
112
|
+
# parse_common_args "$@"
|
|
113
|
+
# # OUTPUT_FORMAT is now set; proceed to parse script-specific args
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
parse_common_args() {
|
|
116
|
+
for arg in "$@"; do
|
|
117
|
+
case "$arg" in
|
|
118
|
+
--json) OUTPUT_FORMAT=json ;;
|
|
119
|
+
*) ;; # ignore unknown flags
|
|
120
|
+
esac
|
|
121
|
+
done
|
|
122
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# create-feature-branch.sh — Creates a new git feature branch for the mema-kit per-feature cycle.
|
|
3
|
+
#
|
|
4
|
+
# Called by /mema.specify at the start of a new feature to create a consistently
|
|
5
|
+
# named local branch. Designed to be idempotent and safe to re-run.
|
|
6
|
+
#
|
|
7
|
+
# USAGE:
|
|
8
|
+
# create-feature-branch.sh <NNN> <feature-name> [--short-name <name>] [--json]
|
|
9
|
+
#
|
|
10
|
+
# ARGUMENTS:
|
|
11
|
+
# NNN — Feature number (e.g. "002")
|
|
12
|
+
# feature-name — Kebab-case feature name (e.g. "create-feature-branch")
|
|
13
|
+
#
|
|
14
|
+
# OPTIONS:
|
|
15
|
+
# --short-name <name> Override the branch name suffix (default: feature-name arg)
|
|
16
|
+
# --json Emit result as JSON to stdout; all other messages go to stderr
|
|
17
|
+
#
|
|
18
|
+
# OUTPUT — text mode:
|
|
19
|
+
# [created] Branch created: feat-002-create-feature-branch
|
|
20
|
+
# [exists] Branch already exists: feat-002-create-feature-branch
|
|
21
|
+
# [error] Working directory is dirty — commit or stash changes before creating a branch
|
|
22
|
+
#
|
|
23
|
+
# OUTPUT — json mode:
|
|
24
|
+
# {"status": "created", "message": "...", "branch": "feat-002-name", "base": "main"}
|
|
25
|
+
# {"status": "exists", "message": "...", "branch": "feat-002-name", "base": "main"}
|
|
26
|
+
# {"status": "error", "message": "..."}
|
|
27
|
+
#
|
|
28
|
+
# EXIT CODES:
|
|
29
|
+
# 0 — branch created or already existed (idempotent success)
|
|
30
|
+
# 1 — dirty working directory, missing args, missing git, or unexpected error
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Self-locate and source shared library.
|
|
34
|
+
# ${BASH_SOURCE[0]} is more reliable than $0 when invoked via symlinks or
|
|
35
|
+
# called with an explicit path from a different working directory.
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
38
|
+
source "${SCRIPT_DIR}/common.sh"
|
|
39
|
+
# common.sh activates: set -euo pipefail, git_root, output_result,
|
|
40
|
+
# check_prereq, parse_common_args
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# Pass 1: Parse common args.
|
|
44
|
+
# parse_common_args uses a for-loop and does NOT consume "$@", so the full
|
|
45
|
+
# argument list is still available for our own parsing pass below.
|
|
46
|
+
# Sets OUTPUT_FORMAT=json if --json is present.
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
parse_common_args "$@"
|
|
49
|
+
|
|
50
|
+
# Verify git is available before doing anything else.
|
|
51
|
+
check_prereq git || exit 1
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# Pass 2: Parse script-specific args.
|
|
55
|
+
# Extracts positional args and --short-name. --json is re-encountered here
|
|
56
|
+
# and skipped — it was already handled by parse_common_args above.
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
feature_num=""
|
|
59
|
+
feature_name=""
|
|
60
|
+
short_name_override=""
|
|
61
|
+
|
|
62
|
+
while [[ $# -gt 0 ]]; do
|
|
63
|
+
case "$1" in
|
|
64
|
+
--json)
|
|
65
|
+
# Already handled by parse_common_args; skip.
|
|
66
|
+
shift
|
|
67
|
+
;;
|
|
68
|
+
--short-name)
|
|
69
|
+
# Guard against --short-name with no following value.
|
|
70
|
+
if [[ $# -lt 2 ]]; then
|
|
71
|
+
echo "Error: --short-name requires a value" >&2
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
short_name_override="$2"
|
|
75
|
+
shift 2
|
|
76
|
+
;;
|
|
77
|
+
-*)
|
|
78
|
+
# Unknown flag — silently ignore to stay forward-compatible.
|
|
79
|
+
shift
|
|
80
|
+
;;
|
|
81
|
+
*)
|
|
82
|
+
# Positional argument: first is NNN, second is feature-name.
|
|
83
|
+
if [[ -z "$feature_num" ]]; then
|
|
84
|
+
feature_num="$1"
|
|
85
|
+
elif [[ -z "$feature_name" ]]; then
|
|
86
|
+
feature_name="$1"
|
|
87
|
+
fi
|
|
88
|
+
shift
|
|
89
|
+
;;
|
|
90
|
+
esac
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# Input validation.
|
|
95
|
+
# Both a feature number and a branch name suffix are required. The suffix
|
|
96
|
+
# comes from either <feature-name> or --short-name.
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
if [[ -z "$feature_num" ]]; then
|
|
99
|
+
echo "Error: missing required argument <NNN> (feature number, e.g. 002)" >&2
|
|
100
|
+
echo "Usage: create-feature-branch.sh <NNN> <feature-name> [--short-name <name>] [--json]" >&2
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
if [[ -z "$feature_name" && -z "$short_name_override" ]]; then
|
|
105
|
+
echo "Error: missing required argument <feature-name> (or use --short-name <name>)" >&2
|
|
106
|
+
echo "Usage: create-feature-branch.sh <NNN> <feature-name> [--short-name <name>] [--json]" >&2
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# Capture base branch and construct target branch name.
|
|
112
|
+
# base_branch is captured BEFORE any git operations so the output correctly
|
|
113
|
+
# records the branch we diverged from, even after we switch branches.
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
base_branch="$(git rev-parse --abbrev-ref HEAD)"
|
|
116
|
+
|
|
117
|
+
# Use --short-name override if provided; fall back to the feature-name positional arg.
|
|
118
|
+
branch_name="feat-${feature_num}-${short_name_override:-${feature_name}}"
|
|
119
|
+
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
# Dirty-dir guard.
|
|
122
|
+
# git status --porcelain is intentionally conservative: it reports untracked
|
|
123
|
+
# files (??), staged changes, and unstaged changes. Any output means the
|
|
124
|
+
# working directory is not clean enough for a safe branch creation.
|
|
125
|
+
# The dirty file list always goes to stderr so it never taints JSON stdout.
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
dirty="$(git status --porcelain)"
|
|
128
|
+
if [[ -n "$dirty" ]]; then
|
|
129
|
+
echo "Dirty files:" >&2
|
|
130
|
+
echo "$dirty" >&2
|
|
131
|
+
output_result "error" "Working directory is dirty — commit or stash changes before creating a branch"
|
|
132
|
+
exit 1
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
# Idempotency check.
|
|
137
|
+
# git branch --list "name" emits the branch name (with leading whitespace)
|
|
138
|
+
# if it exists locally, or empty output if it does not. grep -q . tests for
|
|
139
|
+
# any non-empty output (the dot matches any character, so any output passes).
|
|
140
|
+
# If the branch exists: checkout (no-op if already on it; git returns 0) and exit 0.
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
if git branch --list "${branch_name}" | grep -q .; then
|
|
143
|
+
git checkout "${branch_name}"
|
|
144
|
+
output_result "exists" \
|
|
145
|
+
"Branch already exists: ${branch_name}" \
|
|
146
|
+
", \"branch\": \"${branch_name}\", \"base\": \"${base_branch}\""
|
|
147
|
+
exit 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# Branch creation.
|
|
152
|
+
# git checkout -b creates the branch and switches to it in one atomic step.
|
|
153
|
+
# set -euo pipefail (inherited from common.sh) ensures any failure exits
|
|
154
|
+
# immediately with a non-zero code rather than silently continuing.
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
git checkout -b "${branch_name}"
|
|
157
|
+
|
|
158
|
+
output_result "created" \
|
|
159
|
+
"Branch created: ${branch_name}" \
|
|
160
|
+
", \"branch\": \"${branch_name}\", \"base\": \"${base_branch}\""
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# create-pr.sh — Creates a draft GitHub pull request for the current feature branch.
|
|
3
|
+
#
|
|
4
|
+
# Called by /mema.implement after all tasks are complete and the branch has been
|
|
5
|
+
# committed and pushed via commit-changes.sh. Designed to be idempotent and safe
|
|
6
|
+
# to re-run: if a PR already exists for the current branch, the script exits 0
|
|
7
|
+
# without creating a duplicate.
|
|
8
|
+
#
|
|
9
|
+
# Degrades gracefully when gh is not installed or the remote is not GitHub — prints
|
|
10
|
+
# manual steps to stderr and exits 0 rather than failing hard.
|
|
11
|
+
#
|
|
12
|
+
# USAGE:
|
|
13
|
+
# create-pr.sh [--json] [--base <branch>] <title> [body]
|
|
14
|
+
#
|
|
15
|
+
# ARGUMENTS:
|
|
16
|
+
# title — PR title (required); Claude supplies this
|
|
17
|
+
# body — PR body/description (optional); defaults to empty string
|
|
18
|
+
#
|
|
19
|
+
# OPTIONS:
|
|
20
|
+
# --base <branch> Base branch for the PR (default: main)
|
|
21
|
+
# --json Emit result as JSON to stdout; all other messages go to stderr
|
|
22
|
+
#
|
|
23
|
+
# OUTPUT — text mode:
|
|
24
|
+
# [created] PR opened (draft): https://github.com/owner/repo/pull/42
|
|
25
|
+
# [exists] PR already open: https://github.com/owner/repo/pull/42
|
|
26
|
+
# [missing] gh not installed — see manual steps above
|
|
27
|
+
# [skipped] Non-GitHub remote — PR creation skipped
|
|
28
|
+
# [error] Branch 'feat-004-create-pr' has no remote tracking ref. Push first.
|
|
29
|
+
#
|
|
30
|
+
# OUTPUT — json mode:
|
|
31
|
+
# {"status": "created", "message": "...", "created": true, "url": "...", "branch": "...", "draft": true}
|
|
32
|
+
# {"status": "exists", "message": "...", "created": false, "url": "...", "branch": "...", "draft": true}
|
|
33
|
+
# {"status": "missing", "message": "...", "created": false, "branch": "..."}
|
|
34
|
+
# {"status": "skipped", "message": "...", "created": false, "branch": "...", "manual_url": "..."}
|
|
35
|
+
# {"status": "error", "message": "..."}
|
|
36
|
+
#
|
|
37
|
+
# EXIT CODES:
|
|
38
|
+
# 0 — PR created, PR already exists, gh missing (graceful), or non-GitHub remote (graceful)
|
|
39
|
+
# 1 — branch not pushed, missing title, git not found, or unexpected error
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Self-locate and source shared library.
|
|
43
|
+
# ${BASH_SOURCE[0]} is more reliable than $0 when invoked via symlinks or
|
|
44
|
+
# called with an explicit path from a different working directory.
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
47
|
+
source "${SCRIPT_DIR}/common.sh"
|
|
48
|
+
# common.sh activates: set -euo pipefail, git_root, output_result,
|
|
49
|
+
# check_prereq, parse_common_args
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# Pass 1: Parse common args.
|
|
53
|
+
# parse_common_args uses a for-loop and does NOT consume "$@", so the full
|
|
54
|
+
# argument list is still available for our own parsing pass below.
|
|
55
|
+
# Sets OUTPUT_FORMAT=json if --json is present.
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
parse_common_args "$@"
|
|
58
|
+
|
|
59
|
+
# Verify git is available before doing anything else.
|
|
60
|
+
check_prereq git || exit 1
|
|
61
|
+
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
# Pass 2: Parse script-specific args.
|
|
64
|
+
# Extracts positional args (title, body) and --base. --json is re-encountered
|
|
65
|
+
# here and skipped — it was already handled by parse_common_args above.
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
title=""
|
|
68
|
+
body=""
|
|
69
|
+
base_branch="main"
|
|
70
|
+
|
|
71
|
+
while [[ $# -gt 0 ]]; do
|
|
72
|
+
case "$1" in
|
|
73
|
+
--json)
|
|
74
|
+
# Already handled by parse_common_args; skip.
|
|
75
|
+
shift
|
|
76
|
+
;;
|
|
77
|
+
--base)
|
|
78
|
+
# Guard against --base with no following value.
|
|
79
|
+
if [[ $# -lt 2 ]]; then
|
|
80
|
+
echo "Error: --base requires a value" >&2
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
base_branch="$2"
|
|
84
|
+
shift 2
|
|
85
|
+
;;
|
|
86
|
+
-*)
|
|
87
|
+
# Unknown flag — silently ignore to stay forward-compatible.
|
|
88
|
+
shift
|
|
89
|
+
;;
|
|
90
|
+
*)
|
|
91
|
+
# Positional argument: first is title, second is body.
|
|
92
|
+
if [[ -z "$title" ]]; then
|
|
93
|
+
title="$1"
|
|
94
|
+
elif [[ -z "$body" ]]; then
|
|
95
|
+
body="$1"
|
|
96
|
+
fi
|
|
97
|
+
shift
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
done
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# Input validation: require a non-empty PR title.
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
if [[ -z "$title" ]]; then
|
|
106
|
+
echo "Error: missing required argument <title>" >&2
|
|
107
|
+
echo "Usage: create-pr.sh [--json] [--base <branch>] <title> [body]" >&2
|
|
108
|
+
echo "Example: create-pr.sh \"feat(cli): add --json flag\" \"Adds --json output mode\"" >&2
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# Capture current branch. Done here (before any conditional exits) so it is
|
|
114
|
+
# available in all output paths below.
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
current_branch="$(git rev-parse --abbrev-ref HEAD)"
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# gh availability check — graceful degradation.
|
|
120
|
+
# Unlike check_prereq (which exits 1 on failure), we exit 0 here because a
|
|
121
|
+
# missing gh CLI is not an error — the user can create the PR manually.
|
|
122
|
+
# Manual instructions always go to stderr; the machine result goes to stdout.
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
if ! command -v gh &>/dev/null; then
|
|
125
|
+
echo "gh CLI not found. To create a PR manually:" >&2
|
|
126
|
+
echo " 1. Ensure your branch is pushed: git push -u origin ${current_branch}" >&2
|
|
127
|
+
echo " 2. Open a PR at: https://github.com/<owner>/<repo>/compare/${current_branch}" >&2
|
|
128
|
+
output_result "missing" "gh not installed — see manual steps above" \
|
|
129
|
+
", \"created\": false, \"branch\": \"${current_branch}\""
|
|
130
|
+
exit 0
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
# Remote URL check — non-GitHub detection.
|
|
135
|
+
# gh pr create only works with GitHub-hosted repos. If the origin remote is
|
|
136
|
+
# GitLab, Bitbucket, or anything else, degrade gracefully instead of failing.
|
|
137
|
+
# The || true prevents set -e from killing the script if origin doesn't exist.
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
remote_url="$(git remote get-url origin 2>/dev/null || true)"
|
|
140
|
+
if [[ "$remote_url" != *github.com* ]]; then
|
|
141
|
+
echo "Remote is not GitHub (${remote_url:-no origin remote found})." >&2
|
|
142
|
+
echo "To create a PR manually, visit your hosting provider and open a PR for: ${current_branch}" >&2
|
|
143
|
+
output_result "skipped" "Non-GitHub remote — PR creation skipped" \
|
|
144
|
+
", \"created\": false, \"branch\": \"${current_branch}\", \"manual_url\": \"${remote_url:-}\""
|
|
145
|
+
exit 0
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
# Branch-pushed check — hard error (exit 1).
|
|
150
|
+
# If the branch has no remote tracking ref, there is nothing gh can PR against.
|
|
151
|
+
# This is a real error state, not a graceful-degradation case — the user must
|
|
152
|
+
# push the branch first (commit-changes.sh handles this automatically).
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
if ! git rev-parse --abbrev-ref --symbolic-full-name "@{u}" &>/dev/null; then
|
|
155
|
+
output_result "error" \
|
|
156
|
+
"Branch '${current_branch}' has no remote tracking ref. Push the branch first (commit-changes.sh handles this)."
|
|
157
|
+
exit 1
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
# Idempotency check — detect existing open PR before creating.
|
|
162
|
+
# gh pr list --json url --jq '.[0].url' extracts the URL of the first result.
|
|
163
|
+
# The || true prevents set -e from aborting if gh returns non-zero (e.g. no
|
|
164
|
+
# open PRs found for this branch returns an empty list, not an error, but
|
|
165
|
+
# older gh versions may exit non-zero in edge cases).
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
existing_url="$(gh pr list --head "${current_branch}" --state open --json url --jq '.[0].url' 2>/dev/null || true)"
|
|
168
|
+
if [[ -n "$existing_url" ]]; then
|
|
169
|
+
output_result "exists" "PR already open: ${existing_url}" \
|
|
170
|
+
", \"created\": false, \"url\": \"${existing_url}\", \"branch\": \"${current_branch}\", \"draft\": true"
|
|
171
|
+
exit 0
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Create the draft PR.
|
|
176
|
+
# gh pr create prints the new PR URL to stdout; we capture it directly.
|
|
177
|
+
# Diagnostic progress messages from gh go to stderr automatically.
|
|
178
|
+
# set -euo pipefail (inherited from common.sh) ensures any failure here exits
|
|
179
|
+
# immediately rather than silently continuing with an empty pr_url.
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
pr_url="$(gh pr create --draft --title "${title}" --body "${body:-}" --base "${base_branch}")"
|
|
182
|
+
|
|
183
|
+
output_result "created" "PR opened (draft): ${pr_url}" \
|
|
184
|
+
", \"created\": true, \"url\": \"${pr_url}\", \"branch\": \"${current_branch}\", \"draft\": true"
|
|
@@ -84,6 +84,12 @@ Update `.mema/index.md` to reflect all changes made in Phase 3. This is **mandat
|
|
|
84
84
|
- **UPDATE** when the stack or requirements change.
|
|
85
85
|
- Keep current state only — these are reference documents, not history logs.
|
|
86
86
|
|
|
87
|
+
### project/structure.md — Replace curation
|
|
88
|
+
- **UPDATE** when directories or key files are added, renamed, or removed.
|
|
89
|
+
- Keep current state only — this is a navigation reference, not a history log.
|
|
90
|
+
- **NOOP** if no structural change occurred in this session.
|
|
91
|
+
- Never delete — if the project structure is unknown, leave the file with a minimal tree rather than removing it.
|
|
92
|
+
|
|
87
93
|
### agent/lessons.md and agent/patterns.md — Consolidation curation
|
|
88
94
|
- **Merge similar lessons.** "Drizzle needs type casting" and "Drizzle enum handling requires explicit cast" are the same lesson — keep one entry with both examples.
|
|
89
95
|
- **UPDATE** with new examples when the same pattern/lesson recurs.
|
|
@@ -292,6 +292,10 @@ Follow the curation rules in `_memory-protocol.md`.
|
|
|
292
292
|
|
|
293
293
|
**If a skill file was written** (user did not CANCEL):
|
|
294
294
|
- ADD/UPDATE `agent/patterns.md` with a lightweight record: skill name, complexity level, one-sentence purpose, action taken (`created` / `enhanced` / `overwritten`), date (`YYYY-MM-DD`)
|
|
295
|
+
- UPDATE `project/structure.md`:
|
|
296
|
+
- Add `├── [skill-name]/ — [skill purpose, one clause]` to the `.claude/skills/` subtree in `## Directory Tree`
|
|
297
|
+
- Add entry to `## Where to Find X`: `**[Skill name] skill:** .claude/skills/[skill-name]/SKILL.md`
|
|
298
|
+
- If `project/structure.md` does not exist, NOOP (structure.md is created by `/mema.onboard`)
|
|
295
299
|
|
|
296
300
|
**If no file was written** (user cancelled at any step):
|
|
297
301
|
- NOOP — no memory changes
|
|
@@ -301,4 +305,5 @@ Follow the curation rules in `_memory-protocol.md`.
|
|
|
301
305
|
Update `.mema/index.md`:
|
|
302
306
|
1. Re-read the current index
|
|
303
307
|
2. If `agent/patterns.md` was modified, update its summary entry
|
|
304
|
-
3.
|
|
308
|
+
3. If `project/structure.md` was modified, update its summary entry
|
|
309
|
+
4. Update the `**Updated:**` date
|
|
@@ -124,11 +124,30 @@ All [N] tasks implemented.
|
|
|
124
124
|
|
|
125
125
|
### Files changed
|
|
126
126
|
[List of files created or modified]
|
|
127
|
-
|
|
128
|
-
Archive this feature? (moves to archive/ and removes from active features)
|
|
129
127
|
```
|
|
130
128
|
|
|
131
|
-
2.
|
|
129
|
+
2. **Git Automation Checkpoint:**
|
|
130
|
+
Check if `scripts/bash/commit-changes.sh` exists (use Glob: `scripts/bash/commit-changes.sh`):
|
|
131
|
+
|
|
132
|
+
**If found:**
|
|
133
|
+
- Ask the user: "All [N] tasks are complete. Please confirm your tests pass before I commit. (yes/no)"
|
|
134
|
+
- If **yes**:
|
|
135
|
+
- Compose a Conventional Commits message: `feat(<feature-name>): <brief description from spec Purpose>`
|
|
136
|
+
- Stage implementation files: run `git add <file> ...` for each file created or modified during this implementation (review the progress log in `status.md` for the file list)
|
|
137
|
+
- Run: `bash scripts/bash/commit-changes.sh --json "<commit_message>"`
|
|
138
|
+
- On `status: "ok"`: report "Committed [sha]." and print "**To undo: `git reset HEAD~1`**"
|
|
139
|
+
Then ask: "Would you like to open a draft PR? (yes/no)"
|
|
140
|
+
- If **yes** and `scripts/bash/create-pr.sh` exists:
|
|
141
|
+
Generate a PR title (same as the commit message description) and a short body summarising what was built, then run: `bash scripts/bash/create-pr.sh --json "<title>" "<body>"`
|
|
142
|
+
Handle all statuses: `created` → report URL; `exists` → report existing URL; `missing` → display the manual steps printed to stderr; `skipped` → note non-GitHub remote; `error` → report error
|
|
143
|
+
- If `create-pr.sh` not found: note "Run `npx mema-kit` to install PR automation scripts."
|
|
144
|
+
- On `status: "error"`: report the error; do NOT proceed to archiving
|
|
145
|
+
- If **no**: note "Skipping commit — remember to run `git add` + `git commit` + `git push` manually before opening a PR."
|
|
146
|
+
|
|
147
|
+
**If not found:** note: "Git scripts not installed — run `npx mema-kit` to enable automatic commit and PR creation."
|
|
148
|
+
|
|
149
|
+
3. Archive this feature? (moves to `archive/` and removes from active features)
|
|
150
|
+
If user confirms, archive in Phase 3.
|
|
132
151
|
|
|
133
152
|
### 2h: Learn
|
|
134
153
|
|
|
@@ -143,6 +162,7 @@ Follow curation rules in `_memory-protocol.md`:
|
|
|
143
162
|
|
|
144
163
|
- **Decisions made** during implementation → ADD to `project/decisions/YYYY-MM-DD-short-name.md`
|
|
145
164
|
- **Architecture changes** → UPDATE `project/architecture.md`
|
|
165
|
+
- **Structural changes** (new files, directories, or moves) → UPDATE `project/structure.md`: add/remove/rename the affected entries in `## Directory Tree` and `## Where to Find X`. If nothing structural changed, NOOP.
|
|
146
166
|
- **Lessons learned** → ADD/UPDATE `agent/lessons.md`
|
|
147
167
|
- **Patterns discovered** → ADD/UPDATE `agent/patterns.md`
|
|
148
168
|
- **Task progress** → UPDATE `features/NNN-name/status.md` (done in 2f)
|
|
@@ -220,6 +220,39 @@ Using scan findings, create files with **real content** (not empty placeholders)
|
|
|
220
220
|
- [Constraint discovered]
|
|
221
221
|
```
|
|
222
222
|
|
|
223
|
+
### `.mema/project/structure.md`
|
|
224
|
+
|
|
225
|
+
Using the directory scan from Step 5b, write an annotated repo tree and navigation guide:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
# Repository Structure
|
|
229
|
+
|
|
230
|
+
**Status:** active | **Updated:** [today]
|
|
231
|
+
|
|
232
|
+
## Directory Tree
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
[project-name]/
|
|
236
|
+
[2–3 level annotated tree derived from Step 5b scan]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Entry Points
|
|
240
|
+
|
|
241
|
+
[Key files per subsystem, e.g.:]
|
|
242
|
+
- `[entry file]` — [what it does]
|
|
243
|
+
|
|
244
|
+
## Source vs. Generated
|
|
245
|
+
|
|
246
|
+
- **Source:** [source dirs]
|
|
247
|
+
- **Generated:** [build output, node_modules, etc.]
|
|
248
|
+
- **Gitignored:** `.mema/`, [other gitignored items]
|
|
249
|
+
|
|
250
|
+
## Where to Find X
|
|
251
|
+
|
|
252
|
+
[Quick-reference for the top subsystems found during scan:]
|
|
253
|
+
- **[Component type]:** `[path/]`
|
|
254
|
+
```
|
|
255
|
+
|
|
223
256
|
### `.mema/agent/lessons.md`
|
|
224
257
|
|
|
225
258
|
```
|
|
@@ -262,6 +295,7 @@ Build the index from files just created:
|
|
|
262
295
|
## Project Knowledge
|
|
263
296
|
- `project/architecture.md` — [one-line stack/architecture summary]
|
|
264
297
|
- `project/requirements.md` — [one-line purpose summary]
|
|
298
|
+
- `project/structure.md` — [one-line: e.g. "Annotated repo tree, 3 top-level dirs"]
|
|
265
299
|
|
|
266
300
|
## Agent Knowledge
|
|
267
301
|
- `agent/lessons.md` — [N] lessons recorded
|
|
@@ -336,6 +370,7 @@ Print a summary of what was done:
|
|
|
336
370
|
mema-kit initialized!
|
|
337
371
|
|
|
338
372
|
[check] .mema/ structure created (product/, features/, project/, agent/)
|
|
373
|
+
[check] project/structure.md generated
|
|
339
374
|
[check] CLAUDE.md [generated / updated / already configured]
|
|
340
375
|
[check] .gitignore updated
|
|
341
376
|
|
|
@@ -42,6 +42,7 @@ Read the following (skip any that don't exist):
|
|
|
42
42
|
|
|
43
43
|
1. `.mema/project/architecture.md` — tech stack, structure, architecture pattern
|
|
44
44
|
2. `.mema/project/requirements.md` — project purpose and constraints
|
|
45
|
+
3. `.mema/project/structure.md` — annotated directory tree and navigation guide
|
|
45
46
|
|
|
46
47
|
## Step 5: Load Additional Files (Full Mode Only)
|
|
47
48
|
|
|
@@ -104,6 +105,13 @@ Stack: [tech stack from architecture.md]
|
|
|
104
105
|
Stack: [tech stack]
|
|
105
106
|
Architecture: [pattern]
|
|
106
107
|
|
|
108
|
+
### Repository Structure
|
|
109
|
+
[If project/structure.md exists:]
|
|
110
|
+
[Top-level tree from ## Directory Tree (first level only)]
|
|
111
|
+
Where to find:
|
|
112
|
+
[Entries from ## Where to Find X, condensed to one line each]
|
|
113
|
+
[If project/structure.md does not exist: omit section]
|
|
114
|
+
|
|
107
115
|
### Recent Decisions
|
|
108
116
|
[For each decision file, list:]
|
|
109
117
|
- **[Decision title]** — [one-line summary]
|
|
@@ -96,10 +96,24 @@ Write `.mema/features/NNN-name/spec.md`:
|
|
|
96
96
|
[What this feature deliberately does NOT include]
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
### Create Feature Branch (if scripts available)
|
|
100
|
+
|
|
101
|
+
After saving `spec.md`, create (or check out) the feature branch:
|
|
102
|
+
|
|
103
|
+
1. Parse the feature number and name from the feature directory path:
|
|
104
|
+
- Example: `features/005-skill-integration/` → NNN = `005`, name = `skill-integration`
|
|
105
|
+
2. Check if `scripts/bash/create-feature-branch.sh` exists (use Glob: `scripts/bash/create-feature-branch.sh`)
|
|
106
|
+
3. **If found:** run: `bash scripts/bash/create-feature-branch.sh <NNN> <name> --json`
|
|
107
|
+
- `status: "created"` → include in confirmation: "Branch created: feat-NNN-name"
|
|
108
|
+
- `status: "exists"` → include: "Branch already exists: feat-NNN-name (checked out)"
|
|
109
|
+
- `status: "error"` → surface the error message; note that spec.md was saved successfully; tell user to resolve the issue (e.g. commit or stash dirty files) and re-run `/mema.specify`
|
|
110
|
+
4. **If not found:** note: "Git scripts not installed — run `npx mema-kit` to enable automatic branch creation."
|
|
111
|
+
|
|
99
112
|
### Confirm to User
|
|
100
113
|
|
|
101
114
|
```
|
|
102
115
|
Spec saved: features/[NNN-name]/spec.md
|
|
116
|
+
Branch: [feat-NNN-name (created) | feat-NNN-name (exists) | error — see above | scripts not installed]
|
|
103
117
|
|
|
104
118
|
[Feature name]: [one-line purpose summary]
|
|
105
119
|
[N] acceptance criteria defined
|
package/templates/index.md
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Repository Structure
|
|
2
|
+
|
|
3
|
+
**Status:** active | **Updated:** YYYY-MM-DD
|
|
4
|
+
|
|
5
|
+
## Directory Tree
|
|
6
|
+
|
|
7
|
+
<!-- Annotated 2–3 level tree. Update when directories are added or removed. -->
|
|
8
|
+
<!-- Format: path/ — description -->
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
project-root/
|
|
12
|
+
├── src/ — [description]
|
|
13
|
+
│ └── ...
|
|
14
|
+
└── package.json — [description]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Entry Points
|
|
18
|
+
|
|
19
|
+
<!-- Key files to start reading for each subsystem -->
|
|
20
|
+
- `path/to/entry.file` — [what it does]
|
|
21
|
+
|
|
22
|
+
## Source vs. Generated
|
|
23
|
+
|
|
24
|
+
<!-- What is hand-written vs. auto-generated vs. gitignored -->
|
|
25
|
+
- **Source:** `src/`, `tests/`
|
|
26
|
+
- **Generated:** `dist/`, `node_modules/`
|
|
27
|
+
- **Gitignored:** `.mema/`, `node_modules/`, `.env`
|
|
28
|
+
|
|
29
|
+
## Where to Find X
|
|
30
|
+
|
|
31
|
+
<!-- Quick-reference for navigating the codebase -->
|
|
32
|
+
- **[Component type]:** `path/to/dir/`
|