@nlaprell/shipit 1.0.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 (160) hide show
  1. package/.cursor/commands/create_intent_from_issue.md +28 -0
  2. package/.cursor/commands/create_pr.md +28 -0
  3. package/.cursor/commands/dashboard.md +39 -0
  4. package/.cursor/commands/deploy.md +152 -0
  5. package/.cursor/commands/drift_check.md +36 -0
  6. package/.cursor/commands/fix.md +39 -0
  7. package/.cursor/commands/generate_release_plan.md +31 -0
  8. package/.cursor/commands/generate_roadmap.md +38 -0
  9. package/.cursor/commands/help.md +37 -0
  10. package/.cursor/commands/init_project.md +26 -0
  11. package/.cursor/commands/kill.md +72 -0
  12. package/.cursor/commands/new_intent.md +68 -0
  13. package/.cursor/commands/pr.md +77 -0
  14. package/.cursor/commands/revert-plan.md +58 -0
  15. package/.cursor/commands/risk.md +64 -0
  16. package/.cursor/commands/rollback.md +43 -0
  17. package/.cursor/commands/scope_project.md +53 -0
  18. package/.cursor/commands/ship.md +345 -0
  19. package/.cursor/commands/status.md +71 -0
  20. package/.cursor/commands/suggest.md +44 -0
  21. package/.cursor/commands/test_shipit.md +197 -0
  22. package/.cursor/commands/verify.md +50 -0
  23. package/.cursor/rules/architect.mdc +84 -0
  24. package/.cursor/rules/assumption-extractor.mdc +95 -0
  25. package/.cursor/rules/docs.mdc +66 -0
  26. package/.cursor/rules/implementer.mdc +112 -0
  27. package/.cursor/rules/pm.mdc +136 -0
  28. package/.cursor/rules/qa.mdc +97 -0
  29. package/.cursor/rules/security.mdc +90 -0
  30. package/.cursor/rules/steward.mdc +99 -0
  31. package/.cursor/rules/test-runner.mdc +196 -0
  32. package/AGENTS.md +121 -0
  33. package/README.md +264 -0
  34. package/_system/architecture/CANON.md +159 -0
  35. package/_system/architecture/invariants.yml +87 -0
  36. package/_system/architecture/project-schema.json +98 -0
  37. package/_system/architecture/workflow-state-layout.md +68 -0
  38. package/_system/artifacts/SYSTEM_STATE.md +43 -0
  39. package/_system/artifacts/confidence-calibration.json +16 -0
  40. package/_system/artifacts/dependencies.md +46 -0
  41. package/_system/artifacts/framework-files-manifest.json +179 -0
  42. package/_system/artifacts/usage.json +1 -0
  43. package/_system/behaviors/DO_RELEASE.md +371 -0
  44. package/_system/behaviors/DO_RELEASE_AI.md +329 -0
  45. package/_system/behaviors/PREPARE_RELEASE.md +373 -0
  46. package/_system/behaviors/PREPARE_RELEASE_AI.md +234 -0
  47. package/_system/behaviors/WORK_ROOT_PLATFORM_ISSUES.md +140 -0
  48. package/_system/behaviors/WORK_TEST_PLAN_ISSUES.md +380 -0
  49. package/_system/do-not-repeat/abandoned-designs.md +18 -0
  50. package/_system/do-not-repeat/bad-patterns.md +19 -0
  51. package/_system/do-not-repeat/failed-experiments.md +18 -0
  52. package/_system/do-not-repeat/rejected-libraries.md +19 -0
  53. package/_system/drift/baselines.md +49 -0
  54. package/_system/drift/metrics.md +33 -0
  55. package/_system/golden-data/.gitkeep +0 -0
  56. package/_system/golden-data/README.md +47 -0
  57. package/_system/reports/mutation/mutation.html +492 -0
  58. package/_system/security/audit-allowlist.json +4 -0
  59. package/bin/create-shipit-app +29 -0
  60. package/bin/shipit +183 -0
  61. package/cli/src/commands/check.js +82 -0
  62. package/cli/src/commands/create.js +195 -0
  63. package/cli/src/commands/init.js +267 -0
  64. package/cli/src/commands/upgrade.js +196 -0
  65. package/cli/src/utils/config.js +27 -0
  66. package/cli/src/utils/file-copy.js +144 -0
  67. package/cli/src/utils/gitignore-merge.js +44 -0
  68. package/cli/src/utils/manifest.js +105 -0
  69. package/cli/src/utils/package-json-merge.js +163 -0
  70. package/cli/src/utils/project-json-merge.js +57 -0
  71. package/cli/src/utils/prompts.js +30 -0
  72. package/cli/src/utils/stack-detection.js +56 -0
  73. package/cli/src/utils/stack-files.js +364 -0
  74. package/cli/src/utils/upgrade-backup.js +159 -0
  75. package/cli/src/utils/version.js +64 -0
  76. package/dashboard-app/README.md +73 -0
  77. package/dashboard-app/eslint.config.js +23 -0
  78. package/dashboard-app/index.html +13 -0
  79. package/dashboard-app/package.json +30 -0
  80. package/dashboard-app/pnpm-lock.yaml +2721 -0
  81. package/dashboard-app/public/dashboard.json +66 -0
  82. package/dashboard-app/public/vite.svg +1 -0
  83. package/dashboard-app/src/App.css +141 -0
  84. package/dashboard-app/src/App.tsx +155 -0
  85. package/dashboard-app/src/assets/react.svg +1 -0
  86. package/dashboard-app/src/index.css +68 -0
  87. package/dashboard-app/src/main.tsx +10 -0
  88. package/dashboard-app/tsconfig.app.json +28 -0
  89. package/dashboard-app/tsconfig.json +4 -0
  90. package/dashboard-app/tsconfig.node.json +26 -0
  91. package/dashboard-app/vite.config.ts +7 -0
  92. package/package.json +116 -0
  93. package/scripts/README.md +70 -0
  94. package/scripts/audit-check.sh +125 -0
  95. package/scripts/calibration-report.sh +198 -0
  96. package/scripts/check-readiness.sh +155 -0
  97. package/scripts/collect-metrics.sh +116 -0
  98. package/scripts/command-manifest.yml +131 -0
  99. package/scripts/create-test-plan-issue.sh +110 -0
  100. package/scripts/dashboard-start.sh +16 -0
  101. package/scripts/deploy.sh +170 -0
  102. package/scripts/drift-check.sh +93 -0
  103. package/scripts/execute-rollback.sh +177 -0
  104. package/scripts/export-dashboard-json.js +208 -0
  105. package/scripts/fix-intents.sh +239 -0
  106. package/scripts/generate-dashboard.sh +136 -0
  107. package/scripts/generate-docs.sh +279 -0
  108. package/scripts/generate-project-context.sh +142 -0
  109. package/scripts/generate-release-plan.sh +443 -0
  110. package/scripts/generate-roadmap.sh +189 -0
  111. package/scripts/generate-system-state.sh +95 -0
  112. package/scripts/gh/create-intent-from-issue.sh +82 -0
  113. package/scripts/gh/create-issue-from-intent.sh +59 -0
  114. package/scripts/gh/create-pr.sh +41 -0
  115. package/scripts/gh/link-issue.sh +44 -0
  116. package/scripts/gh/on-ship-update-issue.sh +42 -0
  117. package/scripts/headless/README.md +8 -0
  118. package/scripts/headless/call-llm.js +109 -0
  119. package/scripts/headless/run-phase.sh +99 -0
  120. package/scripts/help.sh +271 -0
  121. package/scripts/init-project.sh +976 -0
  122. package/scripts/kill-intent.sh +125 -0
  123. package/scripts/lib/common.sh +29 -0
  124. package/scripts/lib/intent.sh +61 -0
  125. package/scripts/lib/progress.sh +57 -0
  126. package/scripts/lib/suggest-next.sh +131 -0
  127. package/scripts/lib/validate-intents.sh +240 -0
  128. package/scripts/lib/verify-outputs.sh +55 -0
  129. package/scripts/lib/workflow_state.sh +201 -0
  130. package/scripts/new-intent.sh +271 -0
  131. package/scripts/publish-npm.sh +28 -0
  132. package/scripts/scope-project.sh +380 -0
  133. package/scripts/setup-worktrees.sh +125 -0
  134. package/scripts/status.sh +278 -0
  135. package/scripts/suggest.sh +173 -0
  136. package/scripts/test-headless.sh +47 -0
  137. package/scripts/test-shipit.sh +52 -0
  138. package/scripts/test-workflow-state.sh +49 -0
  139. package/scripts/usage-report.sh +47 -0
  140. package/scripts/usage.sh +58 -0
  141. package/scripts/validate-cursor.sh +151 -0
  142. package/scripts/validate-project.sh +71 -0
  143. package/scripts/validate-vscode.sh +146 -0
  144. package/scripts/verify.sh +153 -0
  145. package/scripts/workflow-orchestrator.sh +97 -0
  146. package/scripts/workflow-templates/01_analysis.md.tpl +25 -0
  147. package/scripts/workflow-templates/02_plan.md.tpl +30 -0
  148. package/scripts/workflow-templates/03_implementation.md.tpl +25 -0
  149. package/scripts/workflow-templates/04_verification.md.tpl +29 -0
  150. package/scripts/workflow-templates/05_release_notes.md.tpl +16 -0
  151. package/scripts/workflow-templates/05_verification_legacy.md.tpl +6 -0
  152. package/scripts/workflow-templates/active.md.tpl +18 -0
  153. package/scripts/workflow-templates/phases.yml +39 -0
  154. package/stryker.conf.json +8 -0
  155. package/work/intent/templates/api-endpoint.md +124 -0
  156. package/work/intent/templates/bugfix.md +116 -0
  157. package/work/intent/templates/frontend-feature.md +115 -0
  158. package/work/intent/templates/generic.md +122 -0
  159. package/work/intent/templates/infra-change.md +121 -0
  160. package/work/intent/templates/refactor.md +116 -0
