@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,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
|