duaer-spec 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.cursor/rules/agents-workflow.mdc +50 -0
  2. package/.cursor/rules/ai-ui-copy.mdc +12 -0
  3. package/.cursor/rules/duaer-spec.mdc +33 -0
  4. package/.cursor/skills/duaer-analyze/SKILL.md +259 -0
  5. package/.cursor/skills/duaer-checklist/SKILL.md +383 -0
  6. package/.cursor/skills/duaer-clarify/SKILL.md +291 -0
  7. package/.cursor/skills/duaer-constitution/SKILL.md +177 -0
  8. package/.cursor/skills/duaer-converge/SKILL.md +277 -0
  9. package/.cursor/skills/duaer-git-commit/SKILL.md +68 -0
  10. package/.cursor/skills/duaer-git-feature/SKILL.md +94 -0
  11. package/.cursor/skills/duaer-git-initialize/SKILL.md +54 -0
  12. package/.cursor/skills/duaer-git-remote/SKILL.md +50 -0
  13. package/.cursor/skills/duaer-git-validate/SKILL.md +54 -0
  14. package/.cursor/skills/duaer-implement/SKILL.md +226 -0
  15. package/.cursor/skills/duaer-plan/SKILL.md +166 -0
  16. package/.cursor/skills/duaer-specify/SKILL.md +345 -0
  17. package/.cursor/skills/duaer-tasks/SKILL.md +214 -0
  18. package/.cursor/skills/duaer-taskstoissues/SKILL.md +109 -0
  19. package/.duaer/extensions/.registry +23 -0
  20. package/.duaer/extensions/git/README.md +119 -0
  21. package/.duaer/extensions/git/commands/duaer.git.commit.md +63 -0
  22. package/.duaer/extensions/git/commands/duaer.git.feature.md +82 -0
  23. package/.duaer/extensions/git/commands/duaer.git.initialize.md +49 -0
  24. package/.duaer/extensions/git/commands/duaer.git.remote.md +45 -0
  25. package/.duaer/extensions/git/commands/duaer.git.validate.md +49 -0
  26. package/.duaer/extensions/git/config-template.yml +79 -0
  27. package/.duaer/extensions/git/extension.yml +142 -0
  28. package/.duaer/extensions/git/git-config.yml +79 -0
  29. package/.duaer/extensions/git/scripts/bash/auto-commit.sh +211 -0
  30. package/.duaer/extensions/git/scripts/bash/create-new-feature-branch.sh +626 -0
  31. package/.duaer/extensions/git/scripts/bash/git-common.sh +56 -0
  32. package/.duaer/extensions/git/scripts/bash/initialize-repo.sh +54 -0
  33. package/.duaer/extensions/git/scripts/powershell/auto-commit.ps1 +230 -0
  34. package/.duaer/extensions/git/scripts/powershell/create-new-feature-branch.ps1 +592 -0
  35. package/.duaer/extensions/git/scripts/powershell/git-common.ps1 +52 -0
  36. package/.duaer/extensions/git/scripts/powershell/initialize-repo.ps1 +69 -0
  37. package/.duaer/extensions/git/scripts/python/auto_commit.py +195 -0
  38. package/.duaer/extensions/git/scripts/python/create_new_feature_branch.py +634 -0
  39. package/.duaer/extensions/git/scripts/python/git_common.py +81 -0
  40. package/.duaer/extensions/git/scripts/python/initialize_repo.py +89 -0
  41. package/.duaer/extensions.yml +167 -0
  42. package/.duaer/init-options.json +9 -0
  43. package/.duaer/integration.json +15 -0
  44. package/.duaer/integrations/cursor-agent.manifest.json +17 -0
  45. package/.duaer/integrations/duaer.manifest.json +19 -0
  46. package/.duaer/memory/.constitution-template.json +4 -0
  47. package/.duaer/memory/constitution.md +37 -0
  48. package/.duaer/memory/project-context.md +24 -0
  49. package/.duaer/memory/testing.md +14 -0
  50. package/.duaer/scripts/bash/check-prerequisites.sh +243 -0
  51. package/.duaer/scripts/bash/common.sh +926 -0
  52. package/.duaer/scripts/bash/create-new-feature.sh +407 -0
  53. package/.duaer/scripts/bash/resolve-template.sh +57 -0
  54. package/.duaer/scripts/bash/setup-plan.sh +85 -0
  55. package/.duaer/scripts/bash/setup-tasks.sh +94 -0
  56. package/.duaer/templates/checklist-template.md +45 -0
  57. package/.duaer/templates/constitution-template.md +50 -0
  58. package/.duaer/templates/plan-template.md +113 -0
  59. package/.duaer/templates/spec-template.md +131 -0
  60. package/.duaer/templates/tasks-template.md +252 -0
  61. package/.duaer/workflows/duaer/workflow.yml +78 -0
  62. package/.duaer/workflows/workflow-registry.json +13 -0
  63. package/ADOPT.md +75 -0
  64. package/AGENTS.md +290 -0
  65. package/CHANGELOG.md +28 -0
  66. package/DUADER.md +57 -0
  67. package/LICENSE +21 -0
  68. package/README.md +74 -0
  69. package/bin/duaer.mjs +298 -0
  70. package/docs/agent/README.md +12 -0
  71. package/docs/agent/change-checklist.md +130 -0
  72. package/docs/agent/e2e-test-plan.md +33 -0
  73. package/docs/agent/workflow.md +127 -0
  74. package/docs/baseline.md +10 -0
  75. package/package.json +49 -0
