@peter.naydenov/morph 3.1.4 → 3.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.
Files changed (61) hide show
  1. package/.mocharc.json +1 -0
  2. package/.opencode/command/speckit.analyze.md +184 -0
  3. package/.opencode/command/speckit.checklist.md +294 -0
  4. package/.opencode/command/speckit.clarify.md +181 -0
  5. package/.opencode/command/speckit.constitution.md +82 -0
  6. package/.opencode/command/speckit.implement.md +135 -0
  7. package/.opencode/command/speckit.plan.md +89 -0
  8. package/.opencode/command/speckit.specify.md +257 -0
  9. package/.opencode/command/speckit.tasks.md +137 -0
  10. package/.opencode/command/speckit.taskstoissues.md +28 -0
  11. package/.specify/memory/constitution.md +43 -0
  12. package/.specify/scripts/bash/check-prerequisites.sh +166 -0
  13. package/.specify/scripts/bash/common.sh +156 -0
  14. package/.specify/scripts/bash/create-new-feature.sh +305 -0
  15. package/.specify/scripts/bash/setup-plan.sh +61 -0
  16. package/.specify/scripts/bash/update-agent-context.sh +790 -0
  17. package/.specify/templates/agent-file-template.md +28 -0
  18. package/.specify/templates/checklist-template.md +40 -0
  19. package/.specify/templates/plan-template.md +104 -0
  20. package/.specify/templates/spec-template.md +115 -0
  21. package/.specify/templates/tasks-template.md +251 -0
  22. package/Changelog.md +17 -0
  23. package/README.md +154 -3
  24. package/dist/main.d.ts +100 -0
  25. package/dist/methods/_actionSupply.d.ts +18 -0
  26. package/dist/methods/_chopTemplates.d.ts +22 -0
  27. package/dist/methods/_defineData.d.ts +25 -0
  28. package/dist/methods/_defineType.d.ts +15 -0
  29. package/dist/methods/_readTemplate.d.ts +24 -0
  30. package/dist/methods/_renderHolder.d.ts +17 -0
  31. package/dist/methods/_setupActions.d.ts +19 -0
  32. package/dist/methods/build.d.ts +35 -0
  33. package/dist/methods/render.d.ts +25 -0
  34. package/dist/methods/settings.d.ts +7 -0
  35. package/dist/morph.cjs +1 -1
  36. package/dist/morph.esm.mjs +1 -1
  37. package/dist/morph.umd.js +1 -1
  38. package/morph.png +0 -0
  39. package/package.json +10 -7
  40. package/simple.js +17 -0
  41. package/specs/001-extend-templates/checklists/requirements.md +35 -0
  42. package/specs/001-extend-templates/contracts/set-method.json +39 -0
  43. package/specs/001-extend-templates/data-model.md +76 -0
  44. package/specs/001-extend-templates/plan.md +90 -0
  45. package/specs/001-extend-templates/quickstart.md +56 -0
  46. package/specs/001-extend-templates/research.md +18 -0
  47. package/specs/001-extend-templates/spec.md +139 -0
  48. package/specs/001-extend-templates/tasks.md +255 -0
  49. package/src/main.js +70 -32
  50. package/src/methods/_actionSupply.js +16 -0
  51. package/src/methods/_chopTemplates.js +22 -1
  52. package/src/methods/_defineData.js +33 -1
  53. package/src/methods/_defineType.js +13 -0
  54. package/src/methods/_readTemplate.js +46 -46
  55. package/src/methods/_renderHolder.js +15 -0
  56. package/src/methods/_setupActions.js +18 -8
  57. package/src/methods/build.js +98 -43
  58. package/src/methods/render.js +32 -11
  59. package/src/methods/settings.js +11 -0
  60. package/tsconfig.json +27 -0
  61. package/types/index.d.ts +0 -60
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ JSON_MODE=false
6
+ SHORT_NAME=""
7
+ BRANCH_NUMBER=""
8
+ ARGS=()
9
+ i=1
10
+ while [ $i -le $# ]; do
11
+ arg="${!i}"
12
+ case "$arg" in
13
+ --json)
14
+ JSON_MODE=true
15
+ ;;
16
+ --short-name)
17
+ if [ $((i + 1)) -gt $# ]; then
18
+ echo 'Error: --short-name requires a value' >&2
19
+ exit 1
20
+ fi
21
+ i=$((i + 1))
22
+ next_arg="${!i}"
23
+ # Check if the next argument is another option (starts with --)
24
+ if [[ "$next_arg" == --* ]]; then
25
+ echo 'Error: --short-name requires a value' >&2
26
+ exit 1
27
+ fi
28
+ SHORT_NAME="$next_arg"
29
+ ;;
30
+ --number)
31
+ if [ $((i + 1)) -gt $# ]; then
32
+ echo 'Error: --number requires a value' >&2
33
+ exit 1
34
+ fi
35
+ i=$((i + 1))
36
+ next_arg="${!i}"
37
+ if [[ "$next_arg" == --* ]]; then
38
+ echo 'Error: --number requires a value' >&2
39
+ exit 1
40
+ fi
41
+ BRANCH_NUMBER="$next_arg"
42
+ ;;
43
+ --help|-h)
44
+ echo "Usage: $0 [--json] [--short-name <name>] [--number N] <feature_description>"
45
+ echo ""
46
+ echo "Options:"
47
+ echo " --json Output in JSON format"
48
+ echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
49
+ echo " --number N Specify branch number manually (overrides auto-detection)"
50
+ echo " --help, -h Show this help message"
51
+ echo ""
52
+ echo "Examples:"
53
+ echo " $0 'Add user authentication system' --short-name 'user-auth'"
54
+ echo " $0 'Implement OAuth2 integration for API' --number 5"
55
+ exit 0
56
+ ;;
57
+ *)
58
+ ARGS+=("$arg")
59
+ ;;
60
+ esac
61
+ i=$((i + 1))
62
+ done
63
+
64
+ FEATURE_DESCRIPTION="${ARGS[*]}"
65
+ if [ -z "$FEATURE_DESCRIPTION" ]; then
66
+ echo "Usage: $0 [--json] [--short-name <name>] [--number N] <feature_description>" >&2
67
+ exit 1
68
+ fi
69
+
70
+ # Function to find the repository root by searching for existing project markers
71
+ find_repo_root() {
72
+ local dir="$1"
73
+ while [ "$dir" != "/" ]; do
74
+ if [ -d "$dir/.git" ] || [ -d "$dir/.specify" ]; then
75
+ echo "$dir"
76
+ return 0
77
+ fi
78
+ dir="$(dirname "$dir")"
79
+ done
80
+ return 1
81
+ }
82
+
83
+ # Function to get highest number from specs directory
84
+ get_highest_from_specs() {
85
+ local specs_dir="$1"
86
+ local highest=0
87
+
88
+ if [ -d "$specs_dir" ]; then
89
+ for dir in "$specs_dir"/*; do
90
+ [ -d "$dir" ] || continue
91
+ dirname=$(basename "$dir")
92
+ number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
93
+ number=$((10#$number))
94
+ if [ "$number" -gt "$highest" ]; then
95
+ highest=$number
96
+ fi
97
+ done
98
+ fi
99
+
100
+ echo "$highest"
101
+ }
102
+
103
+ # Function to get highest number from git branches
104
+ get_highest_from_branches() {
105
+ local highest=0
106
+
107
+ # Get all branches (local and remote)
108
+ branches=$(git branch -a 2>/dev/null || echo "")
109
+
110
+ if [ -n "$branches" ]; then
111
+ while IFS= read -r branch; do
112
+ # Clean branch name: remove leading markers and remote prefixes
113
+ clean_branch=$(echo "$branch" | sed 's/^[* ]*//; s|^remotes/[^/]*/||')
114
+
115
+ # Extract feature number if branch matches pattern ###-*
116
+ if echo "$clean_branch" | grep -q '^[0-9]\{3\}-'; then
117
+ number=$(echo "$clean_branch" | grep -o '^[0-9]\{3\}' || echo "0")
118
+ number=$((10#$number))
119
+ if [ "$number" -gt "$highest" ]; then
120
+ highest=$number
121
+ fi
122
+ fi
123
+ done <<< "$branches"
124
+ fi
125
+
126
+ echo "$highest"
127
+ }
128
+
129
+ # Function to check existing branches (local and remote) and return next available number
130
+ check_existing_branches() {
131
+ local short_name="$1"
132
+ local specs_dir="$2"
133
+
134
+ # Fetch all remotes to get latest branch info (suppress errors if no remotes)
135
+ git fetch --all --prune 2>/dev/null || true
136
+
137
+ # Find all branches matching the pattern using git ls-remote (more reliable)
138
+ local remote_branches=$(git ls-remote --heads origin 2>/dev/null | grep -E "refs/heads/[0-9]+-${short_name}$" | sed 's/.*\/\([0-9]*\)-.*/\1/' | sort -n)
139
+
140
+ # Also check local branches
141
+ local local_branches=$(git branch 2>/dev/null | grep -E "^[* ]*[0-9]+-${short_name}$" | sed 's/^[* ]*//' | sed 's/-.*//' | sort -n)
142
+
143
+ # Check specs directory as well
144
+ local spec_dirs=""
145
+ if [ -d "$specs_dir" ]; then
146
+ spec_dirs=$(find "$specs_dir" -maxdepth 1 -type d -name "[0-9]*-${short_name}" 2>/dev/null | xargs -n1 basename 2>/dev/null | sed 's/-.*//' | sort -n)
147
+ fi
148
+
149
+ # Combine all sources and get the highest number
150
+ local max_num=0
151
+ for num in $remote_branches $local_branches $spec_dirs; do
152
+ if [ "$num" -gt "$max_num" ]; then
153
+ max_num=$num
154
+ fi
155
+ done
156
+
157
+ # Return next number
158
+ echo $((max_num + 1))
159
+ }
160
+
161
+ # Function to clean and format a branch name
162
+ clean_branch_name() {
163
+ local name="$1"
164
+ echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//'
165
+ }
166
+
167
+ # Resolve repository root. Prefer git information when available, but fall back
168
+ # to searching for repository markers so the workflow still functions in repositories that
169
+ # were initialised with --no-git.
170
+ SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
171
+
172
+ if git rev-parse --show-toplevel >/dev/null 2>&1; then
173
+ REPO_ROOT=$(git rev-parse --show-toplevel)
174
+ HAS_GIT=true
175
+ else
176
+ REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")"
177
+ if [ -z "$REPO_ROOT" ]; then
178
+ echo "Error: Could not determine repository root. Please run this script from within the repository." >&2
179
+ exit 1
180
+ fi
181
+ HAS_GIT=false
182
+ fi
183
+
184
+ cd "$REPO_ROOT"
185
+
186
+ SPECS_DIR="$REPO_ROOT/specs"
187
+ mkdir -p "$SPECS_DIR"
188
+
189
+ # Function to generate branch name with stop word filtering and length filtering
190
+ generate_branch_name() {
191
+ local description="$1"
192
+
193
+ # Common stop words to filter out
194
+ local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$"
195
+
196
+ # Convert to lowercase and split into words
197
+ local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g')
198
+
199
+ # Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)
200
+ local meaningful_words=()
201
+ for word in $clean_name; do
202
+ # Skip empty words
203
+ [ -z "$word" ] && continue
204
+
205
+ # Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms)
206
+ if ! echo "$word" | grep -qiE "$stop_words"; then
207
+ if [ ${#word} -ge 3 ]; then
208
+ meaningful_words+=("$word")
209
+ elif echo "$description" | grep -q "\b${word^^}\b"; then
210
+ # Keep short words if they appear as uppercase in original (likely acronyms)
211
+ meaningful_words+=("$word")
212
+ fi
213
+ fi
214
+ done
215
+
216
+ # If we have meaningful words, use first 3-4 of them
217
+ if [ ${#meaningful_words[@]} -gt 0 ]; then
218
+ local max_words=3
219
+ if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi
220
+
221
+ local result=""
222
+ local count=0
223
+ for word in "${meaningful_words[@]}"; do
224
+ if [ $count -ge $max_words ]; then break; fi
225
+ if [ -n "$result" ]; then result="$result-"; fi
226
+ result="$result$word"
227
+ count=$((count + 1))
228
+ done
229
+ echo "$result"
230
+ else
231
+ # Fallback to original logic if no meaningful words found
232
+ local cleaned=$(clean_branch_name "$description")
233
+ echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//'
234
+ fi
235
+ }
236
+
237
+ # Generate branch name
238
+ if [ -n "$SHORT_NAME" ]; then
239
+ # Use provided short name, just clean it up
240
+ BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME")
241
+ else
242
+ # Generate from description with smart filtering
243
+ BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
244
+ fi
245
+
246
+ # Determine branch number
247
+ if [ -z "$BRANCH_NUMBER" ]; then
248
+ if [ "$HAS_GIT" = true ]; then
249
+ # Check existing branches on remotes
250
+ BRANCH_NUMBER=$(check_existing_branches "$BRANCH_SUFFIX" "$SPECS_DIR")
251
+ else
252
+ # Fall back to local directory check
253
+ HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
254
+ BRANCH_NUMBER=$((HIGHEST + 1))
255
+ fi
256
+ fi
257
+
258
+ FEATURE_NUM=$(printf "%03d" "$BRANCH_NUMBER")
259
+ BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
260
+
261
+ # GitHub enforces a 244-byte limit on branch names
262
+ # Validate and truncate if necessary
263
+ MAX_BRANCH_LENGTH=244
264
+ if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
265
+ # Calculate how much we need to trim from suffix
266
+ # Account for: feature number (3) + hyphen (1) = 4 chars
267
+ MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4))
268
+
269
+ # Truncate suffix at word boundary if possible
270
+ TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH)
271
+ # Remove trailing hyphen if truncation created one
272
+ TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//')
273
+
274
+ ORIGINAL_BRANCH_NAME="$BRANCH_NAME"
275
+ BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}"
276
+
277
+ >&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit"
278
+ >&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)"
279
+ >&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)"
280
+ fi
281
+
282
+ if [ "$HAS_GIT" = true ]; then
283
+ git checkout -b "$BRANCH_NAME"
284
+ else
285
+ >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME"
286
+ fi
287
+
288
+ FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
289
+ mkdir -p "$FEATURE_DIR"
290
+
291
+ TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md"
292
+ SPEC_FILE="$FEATURE_DIR/spec.md"
293
+ if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi
294
+
295
+ # Set the SPECIFY_FEATURE environment variable for the current session
296
+ export SPECIFY_FEATURE="$BRANCH_NAME"
297
+
298
+ if $JSON_MODE; then
299
+ printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
300
+ else
301
+ echo "BRANCH_NAME: $BRANCH_NAME"
302
+ echo "SPEC_FILE: $SPEC_FILE"
303
+ echo "FEATURE_NUM: $FEATURE_NUM"
304
+ echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME"
305
+ fi
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # Parse command line arguments
6
+ JSON_MODE=false
7
+ ARGS=()
8
+
9
+ for arg in "$@"; do
10
+ case "$arg" in
11
+ --json)
12
+ JSON_MODE=true
13
+ ;;
14
+ --help|-h)
15
+ echo "Usage: $0 [--json]"
16
+ echo " --json Output results in JSON format"
17
+ echo " --help Show this help message"
18
+ exit 0
19
+ ;;
20
+ *)
21
+ ARGS+=("$arg")
22
+ ;;
23
+ esac
24
+ done
25
+
26
+ # Get script directory and load common functions
27
+ SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+ source "$SCRIPT_DIR/common.sh"
29
+
30
+ # Get all paths and variables from common functions
31
+ eval $(get_feature_paths)
32
+
33
+ # Check if we're on a proper feature branch (only for git repos)
34
+ check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
35
+
36
+ # Ensure the feature directory exists
37
+ mkdir -p "$FEATURE_DIR"
38
+
39
+ # Copy plan template if it exists
40
+ TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md"
41
+ if [[ -f "$TEMPLATE" ]]; then
42
+ cp "$TEMPLATE" "$IMPL_PLAN"
43
+ echo "Copied plan template to $IMPL_PLAN"
44
+ else
45
+ echo "Warning: Plan template not found at $TEMPLATE"
46
+ # Create a basic plan file if template doesn't exist
47
+ touch "$IMPL_PLAN"
48
+ fi
49
+
50
+ # Output results
51
+ if $JSON_MODE; then
52
+ printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \
53
+ "$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH" "$HAS_GIT"
54
+ else
55
+ echo "FEATURE_SPEC: $FEATURE_SPEC"
56
+ echo "IMPL_PLAN: $IMPL_PLAN"
57
+ echo "SPECS_DIR: $FEATURE_DIR"
58
+ echo "BRANCH: $CURRENT_BRANCH"
59
+ echo "HAS_GIT: $HAS_GIT"
60
+ fi
61
+