@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,95 @@
1
+ #!/bin/bash
2
+
3
+ # Generate SYSTEM_STATE.md Script
4
+ # Creates a global summary for agents to maintain understanding
5
+
6
+ set -euo pipefail
7
+
8
+ error_exit() {
9
+ echo "ERROR: $1" >&2
10
+ exit "${2:-1}"
11
+ }
12
+
13
+ # Colors
14
+ GREEN='\033[0;32m'
15
+ YELLOW='\033[1;33m'
16
+ BLUE='\033[0;34m'
17
+ NC='\033[0m'
18
+
19
+ mkdir -p _system/artifacts
20
+ STATE_FILE="_system/artifacts/SYSTEM_STATE.md"
21
+
22
+ echo -e "${BLUE}Generating SYSTEM_STATE.md...${NC}"
23
+
24
+ # Check prerequisites
25
+ if [ -f "project.json" ]; then
26
+ PROJECT_NAME=$(jq -r '.name' project.json 2>/dev/null || echo "project")
27
+ else
28
+ PROJECT_NAME=$(jq -r '.name' package.json 2>/dev/null || echo "shipit")
29
+ fi
30
+
31
+ # Collect state information
32
+ INTENT_TOTAL=$(find intent -name "*.md" ! -name "_TEMPLATE.md" 2>/dev/null | wc -l | tr -d ' ')
33
+ INTENT_ACTIVE=$(find intent -name "*.md" ! -name "_TEMPLATE.md" -print0 2>/dev/null | xargs -0 grep -l "Status.*active" 2>/dev/null | wc -l | tr -d ' ' || echo "0")
34
+ INTENT_PLANNED=$(find intent -name "*.md" ! -name "_TEMPLATE.md" -print0 2>/dev/null | xargs -0 grep -l "Status.*planned" 2>/dev/null | wc -l | tr -d ' ' || echo "0")
35
+ INTENT_SHIPPED=$(find intent -name "*.md" ! -name "_TEMPLATE.md" -print0 2>/dev/null | xargs -0 grep -l "Status.*shipped" 2>/dev/null | wc -l | tr -d ' ' || echo "0")
36
+
37
+ # Get active intent
38
+ ACTIVE_INTENT=$(grep -h "Intent ID:" work/workflow-state/active.md 2>/dev/null | grep -o "F-[0-9]*\|B-[0-9]*\|T-[0-9]*" | head -1 || echo "none")
39
+
40
+ # Get recent intents
41
+ RECENT_INTENTS=$(find intent -name "*.md" ! -name "_TEMPLATE.md" -type f -exec basename {} .md \; 2>/dev/null | head -10 | tr '\n' ',' | sed 's/,$//' || echo "none")
42
+
43
+ # Generate system state
44
+ cat > "$STATE_FILE" << EOF || error_exit "Failed to generate SYSTEM_STATE.md"
45
+ # System State
46
+
47
+ **Generated:** $(date -u +"%Y-%m-%dT%H:%M:%SZ")
48
+ **Project:** $PROJECT_NAME
49
+
50
+ ## Global Summary
51
+
52
+ This file provides a global view of the system state for agents to maintain coherence.
53
+
54
+ ## Intent Status
55
+
56
+ - **Total:** $INTENT_TOTAL
57
+ - **Active:** $INTENT_ACTIVE
58
+ - **Planned:** $INTENT_PLANNED
59
+ - **Shipped:** $INTENT_SHIPPED
60
+
61
+ ## Current Work
62
+
63
+ - **Active Intent:** $ACTIVE_INTENT
64
+ - **Recent Intents:** $RECENT_INTENTS
65
+
66
+ ## System Health
67
+
68
+ - **Workflow State:** [Check work/workflow-state/active.md]
69
+ - **Drift Status:** [Check _system/drift/metrics.md]
70
+ - **Test Coverage:** [Run: pnpm test:coverage]
71
+
72
+ ## Key Decisions
73
+
74
+ [Recent architectural decisions]
75
+
76
+ ## Blockers
77
+
78
+ [Current blockers and dependencies]
79
+
80
+ ## Next Actions
81
+
82
+ [Planned next actions]
83
+
84
+ ---
85
+
86
+ *This file is auto-generated. Run \`pnpm generate-system-state\` to update.*
87
+ *Agents should read this file first to understand global state.*
88
+ EOF
89
+
90
+ echo -e "${GREEN}✓ Generated $STATE_FILE${NC}"
91
+ echo ""
92
+ echo -e "${YELLOW}System State Summary:${NC}"
93
+ echo " Total Intents: $INTENT_TOTAL"
94
+ echo " Active Intent: $ACTIVE_INTENT"
95
+ echo ""
@@ -0,0 +1,82 @@
1
+ #!/bin/bash
2
+ # Create a new intent from a GitHub issue (fetch title/body, create intent file, set GitHub issue field).
3
+ # Usage: create-intent-from-issue.sh <issue-number>
4
+ # Requires: gh. Creates work/intent/features/F-XXX.md by default.
5
+
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
10
+ # shellcheck source=../lib/common.sh
11
+ [ -f "$SCRIPT_DIR/../lib/common.sh" ] && source "$SCRIPT_DIR/../lib/common.sh"
12
+
13
+ require_cmd gh jq
14
+
15
+ ISSUE_NUM="${1:-}"
16
+ [ -n "$ISSUE_NUM" ] || error_exit "Usage: create-intent-from-issue.sh <issue-number>" 1
17
+ [[ "$ISSUE_NUM" =~ ^[0-9]+$ ]] || error_exit "Issue number must be numeric" 1
18
+
19
+ cd "$ROOT_DIR" || error_exit "Failed to cd to repo root" 1
20
+
21
+ # Fetch issue
22
+ JSON=$(gh issue view "$ISSUE_NUM" --json title,body,number 2>/dev/null) || error_exit "Failed to fetch issue #$ISSUE_NUM (gh issue view)" 1
23
+ TITLE=$(echo "$JSON" | jq -r '.title')
24
+ BODY=$(echo "$JSON" | jq -r '.body // ""')
25
+
26
+ # Next intent ID (feature F-XXX)
27
+ INTENT_BASE="work/intent"
28
+ INTENT_DIR="$INTENT_BASE/features"
29
+ mkdir -p "$INTENT_DIR"
30
+ LAST=0
31
+ while IFS= read -r f; do
32
+ [ -e "$f" ] || continue
33
+ base=$(basename "$f")
34
+ if [[ "$base" =~ ^F-([0-9]+)\.md$ ]]; then
35
+ num="${BASH_REMATCH[1]}"
36
+ ((10#$num > LAST)) && LAST=$((10#$num))
37
+ fi
38
+ done < <(find "$INTENT_BASE" -type f -name 'F-*.md' 2>/dev/null)
39
+ NEXT=$((LAST + 1))
40
+ INTENT_ID="F-$(printf "%03d" "$NEXT")"
41
+ INTENT_FILE="$INTENT_DIR/$INTENT_ID.md"
42
+
43
+ # Minimal intent content: title, status, motivation from issue, GitHub issue link
44
+ MOTIVATION=$(echo "$BODY" | head -15 | grep -v '^$' | sed 's/^/- /')
45
+ [ -z "$MOTIVATION" ] && MOTIVATION="- (From issue #$ISSUE_NUM)"
46
+
47
+ cat > "$INTENT_FILE" << EOF
48
+ # $INTENT_ID: $TITLE
49
+
50
+ ## Type
51
+
52
+ feature
53
+
54
+ ## Status
55
+
56
+ planned
57
+
58
+ ## Motivation
59
+
60
+ $MOTIVATION
61
+
62
+ ## Release Target
63
+
64
+ R1
65
+
66
+ ## Dependencies
67
+
68
+ -
69
+
70
+ ## GitHub issue
71
+
72
+ #$ISSUE_NUM
73
+
74
+ ## Do Not Repeat Check
75
+
76
+ - [ ] Checked _system/do-not-repeat/abandoned-designs.md
77
+ - [ ] Checked _system/do-not-repeat/failed-experiments.md
78
+ EOF
79
+
80
+ echo -e "${GREEN}Created $INTENT_FILE from issue #${ISSUE_NUM}${NC}"
81
+ echo "Intent ID: $INTENT_ID"
82
+ echo "Title: $TITLE"
@@ -0,0 +1,59 @@
1
+ #!/bin/bash
2
+ # Create a GitHub issue from an intent and write the issue number back to the intent file.
3
+ # Usage: create-issue-from-intent.sh <intent-id>
4
+ # Requires: gh (repo from gh repo view)
5
+
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
10
+ # shellcheck source=../lib/intent.sh
11
+ [ -f "$SCRIPT_DIR/../lib/intent.sh" ] && source "$SCRIPT_DIR/../lib/intent.sh"
12
+ # shellcheck source=../lib/common.sh
13
+ [ -f "$SCRIPT_DIR/../lib/common.sh" ] && source "$SCRIPT_DIR/../lib/common.sh"
14
+
15
+ require_cmd gh
16
+
17
+ INTENT_ID="${1:-}"
18
+ [ -n "$INTENT_ID" ] || error_exit "Usage: create-issue-from-intent.sh <intent-id>" 1
19
+
20
+ cd "$ROOT_DIR" || error_exit "Failed to cd to repo root" 1
21
+ INTENT_FILE="$(require_intent_file "$INTENT_ID")"
22
+
23
+ # Title: first line of intent (e.g. "F-001: Add feature")
24
+ TITLE=$(head -1 "$INTENT_FILE" | sed 's/^# *//')
25
+ BODY_FILE=$(mktemp)
26
+ trap 'rm -f "$BODY_FILE"' EXIT
27
+ cat "$INTENT_FILE" > "$BODY_FILE"
28
+
29
+ echo -e "${BLUE}Creating GitHub issue: $TITLE${NC}"
30
+ OUTPUT=$(gh issue create --title "$TITLE" --body-file "$BODY_FILE" 2>&1) || error_exit "gh issue create failed: $OUTPUT" 1
31
+ ISSUE_NUM=$(echo "$OUTPUT" | sed -n 's|.*/issues/\([0-9]*\)|\1|p')
32
+ [ -n "$ISSUE_NUM" ] || ISSUE_NUM=$(echo "$OUTPUT" | grep -oE '[0-9]+' | head -1)
33
+ [ -n "$ISSUE_NUM" ] || error_exit "Could not parse issue number from: $OUTPUT" 1
34
+
35
+ # Write #ISSUE_NUM into intent file (add or update ## GitHub issue section)
36
+ if grep -q '^## GitHub issue' "$INTENT_FILE"; then
37
+ # Replace value in existing section, or insert #num if section has only placeholder text
38
+ awk -v num="$ISSUE_NUM" '
39
+ /^## GitHub issue$/ { in_sec=1; printed=0; print; next }
40
+ in_sec && /^#?[0-9]+$/ { if (!printed) { print "#" num; printed=1 }; next }
41
+ in_sec && /^## / { in_sec=0; printed=0 }
42
+ in_sec && NF>0 && !printed { print "#" num; printed=1 }
43
+ { print }
44
+ ' "$INTENT_FILE" > "$INTENT_FILE.nw" && mv "$INTENT_FILE.nw" "$INTENT_FILE"
45
+ else
46
+ # Insert section before ## Do Not Repeat Check
47
+ awk -v num="$ISSUE_NUM" '
48
+ /^## Do Not Repeat Check/ {
49
+ print "## GitHub issue"
50
+ print ""
51
+ print "#" num
52
+ print ""
53
+ }
54
+ { print }
55
+ ' "$INTENT_FILE" > "$INTENT_FILE.nw" && mv "$INTENT_FILE.nw" "$INTENT_FILE"
56
+ fi
57
+
58
+ echo -e "${GREEN}Created issue #${ISSUE_NUM} and updated $INTENT_FILE${NC}"
59
+ echo "$OUTPUT"
@@ -0,0 +1,41 @@
1
+ #!/bin/bash
2
+ # Create a GitHub PR from work/workflow-state/pr.md for the given intent.
3
+ # Resolves pr.md per workflow-state-layout: work/workflow-state/<intent-id>/pr.md or work/workflow-state/pr.md.
4
+ # Usage: create-pr.sh <intent-id>
5
+ # Requires: gh. Run /pr first if pr.md is missing.
6
+
7
+ set -euo pipefail
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
11
+ # shellcheck source=../lib/intent.sh
12
+ [ -f "$SCRIPT_DIR/../lib/intent.sh" ] && source "$SCRIPT_DIR/../lib/intent.sh"
13
+ # shellcheck source=../lib/common.sh
14
+ [ -f "$SCRIPT_DIR/../lib/common.sh" ] && source "$SCRIPT_DIR/../lib/common.sh"
15
+
16
+ require_cmd gh
17
+
18
+ INTENT_ID="${1:-}"
19
+ [ -n "$INTENT_ID" ] || error_exit "Usage: create-pr.sh <intent-id>" 1
20
+
21
+ cd "$ROOT_DIR" || error_exit "Failed to cd to repo root" 1
22
+ require_intent_file "$INTENT_ID" >/dev/null
23
+
24
+ # Resolve pr.md per workflow-state-layout.md
25
+ PR_PER_INTENT="work/workflow-state/${INTENT_ID}/pr.md"
26
+ PR_FLAT="work/workflow-state/pr.md"
27
+ if [ -f "$PR_PER_INTENT" ]; then
28
+ PR_FILE="$PR_PER_INTENT"
29
+ elif [ -f "$PR_FLAT" ]; then
30
+ PR_FILE="$PR_FLAT"
31
+ else
32
+ error_exit "pr.md not found. Run /pr $INTENT_ID first to generate work/workflow-state/pr.md (or work/workflow-state/$INTENT_ID/pr.md)." 1
33
+ fi
34
+
35
+ # PR title from first line of pr.md (e.g. "# PR: F-001 - Title")
36
+ TITLE=$(head -1 "$PR_FILE" | sed 's/^# *PR: *//;s/^# *//')
37
+ [ -n "$TITLE" ] || TITLE="Intent $INTENT_ID"
38
+
39
+ echo -e "${BLUE}Creating PR: $TITLE${NC}"
40
+ gh pr create --title "$TITLE" --body-file "$PR_FILE" || error_exit "gh pr create failed" 1
41
+ echo -e "${GREEN}PR created from $PR_FILE${NC}"
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ # Link an existing GitHub issue to an intent (write issue number into intent file).
3
+ # Usage: link-issue.sh <intent-id> <issue-number>
4
+ # Requires: gh
5
+
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
10
+ # shellcheck source=../lib/intent.sh
11
+ [ -f "$SCRIPT_DIR/../lib/intent.sh" ] && source "$SCRIPT_DIR/../lib/intent.sh"
12
+ # shellcheck source=../lib/common.sh
13
+ [ -f "$SCRIPT_DIR/../lib/common.sh" ] && source "$SCRIPT_DIR/../lib/common.sh"
14
+
15
+ require_cmd gh
16
+
17
+ INTENT_ID="${1:-}"
18
+ ISSUE_NUM="${2:-}"
19
+ [ -n "$INTENT_ID" ] && [ -n "$ISSUE_NUM" ] || error_exit "Usage: link-issue.sh <intent-id> <issue-number>" 1
20
+ [[ "$ISSUE_NUM" =~ ^[0-9]+$ ]] || error_exit "Issue number must be numeric" 1
21
+
22
+ cd "$ROOT_DIR" || error_exit "Failed to cd to repo root" 1
23
+ INTENT_FILE="$(require_intent_file "$INTENT_ID")"
24
+
25
+ if grep -q '^## GitHub issue' "$INTENT_FILE"; then
26
+ awk -v num="$ISSUE_NUM" '
27
+ /^## GitHub issue$/ { in_sec=1; print; next }
28
+ in_sec && /^#?[0-9]+$/ { print "#" num; in_sec=0; next }
29
+ in_sec && /^## / { in_sec=0 }
30
+ { print }
31
+ ' "$INTENT_FILE" > "$INTENT_FILE.nw" && mv "$INTENT_FILE.nw" "$INTENT_FILE"
32
+ else
33
+ awk -v num="$ISSUE_NUM" '
34
+ /^## Do Not Repeat Check/ {
35
+ print "## GitHub issue"
36
+ print ""
37
+ print "#" num
38
+ print ""
39
+ }
40
+ { print }
41
+ ' "$INTENT_FILE" > "$INTENT_FILE.nw" && mv "$INTENT_FILE.nw" "$INTENT_FILE"
42
+ fi
43
+
44
+ echo -e "${GREEN}Linked issue #${ISSUE_NUM} to $INTENT_FILE${NC}"
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+ # When an intent is shipped, update or close the linked GitHub issue.
3
+ # Usage: on-ship-update-issue.sh <intent-id> [--close]
4
+ # Default: add a comment only. Use --close to close the issue.
5
+ # Set SHIP_CLOSE_ISSUE=1 to close (same as --close).
6
+ # Requires: gh
7
+
8
+ set -euo pipefail
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
12
+ # shellcheck source=../lib/intent.sh
13
+ [ -f "$SCRIPT_DIR/../lib/intent.sh" ] && source "$SCRIPT_DIR/../lib/intent.sh"
14
+ # shellcheck source=../lib/common.sh
15
+ [ -f "$SCRIPT_DIR/../lib/common.sh" ] && source "$SCRIPT_DIR/../lib/common.sh"
16
+
17
+ require_cmd gh
18
+
19
+ INTENT_ID="${1:-}"
20
+ [ -n "$INTENT_ID" ] || error_exit "Usage: on-ship-update-issue.sh <intent-id> [--close]" 1
21
+ CLOSE_ISSUE=false
22
+ [ "${2:-}" = "--close" ] && CLOSE_ISSUE=true
23
+ [ "${SHIP_CLOSE_ISSUE:-0}" = "1" ] && CLOSE_ISSUE=true
24
+
25
+ cd "$ROOT_DIR" || error_exit "Failed to cd to repo root" 1
26
+ INTENT_FILE="$(require_intent_file "$INTENT_ID")"
27
+
28
+ # Extract GitHub issue number from intent (line under ## GitHub issue that looks like #N or N)
29
+ ISSUE_NUM=$(awk '/^## GitHub issue$/,/^## /{if (/^#?[0-9]+$/) { gsub(/^#/,""); print; exit }}' "$INTENT_FILE" 2>/dev/null || true)
30
+
31
+ [ -n "$ISSUE_NUM" ] || { echo -e "${YELLOW}No GitHub issue linked to intent $INTENT_ID; skipping.${NC}"; exit 0; }
32
+
33
+ COMMENT="Shipped via intent $INTENT_ID."
34
+ if [ "$CLOSE_ISSUE" = true ]; then
35
+ echo -e "${BLUE}Closing issue #${ISSUE_NUM} with comment.${NC}"
36
+ gh issue close "$ISSUE_NUM" --comment "$COMMENT" || error_exit "gh issue close failed" 1
37
+ echo -e "${GREEN}Closed issue #${ISSUE_NUM}${NC}"
38
+ else
39
+ echo -e "${BLUE}Adding comment to issue #${ISSUE_NUM}.${NC}"
40
+ gh issue comment "$ISSUE_NUM" --body "$COMMENT" || error_exit "gh issue comment failed" 1
41
+ echo -e "${GREEN}Commented on issue #${ISSUE_NUM}${NC}"
42
+ fi
@@ -0,0 +1,8 @@
1
+ # Headless mode scripts
2
+
3
+ Run ShipIt phases from the CLI without Cursor. Uses the same `.cursor/commands` and `.cursor/rules` as Cursor; calls OpenAI or Anthropic API to generate phase output.
4
+
5
+ - **run-phase.sh** — Run one phase (1–5) for an intent. Requires `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`.
6
+ - **call-llm.js** — Helper that sends a prompt to the LLM and prints the response (used by run-phase.sh).
7
+
8
+ See [docs/headless-mode.md](../../docs/headless-mode.md) for full documentation, human gates, and how to approve.
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Call OpenAI or Anthropic API with a prompt; print the model response to stdout.
4
+ * Requires OPENAI_API_KEY or ANTHROPIC_API_KEY. Used by run-phase.sh.
5
+ * Usage: node scripts/headless/call-llm.js [--prompt "inline prompt"]
6
+ * Or: echo "prompt" | node scripts/headless/call-llm.js
7
+ */
8
+
9
+ import { readFileSync } from "node:fs";
10
+
11
+ const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
12
+ const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
13
+
14
+ function getStdin() {
15
+ return new Promise((resolve) => {
16
+ if (process.stdin.isTTY) resolve("");
17
+ else {
18
+ const chunks = [];
19
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
20
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
21
+ }
22
+ });
23
+ }
24
+
25
+ async function main() {
26
+ let prompt = "";
27
+ const arg = process.argv[2];
28
+ if (arg === "--prompt" && process.argv[3] != null) {
29
+ prompt = process.argv[3];
30
+ } else {
31
+ prompt = (await getStdin()).trim();
32
+ }
33
+ if (!prompt) {
34
+ console.error("Usage: echo 'prompt' | node call-llm.js OR node call-llm.js --prompt 'your prompt'");
35
+ process.exit(1);
36
+ }
37
+
38
+ if (OPENAI_API_KEY) {
39
+ const url = "https://api.openai.com/v1/chat/completions";
40
+ const body = {
41
+ model: process.env.OPENAI_MODEL || "gpt-4o-mini",
42
+ messages: [{ role: "user", content: prompt }],
43
+ max_tokens: 4096,
44
+ };
45
+ const res = await fetch(url, {
46
+ method: "POST",
47
+ headers: {
48
+ "Content-Type": "application/json",
49
+ Authorization: `Bearer ${OPENAI_API_KEY}`,
50
+ },
51
+ body: JSON.stringify(body),
52
+ });
53
+ if (!res.ok) {
54
+ const err = await res.text();
55
+ console.error("OpenAI API error:", res.status, err);
56
+ process.exit(1);
57
+ }
58
+ const data = await res.json();
59
+ const text = data.choices?.[0]?.message?.content ?? "";
60
+ if (!text) {
61
+ console.error("OpenAI returned no content:", JSON.stringify(data).slice(0, 200));
62
+ process.exit(1);
63
+ }
64
+ process.stdout.write(text);
65
+ return;
66
+ }
67
+
68
+ if (ANTHROPIC_API_KEY) {
69
+ const url = "https://api.anthropic.com/v1/messages";
70
+ const body = {
71
+ model: process.env.ANTHROPIC_MODEL || "claude-3-5-haiku-20241022",
72
+ max_tokens: 4096,
73
+ messages: [{ role: "user", content: prompt }],
74
+ };
75
+ const res = await fetch(url, {
76
+ method: "POST",
77
+ headers: {
78
+ "Content-Type": "application/json",
79
+ "x-api-key": ANTHROPIC_API_KEY,
80
+ "anthropic-version": "2023-06-01",
81
+ },
82
+ body: JSON.stringify(body),
83
+ });
84
+ if (!res.ok) {
85
+ const err = await res.text();
86
+ console.error("Anthropic API error:", res.status, err);
87
+ process.exit(1);
88
+ }
89
+ const data = await res.json();
90
+ const block = data.content?.find((c) => c.type === "text");
91
+ const text = block?.text ?? "";
92
+ if (!text) {
93
+ console.error("Anthropic returned no text:", JSON.stringify(data).slice(0, 200));
94
+ process.exit(1);
95
+ }
96
+ process.stdout.write(text);
97
+ return;
98
+ }
99
+
100
+ console.error(
101
+ "Set OPENAI_API_KEY or ANTHROPIC_API_KEY to run headless. Never commit API keys."
102
+ );
103
+ process.exit(1);
104
+ }
105
+
106
+ main().catch((e) => {
107
+ console.error(e);
108
+ process.exit(1);
109
+ });
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env bash
2
+ # Run one ShipIt phase headless (no Cursor). Reads same .cursor/ content, calls LLM, writes state.
3
+ # Usage: ./scripts/headless/run-phase.sh <intent-id> <phase-number>
4
+ # Phase: 1=Analysis, 2=Planning, 3=Implementation, 4=Verification, 5=Release.
5
+ # Requires OPENAI_API_KEY or ANTHROPIC_API_KEY. See docs/headless-mode.md.
6
+
7
+ set -euo pipefail
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
11
+ cd "$REPO_ROOT"
12
+
13
+ # Source libs (use absolute paths so we find them from repo root)
14
+ HELPER_DIR="$SCRIPT_DIR/../lib"
15
+ [ -f "$HELPER_DIR/common.sh" ] && source "$HELPER_DIR/common.sh"
16
+ [ -f "$HELPER_DIR/intent.sh" ] && source "$HELPER_DIR/intent.sh"
17
+ [ -f "$HELPER_DIR/workflow_state.sh" ] && source "$HELPER_DIR/workflow_state.sh"
18
+
19
+ INTENT_ID="${1:-}"
20
+ PHASE_NUM="${2:-}"
21
+ if [ -z "$INTENT_ID" ] || [ -z "$PHASE_NUM" ]; then
22
+ echo "Usage: $0 <intent-id> <phase-number>" >&2
23
+ echo " Phase: 1=Analysis, 2=Planning, 3=Implementation, 4=Verification, 5=Release" >&2
24
+ exit 1
25
+ fi
26
+
27
+ # Require intent file
28
+ require_intent_file "$INTENT_ID" >/dev/null || error_exit "Intent file not found for $INTENT_ID" 1
29
+
30
+ # Resolve workflow state dir (flat or per-intent)
31
+ WORKFLOW_DIR="$(get_workflow_state_dir "$INTENT_ID")"
32
+ [ -n "$WORKFLOW_DIR" ] || error_exit "No workflow state dir for $INTENT_ID. Run pnpm workflow-orchestrator $INTENT_ID first." 1
33
+ mkdir -p "$WORKFLOW_DIR"
34
+
35
+ # Phase config: output file, role rule name, phase name
36
+ case "$PHASE_NUM" in
37
+ 1) OUT_FILE="01_analysis.md"; ROLE_RULE="pm"; PHASE_NAME="Analysis (PM)" ;;
38
+ 2) OUT_FILE="02_plan.md"; ROLE_RULE="architect"; PHASE_NAME="Planning (Architect)" ;;
39
+ 3) OUT_FILE="03_implementation.md"; ROLE_RULE="implementer"; PHASE_NAME="Implementation (Implementer)" ;;
40
+ 4) OUT_FILE="04_verification.md"; ROLE_RULE="qa"; PHASE_NAME="Verification (QA)" ;;
41
+ 5) OUT_FILE="05_release_notes.md"; ROLE_RULE="docs"; PHASE_NAME="Release (Docs)" ;;
42
+ *) error_exit "Phase must be 1-5, got: $PHASE_NUM" 1 ;;
43
+ esac
44
+
45
+ # Check API key
46
+ if [ -z "${OPENAI_API_KEY:-}" ] && [ -z "${ANTHROPIC_API_KEY:-}" ]; then
47
+ echo "Set OPENAI_API_KEY or ANTHROPIC_API_KEY to run headless. Never commit API keys." >&2
48
+ echo "See docs/headless-mode.md for details." >&2
49
+ exit 1
50
+ fi
51
+
52
+ # Read role rule and ship command content (same source as Cursor)
53
+ ROLE_FILE=".cursor/rules/${ROLE_RULE}.mdc"
54
+ SHIP_FILE=".cursor/commands/ship.md"
55
+ [ -f "$ROLE_FILE" ] || error_exit "Role rule not found: $ROLE_FILE" 1
56
+ [ -f "$SHIP_FILE" ] || error_exit "Command file not found: $SHIP_FILE" 1
57
+
58
+ ROLE_CONTENT="$(cat "$ROLE_FILE")"
59
+ SHIP_CONTENT="$(cat "$SHIP_FILE")"
60
+
61
+ # Build prompt: single source of instructions
62
+ PROMPT="You are running ShipIt Phase $PHASE_NUM: $PHASE_NAME. Intent ID: $INTENT_ID.
63
+ Write the content for the output file: $OUT_FILE (path under work/workflow-state).
64
+ Do not include the filename or markdown fence in your response; output only the file body.
65
+
66
+ === Role and constraints (follow these) ===
67
+ $ROLE_CONTENT
68
+
69
+ === Workflow instructions (ship command) ===
70
+ $SHIP_CONTENT
71
+
72
+ === Task ===
73
+ Produce the complete content for $OUT_FILE for intent $INTENT_ID. Output only the file body, no preamble."
74
+
75
+ # Call LLM and write output
76
+ echo "Running Phase $PHASE_NUM ($PHASE_NAME) for $INTENT_ID..." >&2
77
+ node "$SCRIPT_DIR/call-llm.js" --prompt "$PROMPT" > "$WORKFLOW_DIR/$OUT_FILE" || exit 1
78
+ echo "Wrote $WORKFLOW_DIR/$OUT_FILE" >&2
79
+
80
+ # Human gates: after phase 2 (plan) and phase 5 (release)
81
+ if [ "$PHASE_NUM" = "2" ]; then
82
+ echo "" >&2
83
+ echo "*** Waiting for human approval ***" >&2
84
+ echo "Review $WORKFLOW_DIR/02_plan.md and add APPROVED (or check the approval box)." >&2
85
+ echo "Then run: $0 $INTENT_ID 3" >&2
86
+ exit 0
87
+ fi
88
+ if [ "$PHASE_NUM" = "5" ]; then
89
+ echo "" >&2
90
+ echo "*** Release phase complete. Steward/human approval may be required. ***" >&2
91
+ echo "Review $WORKFLOW_DIR/05_release_notes.md. Then run verify or ship as needed." >&2
92
+ exit 0
93
+ fi
94
+
95
+ # Next phase hint
96
+ NEXT=$((PHASE_NUM + 1))
97
+ if [ "$NEXT" -le 5 ]; then
98
+ echo "Next: $0 $INTENT_ID $NEXT" >&2
99
+ fi