@@ -0,0 +1,407 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ JSON_MODE=false
6
+ DRY_RUN=false
7
+ ALLOW_EXISTING=false
8
+ SHORT_NAME=""
9
+ BRANCH_NUMBER=""
10
+ USE_TIMESTAMP=false
11
+ NUMBER_EXPLICIT=false
12
+ ARGS=()
13
+ i=1
14
+ while [ $i -le $# ]; do
15
+ arg="${!i}"
16
+ case "$arg" in
17
+ --json)
18
+ JSON_MODE=true
19
+ ;;
20
+ --dry-run)
21
+ DRY_RUN=true
22
+ ;;
23
+ --allow-existing-branch)
24
+ ALLOW_EXISTING=true
25
+ ;;
26
+ --short-name)
27
+ if [ $((i + 1)) -gt $# ]; then
28
+ echo 'Error: --short-name requires a value' >&2
29
+ exit 1
30
+ fi
31
+ i=$((i + 1))
32
+ next_arg="${!i}"
33
+ # Check if the next argument is another option (starts with --)
34
+ if [[ "$next_arg" == --* ]]; then
35
+ echo 'Error: --short-name requires a value' >&2
36
+ exit 1
37
+ fi
38
+ SHORT_NAME="$next_arg"
39
+ ;;
40
+ --number)
41
+ if [ $((i + 1)) -gt $# ]; then
42
+ echo 'Error: --number requires a value' >&2
43
+ exit 1
44
+ fi
45
+ i=$((i + 1))
46
+ next_arg="${!i}"
47
+ if [[ "$next_arg" == --* ]]; then
48
+ echo 'Error: --number requires a value' >&2
49
+ exit 1
50
+ fi
51
+ BRANCH_NUMBER="$next_arg"
52
+ if [ -n "$BRANCH_NUMBER" ]; then
53
+ NUMBER_EXPLICIT=true
54
+ fi
55
+ ;;
56
+ --timestamp)
57
+ USE_TIMESTAMP=true
58
+ ;;
59
+ --help|-h)
60
+ echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name <name>] [--number N] [--timestamp] <feature_description>"
61
+ echo ""
62
+ echo "Options:"
63
+ echo " --json Output in JSON format"
64
+ echo " --dry-run Compute feature name and paths without creating directories or files"
65
+ echo " --allow-existing-branch Reuse an existing feature directory if it already exists"
66
+ echo " --short-name <name> Provide a custom short name (2-4 words) for the feature"
67
+ echo " --number N Prefer a feature number (auto-corrected if its specs prefix exists)"
68
+ echo " --timestamp Use timestamp prefix (YYYYMMDD-HHMMSS) instead of sequential numbering"
69
+ echo " --help, -h Show this help message"
70
+ echo ""
71
+ echo "Examples:"
72
+ echo " $0 'Add user authentication system' --short-name 'user-auth'"
73
+ echo " $0 'Implement OAuth2 integration for API' --number 5"
74
+ echo " $0 --timestamp --short-name 'user-auth' 'Add user authentication'"
75
+ exit 0
76
+ ;;
77
+ *)
78
+ ARGS+=("$arg")
79
+ ;;
80
+ esac
81
+ i=$((i + 1))
82
+ done
83
+
84
+ FEATURE_DESCRIPTION="${ARGS[*]}"
85
+ if [ -z "$FEATURE_DESCRIPTION" ]; then
86
+ echo "Usage: $0 [--json] [--dry-run] [--allow-existing-branch] [--short-name <name>] [--number N] [--timestamp] <feature_description>" >&2
87
+ exit 1
88
+ fi
89
+
90
+ # Trim whitespace and validate description is not empty (e.g., user passed only whitespace)
91
+ FEATURE_DESCRIPTION=$(echo "$FEATURE_DESCRIPTION" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
92
+ if [ -z "$FEATURE_DESCRIPTION" ]; then
93
+ echo "Error: Feature description cannot be empty or contain only whitespace" >&2
94
+ exit 1
95
+ fi
96
+
97
+ MAX_FEATURE_NUMBER=9223372036854775807
98
+ MAX_BRANCH_LENGTH=244
99
+
100
+ is_feature_number_in_range() {
101
+ local value="$1"
102
+ local normalized="${value#"${value%%[!0]*}"}"
103
+ [ -n "$normalized" ] || normalized=0
104
+ [ ${#normalized} -lt ${#MAX_FEATURE_NUMBER} ] && return 0
105
+ [ ${#normalized} -gt ${#MAX_FEATURE_NUMBER} ] && return 1
106
+ # Equal-length digit strings must be compared without arithmetic overflow.
107
+ # shellcheck disable=SC2071
108
+ [[ "$normalized" < "$MAX_FEATURE_NUMBER" || "$normalized" == "$MAX_FEATURE_NUMBER" ]]
109
+ }
110
+
111
+ # Function to get highest number from specs directory
112
+ get_highest_from_specs() {
113
+ local specs_dir="$1"
114
+ local highest=0
115
+
116
+ if [ -d "$specs_dir" ]; then
117
+ for dir in "$specs_dir"/*; do
118
+ [ -d "$dir" ] || continue
119
+ dirname=$(basename "$dir")
120
+ # Match sequential prefixes (>=3 digits), but skip timestamp dirs.
121
+ if echo "$dirname" | grep -Eq '^[0-9]{3,}-' && ! echo "$dirname" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then
122
+ number=$(echo "$dirname" | grep -Eo '^[0-9]+')
123
+ if is_feature_number_in_range "$number"; then
124
+ number=$((10#$number))
125
+ if [ "$number" -gt "$highest" ]; then
126
+ highest=$number
127
+ fi
128
+ fi
129
+ fi
130
+ done
131
+ fi
132
+
133
+ echo "$highest"
134
+ }
135
+
136
+ # Return success when a spec directory owns the given numeric prefix.
137
+ spec_prefix_exists() {
138
+ local specs_dir="$1"
139
+ local feature_num="$2"
140
+
141
+ for spec_path in "$specs_dir/${feature_num}-"*; do
142
+ [ -d "$spec_path" ] && return 0
143
+ done
144
+ return 1
145
+ }
146
+
147
+ # Function to clean and format a branch name
148
+ clean_branch_name() {
149
+ local name="$1"
150
+ echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//'
151
+ }
152
+
153
+ # Fit a feature prefix and suffix within GitHub's branch-name limit.
154
+ fit_branch_name() {
155
+ local feature_num="$1"
156
+ local branch_suffix="$2"
157
+ local branch_name="${feature_num}-${branch_suffix}"
158
+
159
+ if [ ${#branch_name} -gt $MAX_BRANCH_LENGTH ]; then
160
+ local prefix_length=$(( ${#feature_num} + 1 ))
161
+ local max_suffix_length=$((MAX_BRANCH_LENGTH - prefix_length))
162
+ local truncated_suffix
163
+ truncated_suffix=$(printf '%s' "$branch_suffix" | cut -c "1-$max_suffix_length" | sed 's/-$//')
164
+ branch_name="${feature_num}-${truncated_suffix}"
165
+ fi
166
+
167
+ printf '%s' "$branch_name"
168
+ }
169
+
170
+ # Quote a value for POSIX shell reuse, byte-identical to Python's shlex.quote
171
+ # so the persistence hints match the Python variant exactly (printf %q output
172
+ # differs between bash versions and from shlex.quote for spaces/metachars).
173
+ shell_quote() {
174
+ local value="$1" LC_ALL=C
175
+ if [[ "$value" =~ ^[A-Za-z0-9_@%+=:,./-]+$ ]]; then
176
+ printf '%s' "$value"
177
+ else
178
+ local q="'\"'\"'"
179
+ printf "'%s'" "${value//\'/$q}"
180
+ fi
181
+ }
182
+
183
+ # Resolve repository root using common.sh functions which prioritize .duaer
184
+ SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
185
+ source "$SCRIPT_DIR/common.sh"
186
+
187
+ REPO_ROOT=$(get_repo_root) || exit 1
188
+
189
+ cd "$REPO_ROOT"
190
+
191
+ SPECS_DIR="$REPO_ROOT/specs"
192
+ if [ "$DRY_RUN" != true ]; then
193
+ mkdir -p "$SPECS_DIR"
194
+ fi
195
+
196
+ # Function to generate branch name with stop word filtering and length filtering
197
+ generate_branch_name() {
198
+ local description="$1"
199
+
200
+ # Common stop words to filter out
201
+ 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)$"
202
+
203
+ # Convert to lowercase and split into words
204
+ local clean_name=$(printf '%s' "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g')
205
+
206
+ # Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)
207
+ local meaningful_words=()
208
+ for word in $clean_name; do
209
+ # Skip empty words
210
+ [ -z "$word" ] && continue
211
+
212
+ # Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms)
213
+ if ! echo "$word" | grep -qiE "$stop_words"; then
214
+ if [ ${#word} -ge 3 ]; then
215
+ meaningful_words+=("$word")
216
+ # Keep short words that appear as an uppercase acronym in the original.
217
+ # Uppercase via tr and match with grep -w (both portable) rather than
218
+ # bash's 4+ "^^" case expansion (breaks on macOS bash 3.2) and \b (non-POSIX).
219
+ elif printf '%s' "$description" | grep -qw -- "$(printf '%s' "$word" | tr '[:lower:]' '[:upper:]')"; then
220
+ meaningful_words+=("$word")
221
+ fi
222
+ fi
223
+ done
224
+
225
+ # If we have meaningful words, use first 3-4 of them
226
+ if [ ${#meaningful_words[@]} -gt 0 ]; then
227
+ local max_words=3
228
+ if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi
229
+
230
+ local result=""
231
+ local count=0
232
+ for word in "${meaningful_words[@]}"; do
233
+ if [ $count -ge $max_words ]; then break; fi
234
+ if [ -n "$result" ]; then result="$result-"; fi
235
+ result="$result$word"
236
+ count=$((count + 1))
237
+ done
238
+ echo "$result"
239
+ else
240
+ # Fallback to original logic if no meaningful words found
241
+ local cleaned=$(clean_branch_name "$description")
242
+ echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//'
243
+ fi
244
+ }
245
+
246
+ # Generate branch name
247
+ if [ -n "$SHORT_NAME" ]; then
248
+ # Use provided short name, just clean it up
249
+ BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME")
250
+ else
251
+ # Generate from description with smart filtering
252
+ BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
253
+ fi
254
+
255
+ # Warn if --number and --timestamp are both specified
256
+ if [ "$USE_TIMESTAMP" = true ] && [ -n "$BRANCH_NUMBER" ]; then
257
+ >&2 echo "[specify] Warning: --number is ignored when --timestamp is used"
258
+ BRANCH_NUMBER=""
259
+ fi
260
+
261
+ # Determine branch prefix
262
+ if [ "$USE_TIMESTAMP" = true ]; then
263
+ FEATURE_NUM=$(date +%Y%m%d-%H%M%S)
264
+ BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
265
+ else
266
+ if [ -n "$BRANCH_NUMBER" ] && [[ ! "$BRANCH_NUMBER" =~ ^[0-9]+$ ]]; then
267
+ echo "Error: --number must be an unsigned integer, got '$BRANCH_NUMBER'" >&2
268
+ exit 1
269
+ fi
270
+
271
+ # Bash arithmetic is signed 64-bit; reject digit strings that would wrap.
272
+ if [ -n "$BRANCH_NUMBER" ] && ! is_feature_number_in_range "$BRANCH_NUMBER"; then
273
+ echo "Error: --number must be between 0 and $MAX_FEATURE_NUMBER, got '$BRANCH_NUMBER'" >&2
274
+ exit 1
275
+ fi
276
+
277
+ # Determine branch number from existing feature directories
278
+ if [ -z "$BRANCH_NUMBER" ]; then
279
+ HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
280
+ if [ "$HIGHEST" -eq "$MAX_FEATURE_NUMBER" ]; then
281
+ echo "Error: feature number must be between 0 and $MAX_FEATURE_NUMBER, got '9223372036854775808'" >&2
282
+ exit 1
283
+ fi
284
+ BRANCH_NUMBER=$((HIGHEST + 1))
285
+ fi
286
+
287
+ # Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal)
288
+ FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))")
289
+
290
+ # Treat an explicit number as a preference when its prefix is already used
291
+ # by a feature directory. Auto-detected numbers are already conflict-free.
292
+ if [ "$NUMBER_EXPLICIT" = true ]; then
293
+ SPEC_CONFLICT=false
294
+ REQUESTED_BRANCH_NAME=$(fit_branch_name "$FEATURE_NUM" "$BRANCH_SUFFIX")
295
+ REQUESTED_DIR="$SPECS_DIR/$REQUESTED_BRANCH_NAME"
296
+ if [ "$ALLOW_EXISTING" != true ] || [ ! -d "$REQUESTED_DIR" ]; then
297
+ spec_prefix_exists "$SPECS_DIR" "$FEATURE_NUM" && SPEC_CONFLICT=true
298
+ fi
299
+
300
+ if [ "$SPEC_CONFLICT" = true ]; then
301
+ REQUESTED_NUM="$FEATURE_NUM"
302
+ HIGHEST=$(get_highest_from_specs "$SPECS_DIR")
303
+ BRANCH_NUMBER=$HIGHEST
304
+ while true; do
305
+ if [ "$BRANCH_NUMBER" -eq "$MAX_FEATURE_NUMBER" ]; then
306
+ echo "Error: feature number must be between 0 and $MAX_FEATURE_NUMBER, got '9223372036854775808'" >&2
307
+ exit 1
308
+ fi
309
+ BRANCH_NUMBER=$((BRANCH_NUMBER + 1))
310
+ FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))")
311
+ spec_prefix_exists "$SPECS_DIR" "$FEATURE_NUM" || break
312
+ done
313
+ >&2 echo "[specify] Warning: --number $REQUESTED_NUM conflicts with an existing spec directory; using $FEATURE_NUM instead"
314
+ fi
315
+ fi
316
+
317
+ fi
318
+
319
+ # GitHub enforces a 244-byte limit on branch names
320
+ # Validate and truncate if necessary
321
+ ORIGINAL_BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
322
+ BRANCH_NAME=$(fit_branch_name "$FEATURE_NUM" "$BRANCH_SUFFIX")
323
+ if [ "$BRANCH_NAME" != "$ORIGINAL_BRANCH_NAME" ]; then
324
+ >&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit"
325
+ >&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)"
326
+ >&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)"
327
+ fi
328
+
329
+ FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
330
+ SPEC_FILE="$FEATURE_DIR/spec.md"
331
+
332
+ if [ "$DRY_RUN" != true ]; then
333
+ if [ -d "$FEATURE_DIR" ] && [ "$ALLOW_EXISTING" != true ]; then
334
+ if [ "$USE_TIMESTAMP" = true ]; then
335
+ >&2 echo "Error: Feature directory '$FEATURE_DIR' already exists. Rerun to get a new timestamp or use a different --short-name."
336
+ else
337
+ >&2 echo "Error: Feature directory '$FEATURE_DIR' already exists. Please use a different feature name or specify a different number with --number."
338
+ fi
339
+ exit 1
340
+ fi
341
+
342
+ NEEDS_SPEC=false
343
+ SPEC_TEMPLATE_FOUND=false
344
+ SPEC_TEMPLATE_CONTENT=""
345
+ if [ ! -f "$SPEC_FILE" ]; then
346
+ NEEDS_SPEC=true
347
+ if SPEC_TEMPLATE_CONTENT=$(resolve_template_content "spec-template" "$REPO_ROOT"; status=$?; printf x; exit "$status"); then
348
+ SPEC_TEMPLATE_CONTENT="${SPEC_TEMPLATE_CONTENT%x}"
349
+ SPEC_TEMPLATE_FOUND=true
350
+ else
351
+ resolve_status=$?
352
+ if [ "$resolve_status" -ne 1 ]; then
353
+ exit "$resolve_status"
354
+ fi
355
+ fi
356
+ fi
357
+
358
+ mkdir -p "$FEATURE_DIR"
359
+
360
+ if [ "$NEEDS_SPEC" = true ]; then
361
+ if [ "$SPEC_TEMPLATE_FOUND" = true ]; then
362
+ printf '%s' "$SPEC_TEMPLATE_CONTENT" > "$SPEC_FILE"
363
+ else
364
+ echo "Warning: Spec template not found; created empty spec file" >&2
365
+ touch "$SPEC_FILE"
366
+ fi
367
+ fi
368
+
369
+ # Persist to .duaer/feature.json so downstream commands can find the feature
370
+ _persist_feature_json "$REPO_ROOT" "$FEATURE_DIR"
371
+
372
+ # Inform the user how to set feature state in their own shell
373
+ printf '# To persist: export SPECIFY_FEATURE=%s\n' "$(shell_quote "$BRANCH_NAME")" >&2
374
+ printf '# export SPECIFY_FEATURE_DIRECTORY=%s\n' "$(shell_quote "$FEATURE_DIR")" >&2
375
+ fi
376
+
377
+ if $JSON_MODE; then
378
+ if command -v jq >/dev/null 2>&1; then
379
+ if [ "$DRY_RUN" = true ]; then
380
+ jq -cn \
381
+ --arg branch_name "$BRANCH_NAME" \
382
+ --arg spec_file "$SPEC_FILE" \
383
+ --arg feature_num "$FEATURE_NUM" \
384
+ '{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num,DRY_RUN:true}'
385
+ else
386
+ jq -cn \
387
+ --arg branch_name "$BRANCH_NAME" \
388
+ --arg spec_file "$SPEC_FILE" \
389
+ --arg feature_num "$FEATURE_NUM" \
390
+ '{BRANCH_NAME:$branch_name,SPEC_FILE:$spec_file,FEATURE_NUM:$feature_num}'
391
+ fi
392
+ else
393
+ if [ "$DRY_RUN" = true ]; then
394
+ printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s","DRY_RUN":true}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")"
395
+ else
396
+ printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$(json_escape "$BRANCH_NAME")" "$(json_escape "$SPEC_FILE")" "$(json_escape "$FEATURE_NUM")"
397
+ fi
398
+ fi
399
+ else
400
+ echo "BRANCH_NAME: $BRANCH_NAME"
401
+ echo "SPEC_FILE: $SPEC_FILE"
402
+ echo "FEATURE_NUM: $FEATURE_NUM"
403
+ if [ "$DRY_RUN" != true ]; then
404
+ printf '# To persist in your shell: export SPECIFY_FEATURE=%s\n' "$(shell_quote "$BRANCH_NAME")"
405
+ printf '# export SPECIFY_FEATURE_DIRECTORY=%s\n' "$(shell_quote "$FEATURE_DIR")"
406
+ fi
407
+ fi
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR="$(CDPATH="" cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
6
+ source "$SCRIPT_DIR/common.sh"
7
+
8
+ JSON_MODE=false
9
+ TEMPLATE_NAME=""
10
+
11
+ for arg in "$@"; do
12
+ case "$arg" in
13
+ --json) JSON_MODE=true ;;
14
+ --help|-h)
15
+ echo "Usage: $0 <template-name> [--json]"
16
+ exit 0
17
+ ;;
18
+ -*)
19
+ echo "ERROR: Unknown option '$arg'" >&2
20
+ exit 1
21
+ ;;
22
+ *)
23
+ if [[ -n "$TEMPLATE_NAME" ]]; then
24
+ echo "ERROR: Unexpected argument '$arg'" >&2
25
+ exit 1
26
+ fi
27
+ TEMPLATE_NAME="$arg"
28
+ ;;
29
+ esac
30
+ done
31
+
32
+ if [[ -z "$TEMPLATE_NAME" ]]; then
33
+ echo "ERROR: Template name is required" >&2
34
+ exit 1
35
+ fi
36
+
37
+ REPO_ROOT=$(get_repo_root)
38
+ if TEMPLATE_CONTENT=$(resolve_template_content "$TEMPLATE_NAME" "$REPO_ROOT"; status=$?; printf x; exit "$status"); then
39
+ TEMPLATE_CONTENT="${TEMPLATE_CONTENT%x}"
40
+ else
41
+ echo "ERROR: Could not resolve required $TEMPLATE_NAME from the template override stack for $REPO_ROOT" >&2
42
+ exit 1
43
+ fi
44
+
45
+ if $JSON_MODE; then
46
+ if has_jq; then
47
+ jq -cn \
48
+ --arg template_name "$TEMPLATE_NAME" \
49
+ --arg template_content "$TEMPLATE_CONTENT" \
50
+ '{TEMPLATE_NAME:$template_name,TEMPLATE_CONTENT:$template_content}'
51
+ else
52
+ printf '{"TEMPLATE_NAME":"%s","TEMPLATE_CONTENT":"%s"}\n' \
53
+ "$(json_escape "$TEMPLATE_NAME")" "$(json_escape "$TEMPLATE_CONTENT")"
54
+ fi
55
+ else
56
+ printf '%s' "$TEMPLATE_CONTENT"
57
+ fi
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # Parse command line arguments
6
+ JSON_MODE=false
7
+
8
+ for arg in "$@"; do
9
+ case "$arg" in
10
+ --json)
11
+ JSON_MODE=true
12
+ ;;
13
+ --help|-h)
14
+ echo "Usage: $0 [--json]"
15
+ echo " --json Output results in JSON format"
16
+ echo " --help Show this help message"
17
+ exit 0
18
+ ;;
19
+ *)
20
+ echo "ERROR: Unknown option '$arg'" >&2
21
+ exit 1
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
+ _paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
32
+ eval "$_paths_output"
33
+ unset _paths_output
34
+
35
+ # Ensure the feature directory exists
36
+ mkdir -p "$FEATURE_DIR"
37
+
38
+ # Copy plan template if plan doesn't already exist
39
+ if [[ -f "$IMPL_PLAN" ]]; then
40
+ if $JSON_MODE; then
41
+ echo "Plan already exists at $IMPL_PLAN, skipping template copy" >&2
42
+ else
43
+ echo "Plan already exists at $IMPL_PLAN, skipping template copy"
44
+ fi
45
+ else
46
+ if resolve_template_content "plan-template" "$REPO_ROOT" > "$IMPL_PLAN"; then
47
+ if $JSON_MODE; then
48
+ echo "Copied plan template to $IMPL_PLAN" >&2
49
+ else
50
+ echo "Copied plan template to $IMPL_PLAN"
51
+ fi
52
+ else
53
+ resolve_status=$?
54
+ rm -f "$IMPL_PLAN"
55
+ if [ "$resolve_status" -ne 1 ]; then
56
+ exit "$resolve_status"
57
+ fi
58
+ if $JSON_MODE; then
59
+ echo "Warning: Plan template not found" >&2
60
+ else
61
+ echo "Warning: Plan template not found"
62
+ fi
63
+ touch "$IMPL_PLAN"
64
+ fi
65
+ fi
66
+
67
+ # Output results
68
+ if $JSON_MODE; then
69
+ if has_jq; then
70
+ jq -cn \
71
+ --arg feature_spec "$FEATURE_SPEC" \
72
+ --arg impl_plan "$IMPL_PLAN" \
73
+ --arg specs_dir "$FEATURE_DIR" \
74
+ --arg branch "$CURRENT_BRANCH" \
75
+ '{FEATURE_SPEC:$feature_spec,IMPL_PLAN:$impl_plan,SPECS_DIR:$specs_dir,BRANCH:$branch}'
76
+ else
77
+ printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s"}\n' \
78
+ "$(json_escape "$FEATURE_SPEC")" "$(json_escape "$IMPL_PLAN")" "$(json_escape "$FEATURE_DIR")" "$(json_escape "$CURRENT_BRANCH")"
79
+ fi
80
+ else
81
+ echo "FEATURE_SPEC: $FEATURE_SPEC"
82
+ echo "IMPL_PLAN: $IMPL_PLAN"
83
+ echo "SPECS_DIR: $FEATURE_DIR"
84
+ echo "BRANCH: $CURRENT_BRANCH"
85
+ fi
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # Parse command line arguments
6
+ JSON_MODE=false
7
+
8
+ for arg in "$@"; do
9
+ case "$arg" in
10
+ --json) JSON_MODE=true ;;
11
+ --help|-h)
12
+ echo "Usage: $0 [--json]"
13
+ echo " --json Output results in JSON format"
14
+ echo " --help Show this help message"
15
+ exit 0
16
+ ;;
17
+ *) echo "ERROR: Unknown option '$arg'" >&2; exit 1 ;;
18
+ esac
19
+ done
20
+
21
+ # Source common functions
22
+ SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ source "$SCRIPT_DIR/common.sh"
24
+
25
+ # Get feature paths
26
+ _paths_output=$(get_feature_paths) || { echo "ERROR: Failed to resolve feature paths" >&2; exit 1; }
27
+ eval "$_paths_output"
28
+ unset _paths_output
29
+
30
+ # Validate required files
31
+ if [[ ! -f "$IMPL_PLAN" ]]; then
32
+ echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
33
+ echo "Run /duaer-plan first to create the implementation plan." >&2
34
+ exit 1
35
+ fi
36
+
37
+ if [[ ! -f "$FEATURE_SPEC" ]]; then
38
+ echo "ERROR: spec.md not found in $FEATURE_DIR" >&2
39
+ echo "Run /duaer-specify first to create the feature structure." >&2
40
+ exit 1
41
+ fi
42
+
43
+ # Build available docs list
44
+ docs=()
45
+ [[ -f "$RESEARCH" ]] && docs+=("research.md")
46
+ [[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
47
+ if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
48
+ docs+=("contracts/")
49
+ fi
50
+ [[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
51
+
52
+ # Resolve tasks template through override stack
53
+ TASKS_TEMPLATE=$(resolve_template "tasks-template" "$REPO_ROOT") || true
54
+ if TASKS_TEMPLATE_CONTENT=$(resolve_template_content "tasks-template" "$REPO_ROOT"; status=$?; printf x; exit "$status"); then
55
+ TASKS_TEMPLATE_CONTENT="${TASKS_TEMPLATE_CONTENT%x}"
56
+ else
57
+ echo "ERROR: Could not resolve required tasks-template from the template override stack for $REPO_ROOT" >&2
58
+ echo "Template 'tasks-template' was not found in any supported location (overrides, presets, extensions, or shared core). Add an override at .duaer/templates/overrides/tasks-template.md, or run 'specify init' / reinstall shared infra to restore the core .duaer/templates/tasks-template.md template." >&2
59
+ exit 1
60
+ fi
61
+
62
+ # Output results
63
+ if $JSON_MODE; then
64
+ if has_jq; then
65
+ if [[ ${#docs[@]} -eq 0 ]]; then
66
+ json_docs="[]"
67
+ else
68
+ json_docs=$(printf '%s\n' "${docs[@]}" | jq -R . | jq -s .)
69
+ fi
70
+ jq -cn \
71
+ --arg feature_dir "$FEATURE_DIR" \
72
+ --argjson docs "$json_docs" \
73
+ --arg tasks_template "${TASKS_TEMPLATE:-}" \
74
+ --arg tasks_template_content "$TASKS_TEMPLATE_CONTENT" \
75
+ '{FEATURE_DIR:$feature_dir,AVAILABLE_DOCS:$docs,TASKS_TEMPLATE:$tasks_template,TASKS_TEMPLATE_CONTENT:$tasks_template_content}'
76
+ else
77
+ if [[ ${#docs[@]} -eq 0 ]]; then
78
+ json_docs="[]"
79
+ else
80
+ json_docs=$(for d in "${docs[@]}"; do printf '"%s",' "$(json_escape "$d")"; done)
81
+ json_docs="[${json_docs%,}]"
82
+ fi
83
+ printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s,"TASKS_TEMPLATE":"%s","TASKS_TEMPLATE_CONTENT":"%s"}\n' \
84
+ "$(json_escape "$FEATURE_DIR")" "$json_docs" "$(json_escape "${TASKS_TEMPLATE:-}")" "$(json_escape "$TASKS_TEMPLATE_CONTENT")"
85
+ fi
86
+ else
87
+ echo "FEATURE_DIR: $FEATURE_DIR"
88
+ echo "TASKS_TEMPLATE: ${TASKS_TEMPLATE:-not found}"
89
+ echo "AVAILABLE_DOCS:"
90
+ check_file "$RESEARCH" "research.md"
91
+ check_file "$DATA_MODEL" "data-model.md"
92
+ check_dir "$CONTRACTS_DIR" "contracts/"
93
+ check_file "$QUICKSTART" "quickstart.md"
94
+ fi
@@ -0,0 +1,45 @@
1
+ # [CHECKLIST TYPE] Checklist: [FEATURE NAME]
2
+
3
+ **Purpose**: [Brief description of what this checklist covers]
4
+ **Created**: [DATE]
5
+ **Feature**: [Link to spec.md or relevant documentation]
6
+
7
+ **Note**: This custom checklist is generated by the `/duaer-checklist` command based on feature context and requirements.
8
+ **Review Ownership**: This checklist is a reviewer-owned requirements-quality review artifact. Mark an item `[x]` only when the reviewer determines the requirements-quality criterion is satisfied.
9
+ **Marker Semantics**: `[x]` means the criterion has been reviewed and satisfied for requirements quality. It does not mean implementation work is complete.
10
+
11
+ <!--
12
+ ============================================================================
13
+ IMPORTANT: The checklist items below are SAMPLE ITEMS for illustration only.
14
+
15
+ The /duaer-checklist command MUST replace these with actual items based on:
16
+ - User's specific checklist request
17
+ - Feature requirements from spec.md
18
+ - Technical context from plan.md
19
+ - Implementation details from tasks.md
20
+
21
+ DO NOT keep these sample items in the generated checklist file.
22
+ ============================================================================
23
+ -->
24
+
25
+ ## [Category 1]
26
+
27
+ - [ ] CHK001 First checklist item with clear action
28
+ - [ ] CHK002 Second checklist item
29
+ - [ ] CHK003 Third checklist item
30
+
31
+ ## [Category 2]
32
+
33
+ - [ ] CHK004 Another category item
34
+ - [ ] CHK005 Item with specific criteria
35
+ - [ ] CHK006 Final item in this category
36
+
37
+ ## Notes
38
+
39
+ - Mark items `[x]` only after review confirms the requirement-quality criterion is satisfied
40
+ - Leave items unchecked when they still require clarification, correction, or reviewer evaluation
41
+ - `/duaer-implement` reads checklist checkbox state as a gate and must not modify markers
42
+ - `checklists/requirements.md` has a separate built-in lifecycle maintained by `/duaer-specify` and `/duaer-clarify`
43
+ - Add comments or findings inline
44
+ - Link to relevant resources or documentation
45
+ - Items are numbered sequentially for easy reference