@@ -0,0 +1,55 @@
1
+ #!/bin/bash
2
+
3
+ # Output Verification Library
4
+ # Provides functions to verify script outputs and display summaries
5
+
6
+ set -euo pipefail
7
+
8
+ # Colors
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ RED='\033[0;31m'
12
+ NC='\033[0m'
13
+
14
+ # Verify a file exists
15
+ verify_file_exists() {
16
+ local filepath="$1"
17
+ local description="${2:-$(basename "$filepath")}"
18
+
19
+ if [ -f "$filepath" ]; then
20
+ echo -e "${GREEN}✓${NC} $description"
21
+ return 0
22
+ else
23
+ echo -e "${RED}✗${NC} $description (missing)"
24
+ return 1
25
+ fi
26
+ }
27
+
28
+ # Verify intent files were created
29
+ verify_intent_files() {
30
+ local count="${1:-0}"
31
+ local pattern="${2:-F-*.md}"
32
+ local intent_dir="${INTENT_DIR:-work/intent}"
33
+
34
+ if [ $count -eq 0 ]; then
35
+ return 0
36
+ fi
37
+
38
+ local actual_count=$(find "$intent_dir" -name "$pattern" ! -name "_TEMPLATE.md" 2>/dev/null | wc -l | tr -d ' ')
39
+
40
+ if [ "$actual_count" -ge "$count" ]; then
41
+ echo -e "${GREEN}✓${NC} Generated $actual_count intent file(s)"
42
+ return 0
43
+ else
44
+ echo -e "${RED}✗${NC} Expected $count intent file(s), found $actual_count"
45
+ return 1
46
+ fi
47
+ }
48
+
49
+ # Print summary header
50
+ print_summary_header() {
51
+ local title="${1:-Summary}"
52
+ echo ""
53
+ echo -e "${YELLOW}$title${NC}"
54
+ echo ""
55
+ }
@@ -0,0 +1,201 @@
1
+ #!/bin/bash
2
+
3
+ # Workflow state path resolution for flat vs per-intent layout.
4
+ # Source this after common.sh. See _system/architecture/workflow-state-layout.md.
5
+ # Usage: source scripts/lib/workflow_state.sh
6
+ # get_workflow_state_dir [intent_id] -> path (no trailing slash for dir)
7
+ # list_active_intent_ids -> space-separated list
8
+ # ensure_workflow_state_dir intent_id -> path (for writing)
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
12
+ WS_BASE="$REPO_ROOT/work/workflow-state"
13
+ ACTIVE_FILE="$WS_BASE/active.md"
14
+
15
+ # List intent IDs that have per-intent state directories (F-*, B-*, T-*).
16
+ list_per_intent_dirs() {
17
+ local ids=()
18
+ if [ ! -d "$WS_BASE" ]; then
19
+ echo ""
20
+ return 0
21
+ fi
22
+ local d
23
+ for d in "$WS_BASE"/F-* "$WS_BASE"/B-* "$WS_BASE"/T-*; do
24
+ [ -d "$d" ] || continue
25
+ ids+=("$(basename "$d")")
26
+ done 2>/dev/null || true
27
+ [ ${#ids[@]} -eq 0 ] && echo "" || echo "${ids[*]}"
28
+ }
29
+
30
+ # Parse active.md for active intent id(s).
31
+ # Legacy: **Intent ID:** F-001 (single). Multi: ## Active intents block with "F-001 | Phase | status" lines.
32
+ # Output: space-separated list of intent ids considered active (from active.md and/or per-intent dirs).
33
+ list_active_intent_ids() {
34
+ local from_file=()
35
+ local from_dirs=()
36
+ if [ -f "$ACTIVE_FILE" ]; then
37
+ local line
38
+ while IFS= read -r line; do
39
+ if [[ "$line" =~ \*\*Intent\ ID:\*\*\ ([^[:space:]]+) ]]; then
40
+ local id="${BASH_REMATCH[1]}"
41
+ [[ "$id" != "none" ]] && from_file+=("$id")
42
+ fi
43
+ if [[ "$line" =~ ^(F-[0-9]+|B-[0-9]+|T-[0-9]+)[[:space:]]*\| ]]; then
44
+ from_file+=("${BASH_REMATCH[1]}")
45
+ fi
46
+ done < "$ACTIVE_FILE"
47
+ fi
48
+ local dirs
49
+ dirs="$(list_per_intent_dirs)"
50
+ if [ -n "$dirs" ]; then
51
+ from_dirs=($dirs)
52
+ fi
53
+ # Merge: unique list, file first then dirs
54
+ local seen=()
55
+ local out=()
56
+ for id in "${from_file[@]}" "${from_dirs[@]}"; do
57
+ [ -z "$id" ] && continue
58
+ found=0
59
+ if [ ${#seen[@]} -gt 0 ]; then
60
+ for s in "${seen[@]}"; do [ "$s" = "$id" ] && found=1 && break; done
61
+ fi
62
+ [ "$found" -eq 0 ] && seen+=("$id") && out+=("$id")
63
+ done
64
+ [ ${#out[@]} -eq 0 ] && echo "" || echo "${out[*]}"
65
+ }
66
+
67
+ # Is flat layout in use? (flat phase files exist and no per-intent dirs, or flat is only state)
68
+ flat_in_use() {
69
+ if [ -f "$WS_BASE/01_analysis.md" ] || [ -f "$WS_BASE/02_plan.md" ]; then
70
+ local dirs
71
+ dirs="$(list_per_intent_dirs)"
72
+ [ -z "$dirs" ] && return 0
73
+ fi
74
+ return 1
75
+ }
76
+
77
+ # Resolve workflow state directory for reading or writing.
78
+ # Usage: get_workflow_state_dir [intent_id]
79
+ # - With intent_id: returns work/workflow-state/<intent_id> (per-intent).
80
+ # - Without intent_id: returns flat work/workflow-state if flat layout in use; otherwise empty (caller must pass intent_id when multiple actives).
81
+ get_workflow_state_dir() {
82
+ local intent_id="${1:-}"
83
+ if [ -n "$intent_id" ]; then
84
+ if [ -d "$WS_BASE/$intent_id" ]; then
85
+ echo "$WS_BASE/$intent_id"
86
+ return 0
87
+ fi
88
+ # Flat in use and active.md single intent matches: return flat so existing single-intent repos keep working.
89
+ if flat_in_use; then
90
+ local actives
91
+ actives=($(list_active_intent_ids))
92
+ if [ "${#actives[@]}" -eq 1 ] && [ "${actives[0]}" = "$intent_id" ]; then
93
+ echo "$WS_BASE"
94
+ return 0
95
+ fi
96
+ fi
97
+ echo "$WS_BASE/$intent_id"
98
+ return 0
99
+ fi
100
+ # No intent_id: return flat if flat in use (single-intent backward compat).
101
+ if flat_in_use; then
102
+ echo "$WS_BASE"
103
+ return 0
104
+ fi
105
+ # Multiple per-intent dirs or no flat: no single "current" dir.
106
+ echo ""
107
+ return 0
108
+ }
109
+
110
+ # For writing: ensure we have a directory path for this intent. When to use flat vs per-intent:
111
+ # - If flat is in use and this is the only active intent, use flat.
112
+ # - Otherwise use work/workflow-state/<intent_id>/.
113
+ # Usage: ensure_workflow_state_dir <intent_id>
114
+ # Output: path to dir (no trailing slash). Creates dir if per-intent.
115
+ ensure_workflow_state_dir() {
116
+ local intent_id="$1"
117
+ [ -n "$intent_id" ] || { echo ""; return 1; }
118
+ if [ -d "$WS_BASE/$intent_id" ]; then
119
+ echo "$WS_BASE/$intent_id"
120
+ return 0
121
+ fi
122
+ if flat_in_use; then
123
+ local actives
124
+ actives=($(list_active_intent_ids))
125
+ if [ "${#actives[@]}" -le 1 ] && { [ "${#actives[@]}" -eq 0 ] || [ "${actives[0]}" = "$intent_id" ]; }; then
126
+ echo "$WS_BASE"
127
+ return 0
128
+ fi
129
+ fi
130
+ mkdir -p "$WS_BASE/$intent_id"
131
+ echo "$WS_BASE/$intent_id"
132
+ return 0
133
+ }
134
+
135
+ # Append an intent to active.md's active list (for multi-intent). Call when starting workflow for intent_id.
136
+ # If active.md has legacy single **Intent ID:** and it's "none" or same intent, update in place.
137
+ # If we're adding a second intent, ensure "## Active intents" exists and append "intent_id | Phase | active".
138
+ append_or_set_active_intent() {
139
+ local intent_id="$1"
140
+ local phase_name="${2:-Analysis}"
141
+ [ -n "$intent_id" ] || return 1
142
+ mkdir -p "$(dirname "$ACTIVE_FILE")"
143
+ if [ ! -f "$ACTIVE_FILE" ]; then
144
+ cat > "$ACTIVE_FILE" << EOF
145
+ # Active Intent
146
+
147
+ **Intent ID:** $intent_id
148
+ **Status:** active
149
+ **Current Phase:** $phase_name
150
+ **Started:** $(date -u +"%Y-%m-%dT%H:%M:%SZ")
151
+
152
+ ## Progress
153
+
154
+ - [ ] Phase 1: Analysis
155
+ - [ ] Phase 2: Planning
156
+ - [ ] Phase 3: Implementation
157
+ - [ ] Phase 4: Verification
158
+ - [ ] Phase 5: Release Notes
159
+
160
+ ## Active intents
161
+
162
+ $intent_id | $phase_name | active
163
+ EOF
164
+ return 0
165
+ fi
166
+ local actives
167
+ actives=($(list_active_intent_ids))
168
+ if [ "${#actives[@]}" -eq 0 ]; then
169
+ # Replace legacy "none" or empty with this intent
170
+ sed -e "s/**Intent ID:\*\* .*/**Intent ID:** $intent_id/" \
171
+ -e "s/**Status:\*\* .*/**Status:** active/" \
172
+ -e "s/**Current Phase:\*\* .*/**Current Phase:** $phase_name/" \
173
+ -e "s/**Started:\*\* .*/**Started:** $(date -u +"%Y-%m-%dT%H:%M:%SZ")/" \
174
+ "$ACTIVE_FILE" > "${ACTIVE_FILE}.tmp" && mv "${ACTIVE_FILE}.tmp" "$ACTIVE_FILE"
175
+ if ! grep -q "## Active intents" "$ACTIVE_FILE"; then
176
+ printf '\n## Active intents\n\n%s | %s | active\n' "$intent_id" "$phase_name" >> "$ACTIVE_FILE"
177
+ else
178
+ grep -q "$intent_id" "$ACTIVE_FILE" || printf '%s | %s | active\n' "$intent_id" "$phase_name" >> "$ACTIVE_FILE"
179
+ fi
180
+ return 0
181
+ fi
182
+ if [[ " ${actives[*]} " == *" $intent_id "* ]]; then
183
+ # Already listed; update phase if we have ## Active intents
184
+ if grep -q "## Active intents" "$ACTIVE_FILE"; then
185
+ local tmp
186
+ tmp="$(mktemp)"
187
+ awk -v id="$intent_id" -v ph="$phase_name" '
188
+ $0 ~ "^" id "[[:space:]]*\\|" { print id " | " ph " | active"; next }
189
+ { print }
190
+ ' "$ACTIVE_FILE" > "$tmp" && mv "$tmp" "$ACTIVE_FILE"
191
+ fi
192
+ return 0
193
+ fi
194
+ # Add new intent to list
195
+ if ! grep -q "## Active intents" "$ACTIVE_FILE"; then
196
+ printf '\n## Active intents\n\n%s | %s | active\n' "$intent_id" "$phase_name" >> "$ACTIVE_FILE"
197
+ else
198
+ printf '%s | %s | active\n' "$intent_id" "$phase_name" >> "$ACTIVE_FILE"
199
+ fi
200
+ return 0
201
+ }
@@ -0,0 +1,271 @@
1
+ #!/bin/bash
2
+
3
+ # Automated Intent Creation Script
4
+ # Creates a new intent file from template with interactive prompts
5
+
6
+ set -euo pipefail
7
+
8
+ # Error handling
9
+ error_exit() {
10
+ echo "ERROR: $1" >&2
11
+ exit "${2:-1}"
12
+ }
13
+
14
+ # Colors for output
15
+ RED='\033[0;31m'
16
+ GREEN='\033[0;32m'
17
+ YELLOW='\033[1;33m'
18
+ NC='\033[0m' # No Color
19
+
20
+ INTENT_BASE_DIR="work/intent"
21
+ TEMPLATES_DIR="$INTENT_BASE_DIR/templates"
22
+
23
+ # Validate prerequisites: default template must exist
24
+ if [ ! -f "$INTENT_BASE_DIR/_TEMPLATE.md" ]; then
25
+ error_exit "Template file not found: $INTENT_BASE_DIR/_TEMPLATE.md"
26
+ fi
27
+
28
+ # Get intent type
29
+ echo -e "${YELLOW}Intent Type:${NC}"
30
+ echo "1) Feature (F-###)"
31
+ echo "2) Bug (B-###)"
32
+ echo "3) Tech Debt (T-###)"
33
+ read -p "Select type [1-3]: " type_choice
34
+
35
+ case $type_choice in
36
+ 1) INTENT_TYPE="feature"; PREFIX="F"; INTENT_DIR="$INTENT_BASE_DIR/features" ;;
37
+ 2) INTENT_TYPE="bug"; PREFIX="B"; INTENT_DIR="$INTENT_BASE_DIR/bugs" ;;
38
+ 3) INTENT_TYPE="tech-debt"; PREFIX="T"; INTENT_DIR="$INTENT_BASE_DIR/tech-debt" ;;
39
+ *) error_exit "Invalid choice" 1 ;;
40
+ esac
41
+
42
+ # Template (optional): Generic or kind-specific
43
+ echo -e "${YELLOW}Template (optional):${NC}"
44
+ echo "1) Generic (default)"
45
+ echo "2) API endpoint"
46
+ echo "3) Frontend feature"
47
+ echo "4) Infra change"
48
+ echo "5) Bugfix"
49
+ echo "6) Refactor"
50
+ read -p "Select template [1-6, default=1]: " template_choice
51
+
52
+ case $template_choice in
53
+ 1) TEMPLATE_FILE="$INTENT_BASE_DIR/_TEMPLATE.md" ;;
54
+ 2) TEMPLATE_FILE="$TEMPLATES_DIR/api-endpoint.md" ;;
55
+ 3) TEMPLATE_FILE="$TEMPLATES_DIR/frontend-feature.md" ;;
56
+ 4) TEMPLATE_FILE="$TEMPLATES_DIR/infra-change.md" ;;
57
+ 5) TEMPLATE_FILE="$TEMPLATES_DIR/bugfix.md" ;;
58
+ 6) TEMPLATE_FILE="$TEMPLATES_DIR/refactor.md" ;;
59
+ *) TEMPLATE_FILE="$INTENT_BASE_DIR/_TEMPLATE.md" ;;
60
+ esac
61
+ # Fallback to default if chosen template file does not exist (e.g. project without templates/)
62
+ if [ ! -f "$TEMPLATE_FILE" ]; then
63
+ TEMPLATE_FILE="$INTENT_BASE_DIR/_TEMPLATE.md"
64
+ fi
65
+
66
+ mkdir -p "$INTENT_DIR"
67
+
68
+ # Get next intent number without overwriting existing files
69
+ LAST_INTENT=0
70
+ while IFS= read -r file; do
71
+ [ -e "$file" ] || continue
72
+ base="$(basename "$file")"
73
+ if [[ "$base" =~ ^[FBT]-([0-9]+)\.md$ ]]; then
74
+ num="${BASH_REMATCH[1]}"
75
+ if ((10#$num > LAST_INTENT)); then
76
+ LAST_INTENT=$((10#$num))
77
+ fi
78
+ fi
79
+ done < <(find "$INTENT_BASE_DIR" -type f -name '[FBT]-*.md' 2>/dev/null)
80
+ NEXT_NUM=$((LAST_INTENT + 1))
81
+ INTENT_ID="${PREFIX}-$(printf "%03d" $NEXT_NUM)"
82
+
83
+ # Get title
84
+ read -p "Title: " TITLE
85
+ if [ -z "$TITLE" ]; then
86
+ error_exit "Title is required" 1
87
+ fi
88
+
89
+ # Get motivation
90
+ echo -e "${YELLOW}Motivation (press Enter after each line, type 'done' when finished):${NC}"
91
+ MOTIVATION=""
92
+ while IFS= read -r line; do
93
+ [ "$line" = "done" ] && break
94
+ MOTIVATION="${MOTIVATION}- ${line}\n"
95
+ done
96
+
97
+ # Get priority
98
+ echo -e "${YELLOW}Priority:${NC}"
99
+ echo "1) p0 (Critical)"
100
+ echo "2) p1 (High)"
101
+ echo "3) p2 (Medium)"
102
+ echo "4) p3 (Low)"
103
+ read -p "Select priority [1-4, default=2]: " priority_choice
104
+
105
+ case $priority_choice in
106
+ 1) PRIORITY="p0" ;;
107
+ 2) PRIORITY="p1" ;;
108
+ 3) PRIORITY="p2" ;;
109
+ 4) PRIORITY="p3" ;;
110
+ *) PRIORITY="p2" ;;
111
+ esac
112
+
113
+ # Get effort
114
+ echo -e "${YELLOW}Effort:${NC}"
115
+ echo "1) Small (s)"
116
+ echo "2) Medium (m)"
117
+ echo "3) Large (l)"
118
+ read -p "Select effort [1-3, default=2]: " effort_choice
119
+
120
+ case $effort_choice in
121
+ 1) EFFORT="s" ;;
122
+ 2) EFFORT="m" ;;
123
+ 3) EFFORT="l" ;;
124
+ *) EFFORT="m" ;;
125
+ esac
126
+
127
+ # Get release target
128
+ echo -e "${YELLOW}Release Target:${NC}"
129
+ echo "1) R1 (Next release)"
130
+ echo "2) R2 (Following release)"
131
+ echo "3) R3 (Future)"
132
+ echo "4) R4 (Backlog)"
133
+ read -p "Select release target [1-4, default=2]: " release_choice
134
+
135
+ case $release_choice in
136
+ 1) RELEASE_TARGET="R1" ;;
137
+ 2) RELEASE_TARGET="R2" ;;
138
+ 3) RELEASE_TARGET="R3" ;;
139
+ 4) RELEASE_TARGET="R4" ;;
140
+ *) RELEASE_TARGET="R2" ;;
141
+ esac
142
+
143
+ # Get dependencies
144
+ echo -e "${YELLOW}Dependencies:${NC}"
145
+ echo "Enter intent IDs (e.g., F-001, F-002) or 'none' for no dependencies"
146
+ echo "Press Enter after each ID, type 'done' when finished:"
147
+ DEPENDENCIES=()
148
+ while IFS= read -r dep_line; do
149
+ [ "$dep_line" = "done" ] && break
150
+ [ -z "$dep_line" ] && continue
151
+ if [ "$dep_line" = "none" ]; then
152
+ DEPENDENCIES=()
153
+ break
154
+ fi
155
+ # Validate format (F-###, B-###, T-###)
156
+ if [[ "$dep_line" =~ ^[FBT]-[0-9]+$ ]]; then
157
+ DEPENDENCIES+=("$dep_line")
158
+ else
159
+ echo -e "${RED}Invalid format. Use F-###, B-###, or T-###${NC}"
160
+ fi
161
+ done
162
+
163
+ # Get risk level
164
+ echo -e "${YELLOW}Risk Level:${NC}"
165
+ echo "1) Low"
166
+ echo "2) Medium"
167
+ echo "3) High"
168
+ read -p "Select risk [1-3, default=1]: " risk_choice
169
+
170
+ case $risk_choice in
171
+ 1) RISK_LEVEL="low" ;;
172
+ 2) RISK_LEVEL="medium" ;;
173
+ 3) RISK_LEVEL="high" ;;
174
+ *) RISK_LEVEL="low" ;;
175
+ esac
176
+
177
+ # Create intent file
178
+ INTENT_FILE="$INTENT_DIR/$INTENT_ID.md"
179
+ if [ -e "$INTENT_FILE" ]; then
180
+ error_exit "Intent file already exists: $INTENT_FILE" 1
181
+ fi
182
+
183
+ # Read template and replace placeholders (portable across BSD/GNU sed)
184
+ TEMP_FILE="$(mktemp)"
185
+ MOTIVATION_FILE="$(mktemp)"
186
+
187
+ sed -e "s/# F-###: Title/# $INTENT_ID: $TITLE/" \
188
+ -e "s/feature | bug | tech-debt/$INTENT_TYPE/" \
189
+ -e "s/planned | active | blocked | validating | shipped | killed/planned/" \
190
+ -e "s/p0 | p1 | p2 | p3/$PRIORITY/" \
191
+ -e "s/s | m | l/$EFFORT/" \
192
+ -e "s/R1 | R2 | R3 | R4/$RELEASE_TARGET/" \
193
+ -e "s/low | medium | high/$RISK_LEVEL/" \
194
+ "$TEMPLATE_FILE" > "$TEMP_FILE" || error_exit "Failed to create intent template"
195
+
196
+ printf "%b" "$MOTIVATION" | sed 's/^/ /' > "$MOTIVATION_FILE"
197
+
198
+ # Create dependencies file
199
+ DEP_FILE="$(mktemp)"
200
+ if [ ${#DEPENDENCIES[@]} -eq 0 ]; then
201
+ echo "- (none)" > "$DEP_FILE"
202
+ else
203
+ for dep in "${DEPENDENCIES[@]}"; do
204
+ echo "- $dep" >> "$DEP_FILE"
205
+ done
206
+ fi
207
+
208
+ awk -v motivation_file="$MOTIVATION_FILE" -v deps_file="$DEP_FILE" '
209
+ $0 == "(Why it exists, 1–3 bullets)" {
210
+ while ((getline line < motivation_file) > 0) print line;
211
+ close(motivation_file);
212
+ next;
213
+ }
214
+ /^- \(Other intent IDs that must ship first\)$/ {
215
+ while ((getline line < deps_file) > 0) print line;
216
+ close(deps_file);
217
+ # Skip remaining placeholder dependency lines
218
+ while (getline > 0) {
219
+ if (/^## / || /^$/) {
220
+ print;
221
+ break;
222
+ }
223
+ # Skip placeholder lines (lines starting with "- (")
224
+ if (!/^- \(/) {
225
+ print;
226
+ }
227
+ }
228
+ next;
229
+ }
230
+ { print }
231
+ ' "$TEMP_FILE" > "$INTENT_FILE" || error_exit "Failed to create intent file"
232
+
233
+ rm -f "$TEMP_FILE" "$MOTIVATION_FILE" "$DEP_FILE"
234
+
235
+ echo -e "${GREEN}✓ Created intent: $INTENT_FILE${NC}"
236
+
237
+ # Refresh roadmap after creating a new intent (best effort)
238
+ if [ -x "./scripts/generate-roadmap.sh" ]; then
239
+ echo -e "${YELLOW}Updating roadmap...${NC}"
240
+ ./scripts/generate-roadmap.sh || echo "WARNING: roadmap generation failed"
241
+ fi
242
+
243
+ # Refresh release plan after creating a new intent (best effort)
244
+ if [ -x "./scripts/generate-release-plan.sh" ]; then
245
+ echo -e "${YELLOW}Updating release plan...${NC}"
246
+ ./scripts/generate-release-plan.sh || echo "WARNING: release plan generation failed"
247
+ fi
248
+
249
+ echo ""
250
+ echo -e "${GREEN}════════════════════════════════════════${NC}"
251
+ echo -e "${GREEN}✓ Intent created successfully!${NC}"
252
+ echo -e "${GREEN}════════════════════════════════════════${NC}"
253
+ echo ""
254
+ echo -e "${YELLOW}Intent Details:${NC}"
255
+ echo " ID: $INTENT_ID"
256
+ echo " Title: $TITLE"
257
+ echo " Type: $INTENT_TYPE"
258
+ echo " Priority: $PRIORITY"
259
+ echo " Effort: $EFFORT"
260
+ echo " Release Target: $RELEASE_TARGET"
261
+ echo " Risk Level: $RISK_LEVEL"
262
+ if [ ${#DEPENDENCIES[@]} -gt 0 ]; then
263
+ echo " Dependencies: ${DEPENDENCIES[*]}"
264
+ else
265
+ echo " Dependencies: (none)"
266
+ fi
267
+ echo ""
268
+ echo -e "${YELLOW}Next steps:${NC}"
269
+ echo "1. Review $INTENT_FILE"
270
+ echo "2. Fill in acceptance criteria, invariants, and other details"
271
+ echo "3. Run: /ship $INTENT_ID"
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+ # Publish @nlaprell/shipit to npm. Run from repo root.
3
+ # Auth: set NODE_AUTH_TOKEN, or put your npm token (one line, no newline) in .npm-token (gitignored).
4
+ # Token must be granular with Bypass 2FA for scope @nlaprell.
5
+ # Uses a temp .npmrc so stale token in ~/.npmrc is ignored.
6
+ set -euo pipefail
7
+ cd "$(dirname "$0")/.."
8
+
9
+ if [[ -n "${NODE_AUTH_TOKEN:-}" ]]; then
10
+ echo "Using NODE_AUTH_TOKEN from environment."
11
+ elif [[ -f .npm-token ]]; then
12
+ export NODE_AUTH_TOKEN
13
+ NODE_AUTH_TOKEN=$(cat .npm-token | tr -d '\n\r')
14
+ echo "Using token from .npm-token"
15
+ else
16
+ echo "No NODE_AUTH_TOKEN and no .npm-token file."
17
+ echo "Create .npm-token with one line: your granular npm token (Bypass 2FA, scope @nlaprell)."
18
+ echo "Or run: NODE_AUTH_TOKEN=<token> pnpm publish:npm"
19
+ exit 1
20
+ fi
21
+
22
+ # Force npm to use only our token (ignore stale ~/.npmrc)
23
+ tmp_npmrc=$(mktemp)
24
+ echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > "$tmp_npmrc"
25
+ trap 'rm -f "$tmp_npmrc"' EXIT
26
+
27
+ echo "Publishing @nlaprell/shipit@$(node -p "require('./package.json').version")..."
28
+ npm publish --access public --userconfig "$tmp_npmrc"