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 CHANGED
@@ -91,6 +91,7 @@ claude
91
91
  ├── project/ # Stable project knowledge
92
92
  │ ├── architecture.md
93
93
  │ ├── requirements.md
94
+ │ ├── structure.md
94
95
  │ └── decisions/
95
96
  ├── agent/ # Cross-session knowledge
96
97
  │ ├── lessons.md
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 skills installed to .claude/skills/');
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 skills updated to v${VERSION}`);
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.1.0",
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. Update the `**Updated:**` date
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. If user confirms, archive in Phase 3.
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
@@ -19,6 +19,7 @@
19
19
 
20
20
  - `project/architecture.md` — Project architecture (not yet documented)
21
21
  - `project/requirements.md` — Project requirements (not yet documented)
22
+ - `project/structure.md` — Annotated directory tree (not yet documented)
22
23
 
23
24
  ## Agent Knowledge
24
25
 
@@ -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/`