@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,125 @@
1
+ #!/bin/bash
2
+
3
+ # Kill Intent Script
4
+ # Marks an intent as killed and records rationale
5
+
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ # shellcheck source=lib/intent.sh
10
+ [ -f "$SCRIPT_DIR/lib/intent.sh" ] && source "$SCRIPT_DIR/lib/intent.sh"
11
+
12
+ INTENT_ID="${1:-}"
13
+ if [ -z "$INTENT_ID" ]; then
14
+ error_exit "Usage: ./scripts/kill-intent.sh <intent-id> [reason]" 1
15
+ fi
16
+
17
+ REASON="${2:-No reason provided}"
18
+ INTENT_FILE="$(require_intent_file "$INTENT_ID")"
19
+ DATE_UTC="$(date -u +"%Y-%m-%d")"
20
+
21
+ TEMP_FILE="$(mktemp)"
22
+
23
+ awk -v status_value="killed" '
24
+ BEGIN { in_status=0; replaced=0 }
25
+ /^## Status/ {
26
+ print;
27
+ in_status=1;
28
+ next;
29
+ }
30
+ in_status && /^## / {
31
+ if (!replaced) {
32
+ print status_value;
33
+ replaced=1;
34
+ }
35
+ in_status=0;
36
+ print;
37
+ next;
38
+ }
39
+ in_status {
40
+ if ($0 ~ /[^[:space:]]/ && !replaced) {
41
+ print status_value;
42
+ replaced=1;
43
+ }
44
+ next;
45
+ }
46
+ { print }
47
+ END {
48
+ if (in_status && !replaced) {
49
+ print status_value;
50
+ }
51
+ }
52
+ ' "$INTENT_FILE" > "$TEMP_FILE"
53
+
54
+ mv "$TEMP_FILE" "$INTENT_FILE"
55
+
56
+ # Handle Kill Rationale section - replace entire section if it exists, create if it doesn't
57
+ TEMP_FILE2="$(mktemp)"
58
+ awk -v reason="$REASON" -v date="$DATE_UTC" '
59
+ BEGIN { in_section=0; section_written=0 }
60
+ /^## Kill Rationale/ {
61
+ in_section=1;
62
+ section_written=1;
63
+ print "";
64
+ print "## Kill Rationale";
65
+ print "- Kill criterion: (unspecified)";
66
+ print "- Reason: " reason;
67
+ print "- Date: " date;
68
+ next;
69
+ }
70
+ in_section && /^## / {
71
+ # Hit next section, stop skipping
72
+ in_section=0;
73
+ print;
74
+ next;
75
+ }
76
+ in_section {
77
+ # Skip all lines in the Kill Rationale section (we already wrote the replacement)
78
+ next;
79
+ }
80
+ { print }
81
+ END {
82
+ if (!section_written) {
83
+ print "";
84
+ print "## Kill Rationale";
85
+ print "- Kill criterion: (unspecified)";
86
+ print "- Reason: " reason;
87
+ print "- Date: " date;
88
+ }
89
+ }
90
+ ' "$INTENT_FILE" > "$TEMP_FILE2"
91
+
92
+ mv "$TEMP_FILE2" "$INTENT_FILE"
93
+
94
+ # Remove intent from active list (flat active.md); support multi-intent layout
95
+ ACTIVE_FILE="work/workflow-state/active.md"
96
+ if [ -f "$ACTIVE_FILE" ]; then
97
+ ACTIVE_TEMP="$(mktemp)"
98
+ # 1) Drop lines in "## Active intents" that start with this intent_id
99
+ # 2) If **Intent ID:** is this intent, set to none and set Status/Phase to idle/none
100
+ awk -v id="$INTENT_ID" '
101
+ /^## Active intents/ { in_section=1; print; next }
102
+ in_section && /^[FBT]-[0-9]+[[:space:]]*\|/ {
103
+ if ($0 !~ "^" id "[[:space:]]*\\|") print
104
+ next
105
+ }
106
+ in_section { in_section=0 }
107
+ /^\*\*Intent ID:\*\* / {
108
+ if ($0 == "**Intent ID:** " id) { killed_single=1; print "**Intent ID:** none"; next }
109
+ print; next
110
+ }
111
+ killed_single && /^\*\*Status:\*\* / { print "**Status:** idle"; killed_single=0; next }
112
+ killed_single && /^\*\*Current Phase:\*\* / { print "**Current Phase:** none"; killed_single=0; next }
113
+ { print }
114
+ ' "$ACTIVE_FILE" > "$ACTIVE_TEMP"
115
+ # Legacy: if file had **Intent ID:** intent (no newline exact match), sed fallback
116
+ if grep -q "^\*\*Intent ID:\*\* $INTENT_ID" "$ACTIVE_FILE"; then
117
+ sed -e "s/^\*\*Intent ID:\*\* $INTENT_ID/**Intent ID:** none/" \
118
+ -e "s/^\*\*Status:\*\* .*/**Status:** idle/" \
119
+ -e "s/^\*\*Current Phase:\*\* .*/**Current Phase:** none/" \
120
+ "$ACTIVE_TEMP" > "${ACTIVE_TEMP}.2" && mv "${ACTIVE_TEMP}.2" "$ACTIVE_TEMP"
121
+ fi
122
+ mv "$ACTIVE_TEMP" "$ACTIVE_FILE"
123
+ fi
124
+
125
+ echo "✓ Marked $INTENT_ID as killed"
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+
3
+ # Common script plumbing: error handling, colors, optional require_* helpers.
4
+ # Source this from scripts that need consistent exit behavior and colors.
5
+ # For intent resolution, source intent.sh instead (it sources this).
6
+ # Caller script should already use set -euo pipefail.
7
+
8
+ error_exit() {
9
+ echo "ERROR: $1" >&2
10
+ exit "${2:-1}"
11
+ }
12
+
13
+ # Colors (safe to use in echo -e)
14
+ GREEN='\033[0;32m'
15
+ YELLOW='\033[1;33m'
16
+ BLUE='\033[0;34m'
17
+ RED='\033[0;31m'
18
+ CYAN='\033[0;36m'
19
+ NC='\033[0m'
20
+
21
+ # Optional: require a command to be present. Usage: require_cmd node jq git
22
+ require_cmd() {
23
+ local cmd
24
+ for cmd in "$@"; do
25
+ if ! command -v "$cmd" >/dev/null 2>&1; then
26
+ error_exit "$cmd is required but not installed" 1
27
+ fi
28
+ done
29
+ }
@@ -0,0 +1,61 @@
1
+ #!/bin/bash
2
+
3
+ # Intent domain helpers: resolve intent file path, require intent file.
4
+ # Sources common.sh. Use this in scripts that work with intent IDs (e.g. workflow-orchestrator, kill-intent).
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ # shellcheck source=common.sh
8
+ [ -f "$SCRIPT_DIR/common.sh" ] && source "$SCRIPT_DIR/common.sh"
9
+
10
+ INTENT_DIR="${INTENT_DIR:-work/intent}"
11
+
12
+ # Resolve an intent ID (e.g. F-042) or path to the actual intent file path.
13
+ # Outputs the path; returns 0 if found, 1 if not. Use with require_intent_file to exit on failure.
14
+ resolve_intent_file() {
15
+ local intent_id="$1"
16
+
17
+ if [ -f "$intent_id" ]; then
18
+ echo "$intent_id"
19
+ return 0
20
+ fi
21
+
22
+ local candidates=(
23
+ "work/intent/$intent_id.md"
24
+ "work/intent/features/$intent_id.md"
25
+ "work/intent/bugs/$intent_id.md"
26
+ "work/intent/tech-debt/$intent_id.md"
27
+ )
28
+
29
+ local candidate
30
+ for candidate in "${candidates[@]}"; do
31
+ if [ -f "$candidate" ]; then
32
+ echo "$candidate"
33
+ return 0
34
+ fi
35
+ done
36
+
37
+ local matches=()
38
+ shopt -s nullglob
39
+ matches=( work/intent/*/"$intent_id".md )
40
+ shopt -u nullglob
41
+
42
+ if [ "${#matches[@]}" -eq 1 ]; then
43
+ echo "${matches[0]}"
44
+ return 0
45
+ fi
46
+
47
+ if [ "${#matches[@]}" -gt 1 ]; then
48
+ error_exit "Multiple intent files found for $intent_id: ${matches[*]}" 1
49
+ fi
50
+
51
+ return 1
52
+ }
53
+
54
+ # Resolve intent and exit with error if not found. Sets INTENT_FILE in caller.
55
+ # Usage: INTENT_FILE="$(require_intent_file "$INTENT_ID")"
56
+ require_intent_file() {
57
+ local intent_id="$1"
58
+ local path
59
+ path="$(resolve_intent_file "$intent_id")" || error_exit "Intent file not found for id '$intent_id' (looked under work/intent/ and work/intent/*/)" 1
60
+ echo "$path"
61
+ }
@@ -0,0 +1,57 @@
1
+ #!/bin/bash
2
+
3
+ # Progress Indicator Library
4
+ # Provides functions to display progress for long-running operations
5
+
6
+ set -euo pipefail
7
+
8
+ # Colors
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ BLUE='\033[0;34m'
12
+ CYAN='\033[0;36m'
13
+ NC='\033[0m'
14
+
15
+ # Show phase progress
16
+ # Usage: show_phase_progress phase_num total_phases phase_name status
17
+ # Status: "running", "complete", "pending"
18
+ show_phase_progress() {
19
+ local phase_num="$1"
20
+ local total_phases="$2"
21
+ local phase_name="$3"
22
+ local status="${4:-running}"
23
+
24
+ case "$status" in
25
+ complete)
26
+ echo -e "[Phase $phase_num/$total_phases] $phase_name... ${GREEN}✓${NC}"
27
+ ;;
28
+ running)
29
+ echo -e "[Phase $phase_num/$total_phases] $phase_name... ${YELLOW}⏳${NC}"
30
+ ;;
31
+ pending)
32
+ echo -e "[Phase $phase_num/$total_phases] $phase_name... ${CYAN}○${NC}"
33
+ ;;
34
+ esac
35
+ }
36
+
37
+ # Show subtask progress
38
+ # Usage: show_subtask task_num total_tasks task_name
39
+ show_subtask_progress() {
40
+ local task_num="$1"
41
+ local total_tasks="$2"
42
+ local task_name="$3"
43
+
44
+ echo -e " ${BLUE}→${NC} [$task_num/$total_tasks] $task_name"
45
+ }
46
+
47
+ # Update progress in place (overwrite same line)
48
+ # Usage: update_progress_line "message"
49
+ update_progress_line() {
50
+ local message="$1"
51
+ echo -ne "\r\033[K$message"
52
+ }
53
+
54
+ # Complete progress line (move to next line)
55
+ complete_progress_line() {
56
+ echo ""
57
+ }
@@ -0,0 +1,131 @@
1
+ #!/bin/bash
2
+
3
+ # Next-Step Suggestion Library
4
+ # Provides context-aware suggestions for next commands based on project state
5
+
6
+ set -euo pipefail
7
+
8
+ # Colors
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ BLUE='\033[0;34m'
12
+ CYAN='\033[0;36m'
13
+ NC='\033[0m'
14
+
15
+ INTENT_DIR="${INTENT_DIR:-work/intent}"
16
+ WORKFLOW_DIR="${WORKFLOW_DIR:-workflow-state}"
17
+
18
+ # Analyze current project state
19
+ analyze_state() {
20
+ local state=""
21
+
22
+ # Count intents by status
23
+ local planned=0
24
+ local active=0
25
+ local shipped=0
26
+
27
+ if [ -d "$INTENT_DIR" ]; then
28
+ intent_files=()
29
+ while IFS= read -r file; do
30
+ intent_files+=("$file")
31
+ done < <(find "$INTENT_DIR" -type f -name "*.md" ! -name "_TEMPLATE.md" 2>/dev/null)
32
+
33
+ for file in "${intent_files[@]+"${intent_files[@]}"}"; do
34
+ [ -f "$file" ] || continue
35
+
36
+ local status=$(awk '
37
+ $0 ~ /^## Status/ {found=1; next}
38
+ found && $0 ~ /^## / {exit}
39
+ found && $0 ~ /[^[:space:]]/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0); print tolower($0); exit}
40
+ ' "$file" 2>/dev/null || echo "planned")
41
+
42
+ case "$status" in
43
+ planned) planned=$((planned + 1)) ;;
44
+ active) active=$((active + 1)) ;;
45
+ shipped) shipped=$((shipped + 1)) ;;
46
+ esac
47
+ done
48
+ fi
49
+
50
+ # Check for active workflow
51
+ local has_active_workflow=false
52
+ if [ -f "$WORKFLOW_DIR/active.md" ]; then
53
+ local active_status=$(grep -E "^\\*\\*Status:\\*\\*" "$WORKFLOW_DIR/active.md" 2>/dev/null | sed 's/.*\*\*Status:\*\* //' | tr -d ' ' || echo "")
54
+ if [ "$active_status" = "active" ]; then
55
+ has_active_workflow=true
56
+ fi
57
+ fi
58
+
59
+ # Check for release plan
60
+ local has_release_plan=false
61
+ if [ -f "work/release/plan.md" ]; then
62
+ has_release_plan=true
63
+ fi
64
+
65
+ echo "$planned|$active|$shipped|$has_active_workflow|$has_release_plan"
66
+ }
67
+
68
+ # Generate suggestions based on state
69
+ suggest_commands() {
70
+ local state="$1"
71
+ local planned=$(echo "$state" | cut -d'|' -f1)
72
+ local active=$(echo "$state" | cut -d'|' -f2)
73
+ local shipped=$(echo "$state" | cut -d'|' -f3)
74
+ local has_active_workflow=$(echo "$state" | cut -d'|' -f4)
75
+ local has_release_plan=$(echo "$state" | cut -d'|' -f5)
76
+
77
+ local suggestions=()
78
+
79
+ # Logic for suggestions
80
+ if [ "$planned" -eq 0 ] && [ "$active" -eq 0 ]; then
81
+ # No intents yet
82
+ suggestions+=("Run /scope-project to break down your project")
83
+ suggestions+=("Run /new_intent to create your first intent")
84
+ elif [ "$active" -gt 0 ]; then
85
+ # Active workflow
86
+ suggestions+=("Continue workflow for active intent")
87
+ suggestions+=("Run /status to see current phase")
88
+ elif [ "$planned" -gt 0 ] && [ "$has_release_plan" = "true" ]; then
89
+ # Planned intents with release plan
90
+ # Get first planned intent (more specific pattern to avoid false matches)
91
+ local first_intent=$(find "$INTENT_DIR" -name "*.md" ! -name "_TEMPLATE.md" -exec awk '
92
+ $0 ~ /^## Status/ {found=1; next}
93
+ found && $0 ~ /^## / {exit}
94
+ found && $0 ~ /^[[:space:]]*planned[[:space:]]*$/ {print FILENAME; exit}
95
+ ' {} \; 2>/dev/null | head -1)
96
+ if [ -n "$first_intent" ]; then
97
+ local intent_id=$(basename "$first_intent" .md)
98
+ suggestions+=("Run /ship $intent_id to start implementing")
99
+ fi
100
+ suggestions+=("Run /new_intent to create another intent")
101
+ suggestions+=("Edit intents or review release plan")
102
+ elif [ "$planned" -gt 0 ]; then
103
+ # Planned intents but no release plan
104
+ suggestions+=("Run /generate-release-plan to create release plan")
105
+ suggestions+=("Run /ship <intent-id> to start implementing")
106
+ elif [ "$has_release_plan" = "true" ]; then
107
+ # Release plan exists
108
+ suggestions+=("Run /ship <intent-id> to start implementing")
109
+ suggestions+=("Run /generate-roadmap to update roadmap")
110
+ else
111
+ # Default suggestions
112
+ suggestions+=("Run /scope-project to break down your project")
113
+ suggestions+=("Run /new_intent to create an intent")
114
+ fi
115
+
116
+ # Print suggestions
117
+ for suggestion in "${suggestions[@]}"; do
118
+ echo " 💡 $suggestion"
119
+ done
120
+ }
121
+
122
+ # Display suggestions after command completion
123
+ display_suggestions() {
124
+ local context="${1:-}"
125
+ local state=$(analyze_state)
126
+
127
+ echo ""
128
+ echo -e "${CYAN}Next steps:${NC}"
129
+ suggest_commands "$state"
130
+ echo ""
131
+ }
@@ -0,0 +1,240 @@
1
+ #!/bin/bash
2
+
3
+ # Intent Validation Library
4
+ # Provides functions to validate intent files for common issues
5
+
6
+ set -euo pipefail
7
+
8
+ # Colors
9
+ RED='\033[0;31m'
10
+ YELLOW='\033[1;33m'
11
+ GREEN='\033[0;32m'
12
+ NC='\033[0m'
13
+
14
+ INTENT_DIR="${INTENT_DIR:-work/intent}"
15
+
16
+ # Parse intent file and return structured data
17
+ parse_intent() {
18
+ local intent_file="$1"
19
+ local id=$(basename "$intent_file" .md)
20
+
21
+ # Extract release target
22
+ local release_target=$(awk '
23
+ /^## Release Target/ {
24
+ found=1
25
+ # Check inline format "## Release Target: R1"
26
+ if (match($0, /R[0-9]+/)) {
27
+ print substr($0, RSTART, RLENGTH)
28
+ exit
29
+ }
30
+ next
31
+ }
32
+ found && /^## / { exit }
33
+ found && /R[0-9]+/ {
34
+ # Skip template lines like "R1 | R2 | R3"
35
+ if (!/\|/) {
36
+ match($0, /R[0-9]+/)
37
+ if (RSTART > 0) {
38
+ print substr($0, RSTART, RLENGTH)
39
+ exit
40
+ }
41
+ }
42
+ }
43
+ ' "$intent_file" | head -1)
44
+
45
+ # Extract dependencies (normalize to uppercase, remove whitespace issues)
46
+ local deps=$(awk '
47
+ /^## Dependencies/ { found=1; next }
48
+ found && /^## / { exit }
49
+ found && /^- / {
50
+ line=$0
51
+ sub(/^[[:space:]]*-[[:space:]]*/, "", line)
52
+ gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
53
+ # Skip empty, "(none)", "None", or placeholder brackets
54
+ if (line == "" || line == "(none)" || tolower(line) ~ /^none/ || line ~ /^\[.*\]$/) next
55
+ # Extract intent ID (F-001, B-002, etc.)
56
+ if (match(line, /^[A-Z]-[0-9]+/)) {
57
+ print toupper(substr(line, RSTART, RLENGTH))
58
+ }
59
+ }
60
+ ' "$intent_file")
61
+
62
+ echo "$id|$release_target|$deps"
63
+ }
64
+
65
+ # Check dependency ordering conflicts
66
+ # Returns: issue_type|intent_id|message|fix_suggestion
67
+ check_dependency_ordering() {
68
+ local intent_file="$1"
69
+ local intent_data=$(parse_intent "$intent_file")
70
+ local intent_id=$(echo "$intent_data" | cut -d'|' -f1)
71
+ local release_target=$(echo "$intent_data" | cut -d'|' -f2)
72
+ local deps=$(echo "$intent_data" | cut -d'|' -f3- | tr ' ' '\n' | grep -v '^$')
73
+
74
+ if [ -z "$release_target" ] || [ -z "$deps" ]; then
75
+ return 0 # No issue if no release target or dependencies
76
+ fi
77
+
78
+ # Extract numeric release number, validate format
79
+ local release_num=$(echo "$release_target" | sed 's/R//')
80
+ if [ -z "$release_num" ] || ! [[ "$release_num" =~ ^[0-9]+$ ]]; then
81
+ return 0 # Invalid release format, skip check
82
+ fi
83
+
84
+ while IFS= read -r dep; do
85
+ [ -z "$dep" ] && continue
86
+
87
+ local dep_file=$(find "$INTENT_DIR" -type f -name "${dep}.md" -print -quit 2>/dev/null)
88
+ if [ -z "$dep_file" ] || [ ! -f "$dep_file" ]; then
89
+ continue # Missing deps handled separately
90
+ fi
91
+
92
+ local dep_data=$(parse_intent "$dep_file")
93
+ local dep_release=$(echo "$dep_data" | cut -d'|' -f2)
94
+
95
+ if [ -z "$dep_release" ]; then
96
+ continue # Dependency has no release target
97
+ fi
98
+
99
+ local dep_release_num=$(echo "$dep_release" | sed 's/R//')
100
+ if [ -z "$dep_release_num" ] || ! [[ "$dep_release_num" =~ ^[0-9]+$ ]]; then
101
+ continue # Invalid release format, skip
102
+ fi
103
+
104
+ # Issue: dependency is in later release than dependent
105
+ if [ "$dep_release_num" -gt "$release_num" ]; then
106
+ echo "dependency_ordering|$intent_id|$intent_id depends on $dep, but $dep is in $dep_release while $intent_id is in $release_target|Move $dep to $release_target or earlier, or move $intent_id to $dep_release or later"
107
+ return 1
108
+ fi
109
+ done <<< "$deps"
110
+
111
+ return 0
112
+ }
113
+
114
+ # Check whitespace formatting in dependencies
115
+ check_whitespace() {
116
+ local intent_file="$1"
117
+ local intent_data=$(parse_intent "$intent_file")
118
+ local intent_id=$(echo "$intent_data" | cut -d'|' -f1)
119
+
120
+ # Check if dependency lines have leading whitespace
121
+ if awk '
122
+ /^## Dependencies/ { found=1; next }
123
+ found && /^## / { exit }
124
+ found && /^[[:space:]]+- / {
125
+ # Line has leading whitespace before "- "
126
+ exit 1
127
+ }
128
+ ' "$intent_file"; then
129
+ return 0 # No whitespace issues
130
+ else
131
+ echo "whitespace|$intent_id|Dependency lines have leading whitespace|Normalize dependency lines to start at column 0"
132
+ return 1
133
+ fi
134
+ }
135
+
136
+ # Check for missing dependencies
137
+ check_missing_dependencies() {
138
+ local intent_file="$1"
139
+ local intent_data=$(parse_intent "$intent_file")
140
+ local intent_id=$(echo "$intent_data" | cut -d'|' -f1)
141
+ local deps=$(echo "$intent_data" | cut -d'|' -f3- | tr ' ' '\n' | grep -v '^$')
142
+
143
+ local missing=()
144
+ while IFS= read -r dep; do
145
+ [ -z "$dep" ] && continue
146
+
147
+ local dep_file=$(find "$INTENT_DIR" -type f -name "${dep}.md" -print -quit 2>/dev/null)
148
+ if [ -z "$dep_file" ] || [ ! -f "$dep_file" ]; then
149
+ missing+=("$dep")
150
+ fi
151
+ done <<< "$deps"
152
+
153
+ if [ ${#missing[@]} -gt 0 ]; then
154
+ local missing_list=$(IFS=','; echo "${missing[*]}")
155
+ echo "missing_dependency|$intent_id|Missing dependencies: $missing_list|Create intent files for missing dependencies or remove from dependency list"
156
+ return 1
157
+ fi
158
+
159
+ return 0
160
+ }
161
+
162
+ # Check for circular dependencies (simple check - direct cycles only)
163
+ check_circular_dependencies() {
164
+ local intent_file="$1"
165
+ local intent_data=$(parse_intent "$intent_file")
166
+ local intent_id=$(echo "$intent_data" | cut -d'|' -f1)
167
+ local deps=$(echo "$intent_data" | cut -d'|' -f3- | tr ' ' '\n' | grep -v '^$')
168
+
169
+ while IFS= read -r dep; do
170
+ [ -z "$dep" ] && continue
171
+
172
+ local dep_file=$(find "$INTENT_DIR" -type f -name "${dep}.md" -print -quit 2>/dev/null)
173
+ if [ -z "$dep_file" ] || [ ! -f "$dep_file" ]; then
174
+ continue
175
+ fi
176
+
177
+ local dep_data=$(parse_intent "$dep_file")
178
+ local dep_deps=$(echo "$dep_data" | cut -d'|' -f3- | tr ' ' '\n' | grep -v '^$')
179
+
180
+ # Check if dependency depends back on this intent
181
+ if echo "$dep_deps" | grep -q "^$intent_id$"; then
182
+ echo "circular|$intent_id|Circular dependency detected: $intent_id <-> $dep|Remove circular dependency from either $intent_id or $dep"
183
+ return 1
184
+ fi
185
+ done <<< "$deps"
186
+
187
+ return 0
188
+ }
189
+
190
+ # Run all validations on an intent file
191
+ # Returns: list of issues (one per line) in format: issue_type|intent_id|message|fix_suggestion
192
+ validate_intent() {
193
+ local intent_file="$1"
194
+ local issues=()
195
+
196
+ # Run all checks
197
+ local ordering_issue=$(check_dependency_ordering "$intent_file" || true)
198
+ local whitespace_issue=$(check_whitespace "$intent_file" || true)
199
+ local missing_issue=$(check_missing_dependencies "$intent_file" || true)
200
+ local circular_issue=$(check_circular_dependencies "$intent_file" || true)
201
+
202
+ # Collect all issues
203
+ [ -n "$ordering_issue" ] && issues+=("$ordering_issue")
204
+ [ -n "$whitespace_issue" ] && issues+=("$whitespace_issue")
205
+ [ -n "$missing_issue" ] && issues+=("$missing_issue")
206
+ [ -n "$circular_issue" ] && issues+=("$circular_issue")
207
+
208
+ # Print all issues (one per line)
209
+ for issue in "${issues[@]}"; do
210
+ echo "$issue"
211
+ done
212
+
213
+ return ${#issues[@]} # Return number of issues
214
+ }
215
+
216
+ # Validate all intents
217
+ validate_all_intents() {
218
+ local total_issues=0
219
+ local intent_files=()
220
+
221
+ # Find all intent files recursively
222
+ intent_files=()
223
+ while IFS= read -r file; do
224
+ intent_files+=("$file")
225
+ done < <(find "$INTENT_DIR" -type f -name "*.md" ! -name "_TEMPLATE.md" 2>/dev/null)
226
+
227
+ if [ ${#intent_files[@]} -eq 0 ]; then
228
+ return 0
229
+ fi
230
+
231
+ # Validate each intent
232
+ for intent_file in "${intent_files[@]}"; do
233
+ validate_intent "$intent_file" || {
234
+ local count=$?
235
+ total_issues=$((total_issues + count))
236
+ }
237
+ done
238
+
239
+ return $total_issues
240
+ }