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