@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,131 @@
|
|
|
1
|
+
# Command manifest: single source of truth for slash commands and pnpm scripts.
|
|
2
|
+
# help.sh builds the "Available commands" list from this file.
|
|
3
|
+
# Add a command by adding an entry; keep id, slash, pnpm, description, category.
|
|
4
|
+
|
|
5
|
+
commands:
|
|
6
|
+
# Project Management
|
|
7
|
+
- id: init-project
|
|
8
|
+
slash: /init-project
|
|
9
|
+
pnpm: init-project
|
|
10
|
+
description: Initialize a new ShipIt project
|
|
11
|
+
category: Project Management
|
|
12
|
+
- id: scope-project
|
|
13
|
+
slash: /scope-project
|
|
14
|
+
pnpm: scope-project
|
|
15
|
+
description: AI-assisted feature breakdown
|
|
16
|
+
category: Project Management
|
|
17
|
+
|
|
18
|
+
# Intent Management
|
|
19
|
+
- id: new-intent
|
|
20
|
+
slash: /new_intent
|
|
21
|
+
pnpm: new-intent
|
|
22
|
+
description: Create a new intent (interactive wizard)
|
|
23
|
+
category: Intent Management
|
|
24
|
+
- id: kill
|
|
25
|
+
slash: /kill
|
|
26
|
+
pnpm: kill-intent
|
|
27
|
+
description: Kill an intent with rationale
|
|
28
|
+
category: Intent Management
|
|
29
|
+
|
|
30
|
+
# Workflow
|
|
31
|
+
- id: ship
|
|
32
|
+
slash: /ship
|
|
33
|
+
pnpm: workflow-orchestrator
|
|
34
|
+
description: Run full SDLC workflow (6 phases)
|
|
35
|
+
category: Workflow
|
|
36
|
+
- id: verify
|
|
37
|
+
slash: /verify
|
|
38
|
+
pnpm: verify
|
|
39
|
+
description: Re-run verification phase
|
|
40
|
+
category: Workflow
|
|
41
|
+
|
|
42
|
+
# Planning
|
|
43
|
+
- id: generate-release-plan
|
|
44
|
+
slash: /generate-release-plan
|
|
45
|
+
pnpm: generate-release-plan
|
|
46
|
+
description: Build release plan from intents
|
|
47
|
+
category: Planning
|
|
48
|
+
- id: generate-roadmap
|
|
49
|
+
slash: /generate-roadmap
|
|
50
|
+
pnpm: generate-roadmap
|
|
51
|
+
description: Generate roadmap (now/next/later)
|
|
52
|
+
category: Planning
|
|
53
|
+
|
|
54
|
+
# Operations
|
|
55
|
+
- id: deploy
|
|
56
|
+
slash: /deploy
|
|
57
|
+
pnpm: deploy
|
|
58
|
+
description: Deploy with readiness checks
|
|
59
|
+
category: Operations
|
|
60
|
+
- id: drift-check
|
|
61
|
+
slash: /drift_check
|
|
62
|
+
pnpm: drift-check
|
|
63
|
+
description: Check for entropy/decay
|
|
64
|
+
category: Operations
|
|
65
|
+
- id: risk
|
|
66
|
+
slash: /risk
|
|
67
|
+
pnpm: null
|
|
68
|
+
description: Force security/threat skim
|
|
69
|
+
category: Operations
|
|
70
|
+
|
|
71
|
+
# Help & Status
|
|
72
|
+
- id: help
|
|
73
|
+
slash: /help
|
|
74
|
+
pnpm: help
|
|
75
|
+
description: Show help for a command
|
|
76
|
+
category: Help & Status
|
|
77
|
+
- id: status
|
|
78
|
+
slash: /status
|
|
79
|
+
pnpm: status
|
|
80
|
+
description: Show current project status
|
|
81
|
+
category: Help & Status
|
|
82
|
+
- id: suggest
|
|
83
|
+
slash: /suggest
|
|
84
|
+
pnpm: suggest
|
|
85
|
+
description: Get suggested next actions
|
|
86
|
+
category: Help & Status
|
|
87
|
+
- id: usage-record
|
|
88
|
+
slash: /usage-record
|
|
89
|
+
pnpm: usage-record
|
|
90
|
+
description: Record token/cost for a phase (intent_id phase tokens_in tokens_out [cost])
|
|
91
|
+
category: Help & Status
|
|
92
|
+
- id: usage-report
|
|
93
|
+
slash: /usage-report
|
|
94
|
+
pnpm: usage-report
|
|
95
|
+
description: Show token/cost usage table (optionally --last N)
|
|
96
|
+
category: Help & Status
|
|
97
|
+
- id: calibration-report
|
|
98
|
+
slash: /calibration-report
|
|
99
|
+
pnpm: calibration-report
|
|
100
|
+
description: Confidence calibration report (stated vs actual); --json for dashboard
|
|
101
|
+
category: Help & Status
|
|
102
|
+
- id: pr
|
|
103
|
+
slash: /pr
|
|
104
|
+
pnpm: null
|
|
105
|
+
description: Generate PR summary/checklist
|
|
106
|
+
category: Help & Status
|
|
107
|
+
- id: create-pr
|
|
108
|
+
slash: /create-pr
|
|
109
|
+
pnpm: gh-create-pr
|
|
110
|
+
description: Generate pr.md then create GitHub PR for intent
|
|
111
|
+
category: Help & Status
|
|
112
|
+
- id: create-intent-from-issue
|
|
113
|
+
slash: /create-intent-from-issue
|
|
114
|
+
pnpm: create-intent-from-issue
|
|
115
|
+
description: Create intent from GitHub issue
|
|
116
|
+
category: Intent Management
|
|
117
|
+
- id: revert-plan
|
|
118
|
+
slash: /revert-plan
|
|
119
|
+
pnpm: null
|
|
120
|
+
description: Write rollback plan
|
|
121
|
+
category: Help & Status
|
|
122
|
+
- id: rollback
|
|
123
|
+
slash: /rollback
|
|
124
|
+
pnpm: execute-rollback
|
|
125
|
+
description: Execute rollback in guided mode (reads plan from /revert-plan)
|
|
126
|
+
category: Workflow
|
|
127
|
+
- id: dashboard
|
|
128
|
+
slash: /dashboard
|
|
129
|
+
pnpm: dashboard
|
|
130
|
+
description: Start web dashboard (exports data first, then launches UI)
|
|
131
|
+
category: Help & Status
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'EOF'
|
|
6
|
+
Usage:
|
|
7
|
+
./scripts/create-test-plan-issue.sh \
|
|
8
|
+
--title "..." \
|
|
9
|
+
--severity low|medium|high \
|
|
10
|
+
--step "4-1" \
|
|
11
|
+
--expected "..." \
|
|
12
|
+
--actual "..." \
|
|
13
|
+
--error "..." \
|
|
14
|
+
--impl "Action item 1" \
|
|
15
|
+
[--impl "Action item 2"] \
|
|
16
|
+
[--repo owner/name]
|
|
17
|
+
|
|
18
|
+
Notes:
|
|
19
|
+
- If --repo is omitted, the script will try to resolve it via:
|
|
20
|
+
gh repo view --json nameWithOwner -q .nameWithOwner
|
|
21
|
+
- This script intentionally avoids literal '\n' sequences in the issue body.
|
|
22
|
+
EOF
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
TITLE=""
|
|
26
|
+
SEVERITY=""
|
|
27
|
+
STEP_ID=""
|
|
28
|
+
EXPECTED=""
|
|
29
|
+
ACTUAL=""
|
|
30
|
+
ERROR_TEXT=""
|
|
31
|
+
REPO=""
|
|
32
|
+
IMPL_ITEMS=()
|
|
33
|
+
|
|
34
|
+
while [ $# -gt 0 ]; do
|
|
35
|
+
case "$1" in
|
|
36
|
+
--title) TITLE="${2:-}"; shift 2 ;;
|
|
37
|
+
--severity) SEVERITY="${2:-}"; shift 2 ;;
|
|
38
|
+
--step) STEP_ID="${2:-}"; shift 2 ;;
|
|
39
|
+
--expected) EXPECTED="${2:-}"; shift 2 ;;
|
|
40
|
+
--actual) ACTUAL="${2:-}"; shift 2 ;;
|
|
41
|
+
--error) ERROR_TEXT="${2:-}"; shift 2 ;;
|
|
42
|
+
--impl) IMPL_ITEMS+=("${2:-}"); shift 2 ;;
|
|
43
|
+
--repo) REPO="${2:-}"; shift 2 ;;
|
|
44
|
+
-h|--help) usage; exit 0 ;;
|
|
45
|
+
*) echo "Unknown arg: $1" >&2; usage; exit 2 ;;
|
|
46
|
+
esac
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
if [ -z "$TITLE" ] || [ -z "$SEVERITY" ] || [ -z "$STEP_ID" ] || [ -z "$EXPECTED" ] || [ -z "$ACTUAL" ] || [ -z "$ERROR_TEXT" ]; then
|
|
50
|
+
echo "Missing required arguments." >&2
|
|
51
|
+
usage
|
|
52
|
+
exit 2
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
case "$SEVERITY" in
|
|
56
|
+
low|medium|high) ;;
|
|
57
|
+
*) echo "Invalid --severity: $SEVERITY (expected low|medium|high)" >&2; exit 2 ;;
|
|
58
|
+
esac
|
|
59
|
+
|
|
60
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
61
|
+
echo "gh is not installed (required)." >&2
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
gh auth status >/dev/null
|
|
66
|
+
|
|
67
|
+
if [ -z "$REPO" ]; then
|
|
68
|
+
REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || true)"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
if [ -z "$REPO" ]; then
|
|
72
|
+
echo "Could not resolve GitHub repo. Provide --repo owner/name." >&2
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
FIRST_SEEN="$(date +%F)"
|
|
77
|
+
|
|
78
|
+
BODY_FILE="$(mktemp)"
|
|
79
|
+
{
|
|
80
|
+
echo "**Severity:** $SEVERITY"
|
|
81
|
+
echo "**Step:** $STEP_ID"
|
|
82
|
+
echo "**First Seen:** $FIRST_SEEN"
|
|
83
|
+
echo
|
|
84
|
+
echo "## Expected"
|
|
85
|
+
echo
|
|
86
|
+
echo "$EXPECTED"
|
|
87
|
+
echo
|
|
88
|
+
echo "## Actual"
|
|
89
|
+
echo
|
|
90
|
+
echo "$ACTUAL"
|
|
91
|
+
echo
|
|
92
|
+
echo "## Error"
|
|
93
|
+
echo
|
|
94
|
+
echo "$ERROR_TEXT"
|
|
95
|
+
echo
|
|
96
|
+
echo "## Implementation"
|
|
97
|
+
echo
|
|
98
|
+
if [ "${#IMPL_ITEMS[@]}" -eq 0 ]; then
|
|
99
|
+
echo "- (none provided)"
|
|
100
|
+
else
|
|
101
|
+
for item in "${IMPL_ITEMS[@]}"; do
|
|
102
|
+
echo "- $item"
|
|
103
|
+
done
|
|
104
|
+
fi
|
|
105
|
+
} >"$BODY_FILE"
|
|
106
|
+
|
|
107
|
+
gh issue create --repo "$REPO" --title "$TITLE" --body-file "$BODY_FILE"
|
|
108
|
+
|
|
109
|
+
rm -f "$BODY_FILE"
|
|
110
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Start the ShipIt dashboard: export data first, then launch the dev server.
|
|
3
|
+
# Ensures fresh data whenever the dashboard is opened.
|
|
4
|
+
# Usage: ./scripts/dashboard-start.sh or pnpm dashboard
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
10
|
+
cd "$REPO_ROOT"
|
|
11
|
+
|
|
12
|
+
echo "Exporting dashboard data..."
|
|
13
|
+
node scripts/export-dashboard-json.js
|
|
14
|
+
|
|
15
|
+
echo "Starting dashboard dev server..."
|
|
16
|
+
cd dashboard-app && pnpm dev
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Deployment Script
|
|
4
|
+
# Handles deployment to various platforms
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
error_exit() {
|
|
9
|
+
echo "ERROR: $1" >&2
|
|
10
|
+
exit "${2:-1}"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
warning() {
|
|
14
|
+
echo "WARNING: $1" >&2
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Colors
|
|
18
|
+
RED='\033[0;31m'
|
|
19
|
+
GREEN='\033[0;32m'
|
|
20
|
+
YELLOW='\033[1;33m'
|
|
21
|
+
BLUE='\033[0;34m'
|
|
22
|
+
NC='\033[0m'
|
|
23
|
+
|
|
24
|
+
ENVIRONMENT="${1:-production}"
|
|
25
|
+
PLATFORM="${2:-}"
|
|
26
|
+
|
|
27
|
+
echo -e "${BLUE}Deploying to $ENVIRONMENT...${NC}"
|
|
28
|
+
echo ""
|
|
29
|
+
|
|
30
|
+
# Check prerequisites
|
|
31
|
+
if [ ! -f "project.json" ]; then
|
|
32
|
+
error_exit "project.json not found. Run /init-project first." 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Run readiness checks
|
|
36
|
+
echo -e "${YELLOW}Running readiness checks...${NC}"
|
|
37
|
+
if [ -f "scripts/check-readiness.sh" ]; then
|
|
38
|
+
if ! ./scripts/check-readiness.sh "$ENVIRONMENT"; then
|
|
39
|
+
error_exit "Readiness checks failed. Fix issues before deploying." 1
|
|
40
|
+
fi
|
|
41
|
+
else
|
|
42
|
+
warning "check-readiness.sh not found, skipping checks"
|
|
43
|
+
fi
|
|
44
|
+
echo ""
|
|
45
|
+
|
|
46
|
+
# Determine platform
|
|
47
|
+
if [ -z "$PLATFORM" ]; then
|
|
48
|
+
if [ -f "vercel.json" ] || [ -f ".vercel/project.json" ]; then
|
|
49
|
+
PLATFORM="vercel"
|
|
50
|
+
elif [ -f "netlify.toml" ]; then
|
|
51
|
+
PLATFORM="netlify"
|
|
52
|
+
elif [ -f "Dockerfile" ]; then
|
|
53
|
+
PLATFORM="docker"
|
|
54
|
+
elif [ -d "infrastructure" ] && [ -f "infrastructure/cdk.json" ]; then
|
|
55
|
+
PLATFORM="aws-cdk"
|
|
56
|
+
else
|
|
57
|
+
echo -e "${YELLOW}Platform not detected. Available options:${NC}"
|
|
58
|
+
echo "1) Vercel"
|
|
59
|
+
echo "2) Netlify"
|
|
60
|
+
echo "3) Docker"
|
|
61
|
+
echo "4) AWS CDK"
|
|
62
|
+
echo "5) Manual (no automation)"
|
|
63
|
+
read -p "Select platform [1-5]: " platform_choice
|
|
64
|
+
|
|
65
|
+
case $platform_choice in
|
|
66
|
+
1) PLATFORM="vercel" ;;
|
|
67
|
+
2) PLATFORM="netlify" ;;
|
|
68
|
+
3) PLATFORM="docker" ;;
|
|
69
|
+
4) PLATFORM="aws-cdk" ;;
|
|
70
|
+
5) PLATFORM="manual" ;;
|
|
71
|
+
*) PLATFORM="manual" ;;
|
|
72
|
+
esac
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
echo -e "${GREEN}Platform: $PLATFORM${NC}"
|
|
77
|
+
echo ""
|
|
78
|
+
|
|
79
|
+
# Create deployment history
|
|
80
|
+
DEPLOY_HISTORY="deployment-history.md"
|
|
81
|
+
if [ ! -f "$DEPLOY_HISTORY" ]; then
|
|
82
|
+
cat > "$DEPLOY_HISTORY" << EOF
|
|
83
|
+
# Deployment History
|
|
84
|
+
|
|
85
|
+
## Deployments
|
|
86
|
+
|
|
87
|
+
EOF
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Deploy based on platform
|
|
91
|
+
case $PLATFORM in
|
|
92
|
+
vercel)
|
|
93
|
+
echo -e "${YELLOW}Deploying to Vercel...${NC}"
|
|
94
|
+
if command -v vercel >/dev/null 2>&1; then
|
|
95
|
+
vercel --prod
|
|
96
|
+
else
|
|
97
|
+
error_exit "Vercel CLI not found. Install with: npm i -g vercel" 1
|
|
98
|
+
fi
|
|
99
|
+
;;
|
|
100
|
+
|
|
101
|
+
netlify)
|
|
102
|
+
echo -e "${YELLOW}Deploying to Netlify...${NC}"
|
|
103
|
+
if command -v netlify >/dev/null 2>&1; then
|
|
104
|
+
netlify deploy --prod
|
|
105
|
+
else
|
|
106
|
+
error_exit "Netlify CLI not found. Install with: npm i -g netlify-cli" 1
|
|
107
|
+
fi
|
|
108
|
+
;;
|
|
109
|
+
|
|
110
|
+
docker)
|
|
111
|
+
echo -e "${YELLOW}Building and deploying Docker image...${NC}"
|
|
112
|
+
if [ ! -f "Dockerfile" ]; then
|
|
113
|
+
error_exit "Dockerfile not found" 1
|
|
114
|
+
fi
|
|
115
|
+
IMAGE_NAME=$(jq -r '.name' project.json | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
|
|
116
|
+
docker build -t "$IMAGE_NAME:latest" .
|
|
117
|
+
echo -e "${GREEN}✓ Docker image built: $IMAGE_NAME:latest${NC}"
|
|
118
|
+
echo -e "${YELLOW}Note: Push and deploy manually or configure registry${NC}"
|
|
119
|
+
;;
|
|
120
|
+
|
|
121
|
+
aws-cdk)
|
|
122
|
+
echo -e "${YELLOW}Deploying with AWS CDK...${NC}"
|
|
123
|
+
if [ ! -d "infrastructure" ]; then
|
|
124
|
+
error_exit "infrastructure directory not found" 1
|
|
125
|
+
fi
|
|
126
|
+
cd infrastructure || error_exit "Failed to enter infrastructure directory" 1
|
|
127
|
+
if command -v cdk >/dev/null 2>&1; then
|
|
128
|
+
cdk deploy --require-approval never
|
|
129
|
+
else
|
|
130
|
+
error_exit "AWS CDK CLI not found. Install with: npm i -g aws-cdk" 1
|
|
131
|
+
fi
|
|
132
|
+
cd ..
|
|
133
|
+
;;
|
|
134
|
+
|
|
135
|
+
manual)
|
|
136
|
+
echo -e "${YELLOW}Manual deployment mode${NC}"
|
|
137
|
+
echo "Readiness checks passed. Deploy manually using your preferred method."
|
|
138
|
+
echo ""
|
|
139
|
+
echo "Suggested steps:"
|
|
140
|
+
echo "1. Build: pnpm build (or equivalent)"
|
|
141
|
+
echo "2. Test: pnpm test"
|
|
142
|
+
echo "3. Deploy using your platform's method"
|
|
143
|
+
;;
|
|
144
|
+
|
|
145
|
+
*)
|
|
146
|
+
error_exit "Unknown platform: $PLATFORM" 1
|
|
147
|
+
;;
|
|
148
|
+
esac
|
|
149
|
+
|
|
150
|
+
# Record deployment
|
|
151
|
+
DEPLOY_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
152
|
+
cat >> "$DEPLOY_HISTORY" << EOF
|
|
153
|
+
### $DEPLOY_TIME - $ENVIRONMENT ($PLATFORM)
|
|
154
|
+
- Environment: $ENVIRONMENT
|
|
155
|
+
- Platform: $PLATFORM
|
|
156
|
+
- Status: deployed
|
|
157
|
+
- Readiness checks: passed
|
|
158
|
+
|
|
159
|
+
EOF
|
|
160
|
+
|
|
161
|
+
echo ""
|
|
162
|
+
echo -e "${GREEN}════════════════════════════════════════${NC}"
|
|
163
|
+
echo -e "${GREEN}✓ Deployment complete${NC}"
|
|
164
|
+
echo -e "${GREEN}════════════════════════════════════════${NC}"
|
|
165
|
+
echo ""
|
|
166
|
+
echo -e "${YELLOW}Next steps:${NC}"
|
|
167
|
+
echo "1. Verify deployment is healthy"
|
|
168
|
+
echo "2. Run smoke tests"
|
|
169
|
+
echo "3. Monitor logs and metrics"
|
|
170
|
+
echo ""
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Drift Metrics Check Script
|
|
4
|
+
# Calculates entropy indicators to detect system drift
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# Error handling
|
|
9
|
+
error_exit() {
|
|
10
|
+
echo "ERROR: $1" >&2
|
|
11
|
+
exit "${2:-1}"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# Validate prerequisites
|
|
15
|
+
command -v git >/dev/null 2>&1 || error_exit "git is required but not installed"
|
|
16
|
+
command -v jq >/dev/null 2>&1 || error_exit "jq is required but not installed"
|
|
17
|
+
command -v bc >/dev/null 2>&1 || error_exit "bc is required but not installed"
|
|
18
|
+
|
|
19
|
+
DRIFT_DIR="_system/drift"
|
|
20
|
+
METRICS_FILE="$DRIFT_DIR/metrics.md"
|
|
21
|
+
|
|
22
|
+
# Create drift directory if it doesn't exist
|
|
23
|
+
mkdir -p "$DRIFT_DIR" || error_exit "Failed to create drift directory"
|
|
24
|
+
|
|
25
|
+
# Start metrics report
|
|
26
|
+
cat > "$METRICS_FILE" << EOF
|
|
27
|
+
# Drift Metrics Report
|
|
28
|
+
|
|
29
|
+
Generated: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
30
|
+
|
|
31
|
+
EOF
|
|
32
|
+
|
|
33
|
+
# 1. Average PR size (last 20 PRs)
|
|
34
|
+
echo "## PR Size Trend" >> "$METRICS_FILE" || error_exit "Failed to write to metrics file"
|
|
35
|
+
if git rev-parse --git-dir > /dev/null 2>&1; then
|
|
36
|
+
PR_SIZE=$(git log --oneline -20 --stat 2>/dev/null | grep "files changed" | \
|
|
37
|
+
awk '{sum+=$1; count++} END {if (count > 0) print "Avg files changed: " sum/count; else print "No PR data available"}' || echo "No PR data available")
|
|
38
|
+
echo "$PR_SIZE" >> "$METRICS_FILE" || error_exit "Failed to write PR size data"
|
|
39
|
+
else
|
|
40
|
+
echo "Not a git repository" >> "$METRICS_FILE" || error_exit "Failed to write git status"
|
|
41
|
+
fi
|
|
42
|
+
echo "" >> "$METRICS_FILE" || error_exit "Failed to write newline"
|
|
43
|
+
|
|
44
|
+
# 2. Test-to-code ratio (git-tracked only so node_modules/dist/build/reports are excluded)
|
|
45
|
+
echo "## Test-to-Code Ratio" >> "$METRICS_FILE"
|
|
46
|
+
if git rev-parse --git-dir > /dev/null 2>&1; then
|
|
47
|
+
TEST_FILES=$(git ls-files -- '*.test.ts' '*.spec.ts' 2>/dev/null | tr '\n' ' ')
|
|
48
|
+
CODE_FILES=$(git ls-files -- '*.ts' 2>/dev/null | grep -v -E '\.(test|spec)\.ts$' | tr '\n' ' ')
|
|
49
|
+
if [ -z "$TEST_FILES" ]; then TEST_LINES=0; else TEST_LINES=$(echo "$TEST_FILES" | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}' || echo "0"); fi
|
|
50
|
+
if [ -z "$CODE_FILES" ]; then CODE_LINES=0; else CODE_LINES=$(echo "$CODE_FILES" | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}' || echo "0"); fi
|
|
51
|
+
else
|
|
52
|
+
# Fallback: find with exclusions (no git or not a repo)
|
|
53
|
+
TEST_LINES=$(find . -type d \( -name node_modules -o -name dist -o -name build -o -name reports \) -prune -o -type f \( -name "*.test.ts" -o -name "*.spec.ts" \) -print 2>/dev/null | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}' || echo "0")
|
|
54
|
+
CODE_LINES=$(find . -type d \( -name node_modules -o -name dist -o -name build -o -name reports \) -prune -o -type f -name "*.ts" ! -name "*.test.ts" ! -name "*.spec.ts" -print 2>/dev/null | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}' || echo "0")
|
|
55
|
+
fi
|
|
56
|
+
if [ "${CODE_LINES:-0}" -gt 0 ]; then
|
|
57
|
+
RATIO=$(echo "scale=2; $TEST_LINES / $CODE_LINES" | bc 2>/dev/null || echo "N/A")
|
|
58
|
+
echo "Test lines: $TEST_LINES" >> "$METRICS_FILE"
|
|
59
|
+
echo "Code lines: $CODE_LINES" >> "$METRICS_FILE"
|
|
60
|
+
echo "Ratio: $RATIO" >> "$METRICS_FILE"
|
|
61
|
+
else
|
|
62
|
+
echo "No code found" >> "$METRICS_FILE"
|
|
63
|
+
fi
|
|
64
|
+
echo "" >> "$METRICS_FILE"
|
|
65
|
+
|
|
66
|
+
# 3. Dependency count
|
|
67
|
+
echo "## Dependency Growth" >> "$METRICS_FILE" || error_exit "Failed to write dependency section"
|
|
68
|
+
if [ -f "package.json" ]; then
|
|
69
|
+
DEPS=$(jq '.dependencies | length' package.json 2>/dev/null || echo "N/A")
|
|
70
|
+
DEV_DEPS=$(jq '.devDependencies | length' package.json 2>/dev/null || echo "N/A")
|
|
71
|
+
echo "Dependencies: $DEPS" >> "$METRICS_FILE" || error_exit "Failed to write dependencies"
|
|
72
|
+
echo "DevDependencies: $DEV_DEPS" >> "$METRICS_FILE" || error_exit "Failed to write devDependencies"
|
|
73
|
+
else
|
|
74
|
+
echo "No package.json found" >> "$METRICS_FILE" || error_exit "Failed to write package.json status"
|
|
75
|
+
fi
|
|
76
|
+
echo "" >> "$METRICS_FILE" || error_exit "Failed to write newline"
|
|
77
|
+
|
|
78
|
+
# 4. New files without intents
|
|
79
|
+
echo "## Untracked New Concepts" >> "$METRICS_FILE"
|
|
80
|
+
if git rev-parse --git-dir > /dev/null 2>&1; then
|
|
81
|
+
NEW_FILES=$(git diff --name-only HEAD~20 --diff-filter=A 2>/dev/null | grep -v "work/intent/" | head -10 || echo "No new files")
|
|
82
|
+
echo "$NEW_FILES" >> "$METRICS_FILE"
|
|
83
|
+
else
|
|
84
|
+
echo "Not a git repository" >> "$METRICS_FILE"
|
|
85
|
+
fi
|
|
86
|
+
echo "" >> "$METRICS_FILE"
|
|
87
|
+
|
|
88
|
+
# 5. CI Performance (placeholder)
|
|
89
|
+
echo "## CI Performance" >> "$METRICS_FILE"
|
|
90
|
+
echo "(Manually update from CI dashboard)" >> "$METRICS_FILE"
|
|
91
|
+
echo "" >> "$METRICS_FILE"
|
|
92
|
+
|
|
93
|
+
echo "Drift check complete. See $METRICS_FILE"
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Execute rollback for an intent in guided mode.
|
|
3
|
+
# Reads rollback plan from work/workflow-state/ (flat or per-intent per workflow-state-layout).
|
|
4
|
+
# High-risk steps: display only. Safe steps: prompt [y/N], execute if confirmed.
|
|
5
|
+
# Audit log: work/workflow-state/rollback-log.md
|
|
6
|
+
# Usage: ./scripts/execute-rollback.sh <intent-id> [--dry-run]
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
|
+
cd "$REPO_ROOT"
|
|
13
|
+
|
|
14
|
+
# shellcheck source=scripts/lib/common.sh
|
|
15
|
+
. "$SCRIPT_DIR/lib/common.sh"
|
|
16
|
+
|
|
17
|
+
INTENT_ID=""
|
|
18
|
+
DRY_RUN=false
|
|
19
|
+
|
|
20
|
+
while [ $# -gt 0 ]; do
|
|
21
|
+
case "$1" in
|
|
22
|
+
--dry-run) DRY_RUN=true; shift ;;
|
|
23
|
+
-*)
|
|
24
|
+
error_exit "Unknown option: $1. Usage: $0 <intent-id> [--dry-run]" 1
|
|
25
|
+
;;
|
|
26
|
+
*)
|
|
27
|
+
[ -z "$INTENT_ID" ] || error_exit "Multiple intent IDs given. Usage: $0 <intent-id> [--dry-run]" 1
|
|
28
|
+
INTENT_ID="$1"
|
|
29
|
+
shift
|
|
30
|
+
;;
|
|
31
|
+
esac
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
[ -n "$INTENT_ID" ] || error_exit "Missing intent-id. Usage: $0 <intent-id> [--dry-run]" 1
|
|
35
|
+
|
|
36
|
+
WS="work/workflow-state"
|
|
37
|
+
LOG_FILE="$WS/rollback-log.md"
|
|
38
|
+
|
|
39
|
+
# Resolve rollback plan path per workflow-state-layout.md
|
|
40
|
+
resolve_rollback_plan() {
|
|
41
|
+
local per_intent="$WS/$INTENT_ID"
|
|
42
|
+
if [ -d "$per_intent" ]; then
|
|
43
|
+
if [ -f "$per_intent/rollback.md" ]; then
|
|
44
|
+
echo "$per_intent/rollback.md"
|
|
45
|
+
return 0
|
|
46
|
+
fi
|
|
47
|
+
if [ -f "$per_intent/02_plan.md" ]; then
|
|
48
|
+
echo "$per_intent/02_plan.md"
|
|
49
|
+
return 0
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
if [ -f "$WS/rollback.md" ]; then
|
|
53
|
+
echo "$WS/rollback.md"
|
|
54
|
+
return 0
|
|
55
|
+
fi
|
|
56
|
+
if [ -f "$WS/02_plan.md" ]; then
|
|
57
|
+
echo "$WS/02_plan.md"
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
return 1
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ROLLBACK_SOURCE="$(resolve_rollback_plan)" || error_exit "No rollback plan found. Run /revert-plan $INTENT_ID first." 1
|
|
64
|
+
|
|
65
|
+
# Extract rollback content: from rollback.md (full file) or from 02_plan.md (rollback section)
|
|
66
|
+
get_rollback_content() {
|
|
67
|
+
if [[ "$ROLLBACK_SOURCE" == *rollback.md ]]; then
|
|
68
|
+
cat "$ROLLBACK_SOURCE"
|
|
69
|
+
else
|
|
70
|
+
awk '/^#+[[:space:]]+[Rr]ollback|^#[[:space:]]+Rollback Plan/,/^## [^Rr]/ {print}' "$ROLLBACK_SOURCE" || cat "$ROLLBACK_SOURCE"
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# High-risk keywords: do NOT offer execution, display only
|
|
75
|
+
HIGH_RISK_PATTERNS="force|drop|delete|migration down|production|auth|secret|--force|-f |rm -r|rm -rf"
|
|
76
|
+
|
|
77
|
+
# Safe pattern: simple git revert (single sha, no force)
|
|
78
|
+
# Note: step is content AFTER the bullet "- ", so no leading dash
|
|
79
|
+
is_safe_git_revert() {
|
|
80
|
+
local step="$1"
|
|
81
|
+
[[ "$step" =~ ^git[[:space:]]+revert[[:space:]]+[a-fA-F0-9]+([[:space:]]*$|[[:space:]]+-m) ]] && \
|
|
82
|
+
! echo "$step" | grep -qEi "$HIGH_RISK_PATTERNS"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
is_high_risk() {
|
|
86
|
+
echo "$1" | grep -qEi "$HIGH_RISK_PATTERNS"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
log_entry() {
|
|
90
|
+
local outcome="$1"
|
|
91
|
+
local step_preview="$2"
|
|
92
|
+
local truncated="${step_preview:0:100}"
|
|
93
|
+
local suffix=""
|
|
94
|
+
[ ${#step_preview} -gt 100 ] && suffix="..."
|
|
95
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
96
|
+
{
|
|
97
|
+
echo ""
|
|
98
|
+
echo "## $(date -u +"%Y-%m-%dT%H:%M:%SZ") - $INTENT_ID"
|
|
99
|
+
echo "- **Outcome:** $outcome"
|
|
100
|
+
echo "- **Step:** ${truncated}${suffix}"
|
|
101
|
+
echo ""
|
|
102
|
+
} >> "$LOG_FILE"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
CONTENT="$(get_rollback_content)"
|
|
106
|
+
|
|
107
|
+
if [ -z "$CONTENT" ]; then
|
|
108
|
+
error_exit "Rollback plan is empty or has no rollback section." 1
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
echo -e "${BLUE}Rollback Plan for $INTENT_ID${NC}"
|
|
112
|
+
echo -e "${BLUE}Source: $ROLLBACK_SOURCE${NC}"
|
|
113
|
+
echo ""
|
|
114
|
+
|
|
115
|
+
if [ "$DRY_RUN" = true ]; then
|
|
116
|
+
echo -e "${YELLOW}[DRY RUN] Would process the following steps:${NC}"
|
|
117
|
+
echo "$CONTENT" | grep -E '^[[:space:]]*- ' | sed 's/^/ /'
|
|
118
|
+
echo ""
|
|
119
|
+
echo "Run without --dry-run to execute (safe steps only, with confirmation)."
|
|
120
|
+
exit 0
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# Extract bullet steps (lines starting with - )
|
|
124
|
+
steps=()
|
|
125
|
+
while IFS= read -r line; do
|
|
126
|
+
if [[ "$line" =~ ^[[:space:]]*-[[:space:]]+(.+) ]]; then
|
|
127
|
+
steps+=("${BASH_REMATCH[1]}")
|
|
128
|
+
fi
|
|
129
|
+
done < <(echo "$CONTENT")
|
|
130
|
+
|
|
131
|
+
if [ ${#steps[@]} -eq 0 ]; then
|
|
132
|
+
echo "No bullet steps found in rollback plan. Displaying full plan:"
|
|
133
|
+
echo "$CONTENT"
|
|
134
|
+
exit 0
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
echo "Before executing any step that touches production, infra, or data, ensure you have reviewed the rollback plan."
|
|
138
|
+
echo ""
|
|
139
|
+
|
|
140
|
+
for step in "${steps[@]}"; do
|
|
141
|
+
step_trimmed="${step#"${step%%[![:space:]]*}"}"
|
|
142
|
+
if [ -z "$step_trimmed" ]; then
|
|
143
|
+
continue
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
if is_high_risk "$step_trimmed"; then
|
|
147
|
+
echo -e "${RED}[MANUAL]${NC} $step_trimmed"
|
|
148
|
+
echo " → Run this manually. Do not auto-execute."
|
|
149
|
+
log_entry "manual" "$step_trimmed"
|
|
150
|
+
continue
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
if is_safe_git_revert "$step_trimmed"; then
|
|
154
|
+
echo -e "${YELLOW}[CONFIRM]${NC} $step_trimmed"
|
|
155
|
+
read -r -p "Run this? [y/N] " resp
|
|
156
|
+
if [[ "$resp" =~ ^[yY]$ ]]; then
|
|
157
|
+
sha="$(echo "$step_trimmed" | grep -oE '[a-fA-F0-9]{7,40}' | head -1)"
|
|
158
|
+
if [ -n "$sha" ] && git revert --no-edit "$sha"; then
|
|
159
|
+
echo -e "${GREEN}Done.${NC}"
|
|
160
|
+
log_entry "confirmed" "$step_trimmed"
|
|
161
|
+
else
|
|
162
|
+
echo -e "${RED}Failed. Run manually if needed.${NC}"
|
|
163
|
+
log_entry "failed" "$step_trimmed"
|
|
164
|
+
fi
|
|
165
|
+
else
|
|
166
|
+
echo "Skipped."
|
|
167
|
+
log_entry "skipped" "$step_trimmed"
|
|
168
|
+
fi
|
|
169
|
+
else
|
|
170
|
+
echo -e "${YELLOW}[MANUAL]${NC} $step_trimmed"
|
|
171
|
+
echo " → Not in safe allowlist. Run manually."
|
|
172
|
+
log_entry "manual" "$step_trimmed"
|
|
173
|
+
fi
|
|
174
|
+
echo ""
|
|
175
|
+
done
|
|
176
|
+
|
|
177
|
+
echo -e "${GREEN}Rollback flow complete. Log: $LOG_FILE${NC}"
|