@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.
- package/.cursor/commands/create_intent_from_issue.md +28 -0
- package/.cursor/commands/create_pr.md +28 -0
- package/.cursor/commands/dashboard.md +39 -0
- package/.cursor/commands/deploy.md +152 -0
- package/.cursor/commands/drift_check.md +36 -0
- package/.cursor/commands/fix.md +39 -0
- package/.cursor/commands/generate_release_plan.md +31 -0
- package/.cursor/commands/generate_roadmap.md +38 -0
- package/.cursor/commands/help.md +37 -0
- package/.cursor/commands/init_project.md +26 -0
- package/.cursor/commands/kill.md +72 -0
- package/.cursor/commands/new_intent.md +68 -0
- package/.cursor/commands/pr.md +77 -0
- package/.cursor/commands/revert-plan.md +58 -0
- package/.cursor/commands/risk.md +64 -0
- package/.cursor/commands/rollback.md +43 -0
- package/.cursor/commands/scope_project.md +53 -0
- package/.cursor/commands/ship.md +345 -0
- package/.cursor/commands/status.md +71 -0
- package/.cursor/commands/suggest.md +44 -0
- package/.cursor/commands/test_shipit.md +197 -0
- package/.cursor/commands/verify.md +50 -0
- package/.cursor/rules/architect.mdc +84 -0
- package/.cursor/rules/assumption-extractor.mdc +95 -0
- package/.cursor/rules/docs.mdc +66 -0
- package/.cursor/rules/implementer.mdc +112 -0
- package/.cursor/rules/pm.mdc +136 -0
- package/.cursor/rules/qa.mdc +97 -0
- package/.cursor/rules/security.mdc +90 -0
- package/.cursor/rules/steward.mdc +99 -0
- package/.cursor/rules/test-runner.mdc +196 -0
- package/AGENTS.md +121 -0
- package/README.md +264 -0
- package/_system/architecture/CANON.md +159 -0
- package/_system/architecture/invariants.yml +87 -0
- package/_system/architecture/project-schema.json +98 -0
- package/_system/architecture/workflow-state-layout.md +68 -0
- package/_system/artifacts/SYSTEM_STATE.md +43 -0
- package/_system/artifacts/confidence-calibration.json +16 -0
- package/_system/artifacts/dependencies.md +46 -0
- package/_system/artifacts/framework-files-manifest.json +179 -0
- package/_system/artifacts/usage.json +1 -0
- package/_system/behaviors/DO_RELEASE.md +371 -0
- package/_system/behaviors/DO_RELEASE_AI.md +329 -0
- package/_system/behaviors/PREPARE_RELEASE.md +373 -0
- package/_system/behaviors/PREPARE_RELEASE_AI.md +234 -0
- package/_system/behaviors/WORK_ROOT_PLATFORM_ISSUES.md +140 -0
- package/_system/behaviors/WORK_TEST_PLAN_ISSUES.md +380 -0
- package/_system/do-not-repeat/abandoned-designs.md +18 -0
- package/_system/do-not-repeat/bad-patterns.md +19 -0
- package/_system/do-not-repeat/failed-experiments.md +18 -0
- package/_system/do-not-repeat/rejected-libraries.md +19 -0
- package/_system/drift/baselines.md +49 -0
- package/_system/drift/metrics.md +33 -0
- package/_system/golden-data/.gitkeep +0 -0
- package/_system/golden-data/README.md +47 -0
- package/_system/reports/mutation/mutation.html +492 -0
- package/_system/security/audit-allowlist.json +4 -0
- package/bin/create-shipit-app +29 -0
- package/bin/shipit +183 -0
- package/cli/src/commands/check.js +82 -0
- package/cli/src/commands/create.js +195 -0
- package/cli/src/commands/init.js +267 -0
- package/cli/src/commands/upgrade.js +196 -0
- package/cli/src/utils/config.js +27 -0
- package/cli/src/utils/file-copy.js +144 -0
- package/cli/src/utils/gitignore-merge.js +44 -0
- package/cli/src/utils/manifest.js +105 -0
- package/cli/src/utils/package-json-merge.js +163 -0
- package/cli/src/utils/project-json-merge.js +57 -0
- package/cli/src/utils/prompts.js +30 -0
- package/cli/src/utils/stack-detection.js +56 -0
- package/cli/src/utils/stack-files.js +364 -0
- package/cli/src/utils/upgrade-backup.js +159 -0
- package/cli/src/utils/version.js +64 -0
- package/dashboard-app/README.md +73 -0
- package/dashboard-app/eslint.config.js +23 -0
- package/dashboard-app/index.html +13 -0
- package/dashboard-app/package.json +30 -0
- package/dashboard-app/pnpm-lock.yaml +2721 -0
- package/dashboard-app/public/dashboard.json +66 -0
- package/dashboard-app/public/vite.svg +1 -0
- package/dashboard-app/src/App.css +141 -0
- package/dashboard-app/src/App.tsx +155 -0
- package/dashboard-app/src/assets/react.svg +1 -0
- package/dashboard-app/src/index.css +68 -0
- package/dashboard-app/src/main.tsx +10 -0
- package/dashboard-app/tsconfig.app.json +28 -0
- package/dashboard-app/tsconfig.json +4 -0
- package/dashboard-app/tsconfig.node.json +26 -0
- package/dashboard-app/vite.config.ts +7 -0
- package/package.json +116 -0
- package/scripts/README.md +70 -0
- package/scripts/audit-check.sh +125 -0
- package/scripts/calibration-report.sh +198 -0
- package/scripts/check-readiness.sh +155 -0
- package/scripts/collect-metrics.sh +116 -0
- package/scripts/command-manifest.yml +131 -0
- package/scripts/create-test-plan-issue.sh +110 -0
- package/scripts/dashboard-start.sh +16 -0
- package/scripts/deploy.sh +170 -0
- package/scripts/drift-check.sh +93 -0
- package/scripts/execute-rollback.sh +177 -0
- package/scripts/export-dashboard-json.js +208 -0
- package/scripts/fix-intents.sh +239 -0
- package/scripts/generate-dashboard.sh +136 -0
- package/scripts/generate-docs.sh +279 -0
- package/scripts/generate-project-context.sh +142 -0
- package/scripts/generate-release-plan.sh +443 -0
- package/scripts/generate-roadmap.sh +189 -0
- package/scripts/generate-system-state.sh +95 -0
- package/scripts/gh/create-intent-from-issue.sh +82 -0
- package/scripts/gh/create-issue-from-intent.sh +59 -0
- package/scripts/gh/create-pr.sh +41 -0
- package/scripts/gh/link-issue.sh +44 -0
- package/scripts/gh/on-ship-update-issue.sh +42 -0
- package/scripts/headless/README.md +8 -0
- package/scripts/headless/call-llm.js +109 -0
- package/scripts/headless/run-phase.sh +99 -0
- package/scripts/help.sh +271 -0
- package/scripts/init-project.sh +976 -0
- package/scripts/kill-intent.sh +125 -0
- package/scripts/lib/common.sh +29 -0
- package/scripts/lib/intent.sh +61 -0
- package/scripts/lib/progress.sh +57 -0
- package/scripts/lib/suggest-next.sh +131 -0
- package/scripts/lib/validate-intents.sh +240 -0
- package/scripts/lib/verify-outputs.sh +55 -0
- package/scripts/lib/workflow_state.sh +201 -0
- package/scripts/new-intent.sh +271 -0
- package/scripts/publish-npm.sh +28 -0
- package/scripts/scope-project.sh +380 -0
- package/scripts/setup-worktrees.sh +125 -0
- package/scripts/status.sh +278 -0
- package/scripts/suggest.sh +173 -0
- package/scripts/test-headless.sh +47 -0
- package/scripts/test-shipit.sh +52 -0
- package/scripts/test-workflow-state.sh +49 -0
- package/scripts/usage-report.sh +47 -0
- package/scripts/usage.sh +58 -0
- package/scripts/validate-cursor.sh +151 -0
- package/scripts/validate-project.sh +71 -0
- package/scripts/validate-vscode.sh +146 -0
- package/scripts/verify.sh +153 -0
- package/scripts/workflow-orchestrator.sh +97 -0
- package/scripts/workflow-templates/01_analysis.md.tpl +25 -0
- package/scripts/workflow-templates/02_plan.md.tpl +30 -0
- package/scripts/workflow-templates/03_implementation.md.tpl +25 -0
- package/scripts/workflow-templates/04_verification.md.tpl +29 -0
- package/scripts/workflow-templates/05_release_notes.md.tpl +16 -0
- package/scripts/workflow-templates/05_verification_legacy.md.tpl +6 -0
- package/scripts/workflow-templates/active.md.tpl +18 -0
- package/scripts/workflow-templates/phases.yml +39 -0
- package/stryker.conf.json +8 -0
- package/work/intent/templates/api-endpoint.md +124 -0
- package/work/intent/templates/bugfix.md +116 -0
- package/work/intent/templates/frontend-feature.md +115 -0
- package/work/intent/templates/generic.md +122 -0
- package/work/intent/templates/infra-change.md +121 -0
- 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"
|