@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,278 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Show current ShipIt project status
|
|
4
|
+
# Displays active intent(s), workflow phase(s), intent counts. Supports flat and per-intent layout.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
# shellcheck source=lib/workflow_state.sh
|
|
10
|
+
[ -f "$SCRIPT_DIR/lib/workflow_state.sh" ] && source "$SCRIPT_DIR/lib/workflow_state.sh"
|
|
11
|
+
|
|
12
|
+
# Colors
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
YELLOW='\033[1;33m'
|
|
15
|
+
BLUE='\033[0;34m'
|
|
16
|
+
CYAN='\033[0;36m'
|
|
17
|
+
RED='\033[0;31m'
|
|
18
|
+
NC='\033[0m'
|
|
19
|
+
|
|
20
|
+
INTENT_DIR="work/intent"
|
|
21
|
+
WS_BASE="work/workflow-state"
|
|
22
|
+
|
|
23
|
+
echo -e "${BLUE}════════════════════════════════════════${NC}"
|
|
24
|
+
echo -e "${BLUE}ShipIt Project Status${NC}"
|
|
25
|
+
echo -e "${BLUE}════════════════════════════════════════${NC}"
|
|
26
|
+
echo ""
|
|
27
|
+
|
|
28
|
+
# Check if project.json exists
|
|
29
|
+
if [ ! -f "project.json" ]; then
|
|
30
|
+
echo -e "${RED}⚠ Not a ShipIt project (project.json missing)${NC}"
|
|
31
|
+
echo "Run /init-project to initialize a project."
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
PROJECT_NAME=$(jq -r '.name' project.json 2>/dev/null || echo "unknown")
|
|
36
|
+
echo -e "${CYAN}Project:${NC} $PROJECT_NAME"
|
|
37
|
+
echo ""
|
|
38
|
+
|
|
39
|
+
# Active intent(s) — from flat active.md or per-intent dirs
|
|
40
|
+
ACTIVE_IDS=($(list_active_intent_ids))
|
|
41
|
+
if [ "${#ACTIVE_IDS[@]}" -gt 0 ]; then
|
|
42
|
+
echo -e "${CYAN}Active Intent(s):${NC}"
|
|
43
|
+
for aid in "${ACTIVE_IDS[@]}"; do
|
|
44
|
+
dir="$(get_workflow_state_dir "$aid")"
|
|
45
|
+
phase=""
|
|
46
|
+
if [ -n "$dir" ] && [ -f "$dir/02_plan.md" ]; then
|
|
47
|
+
phase="Planning"
|
|
48
|
+
elif [ -n "$dir" ] && [ -f "$dir/03_implementation.md" ]; then
|
|
49
|
+
phase="Implementation"
|
|
50
|
+
elif [ -n "$dir" ] && [ -f "$dir/04_verification.md" ]; then
|
|
51
|
+
phase="Verification"
|
|
52
|
+
elif [ -n "$dir" ] && [ -f "$dir/01_analysis.md" ]; then
|
|
53
|
+
phase="Analysis"
|
|
54
|
+
else
|
|
55
|
+
phase="(see workflow state)"
|
|
56
|
+
fi
|
|
57
|
+
echo " $aid — $phase"
|
|
58
|
+
done
|
|
59
|
+
echo ""
|
|
60
|
+
fi
|
|
61
|
+
if [ -f "$WS_BASE/active.md" ]; then
|
|
62
|
+
ACTIVE_STATUS=$(grep -E "^\\*\\*Status:\\*\\*" "$WS_BASE/active.md" 2>/dev/null | sed 's/.*\*\*Status:\*\* //' | tr -d ' ' || echo "")
|
|
63
|
+
if [ "$ACTIVE_STATUS" = "idle" ] && [ "${#ACTIVE_IDS[@]}" -eq 0 ]; then
|
|
64
|
+
echo -e "${CYAN}Active Intent:${NC} none (idle)"
|
|
65
|
+
echo ""
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Intent counts (initialize to 0)
|
|
70
|
+
PLANNED=0
|
|
71
|
+
ACTIVE=0
|
|
72
|
+
BLOCKED=0
|
|
73
|
+
VALIDATING=0
|
|
74
|
+
SHIPPED=0
|
|
75
|
+
KILLED=0
|
|
76
|
+
TOTAL=0
|
|
77
|
+
|
|
78
|
+
if [ -d "$INTENT_DIR" ]; then
|
|
79
|
+
intent_files=()
|
|
80
|
+
while IFS= read -r file; do
|
|
81
|
+
intent_files+=("$file")
|
|
82
|
+
done < <(find "$INTENT_DIR" -type f -name "*.md" ! -name "_TEMPLATE.md" 2>/dev/null)
|
|
83
|
+
|
|
84
|
+
for file in "${intent_files[@]}"; do
|
|
85
|
+
[ -f "$file" ] || continue
|
|
86
|
+
|
|
87
|
+
STATUS=$(awk '
|
|
88
|
+
$0 ~ /^## Status/ {found=1; next}
|
|
89
|
+
found && $0 ~ /^## / {exit}
|
|
90
|
+
found && $0 ~ /[^[:space:]]/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0); print tolower($0); exit}
|
|
91
|
+
' "$file" 2>/dev/null || echo "planned")
|
|
92
|
+
|
|
93
|
+
case "$STATUS" in
|
|
94
|
+
planned) PLANNED=$((PLANNED + 1)) ;;
|
|
95
|
+
active) ACTIVE=$((ACTIVE + 1)) ;;
|
|
96
|
+
blocked) BLOCKED=$((BLOCKED + 1)) ;;
|
|
97
|
+
validating) VALIDATING=$((VALIDATING + 1)) ;;
|
|
98
|
+
shipped) SHIPPED=$((SHIPPED + 1)) ;;
|
|
99
|
+
killed) KILLED=$((KILLED + 1)) ;;
|
|
100
|
+
esac
|
|
101
|
+
done
|
|
102
|
+
|
|
103
|
+
TOTAL=$((PLANNED + ACTIVE + BLOCKED + VALIDATING + SHIPPED + KILLED))
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ "$TOTAL" -gt 0 ]; then
|
|
107
|
+
echo -e "${CYAN}Intent Status:${NC}"
|
|
108
|
+
echo " Planned: $PLANNED"
|
|
109
|
+
echo " Active: $ACTIVE"
|
|
110
|
+
echo " Blocked: $BLOCKED"
|
|
111
|
+
echo " Validating: $VALIDATING"
|
|
112
|
+
echo " Shipped: $SHIPPED"
|
|
113
|
+
echo " Killed: $KILLED"
|
|
114
|
+
echo " Total: $TOTAL"
|
|
115
|
+
echo ""
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# Workflow state files (flat or per-intent)
|
|
119
|
+
if [ -d "$WS_BASE" ]; then
|
|
120
|
+
echo -e "${CYAN}Workflow State:${NC}"
|
|
121
|
+
if [ "${#ACTIVE_IDS[@]}" -eq 0 ]; then
|
|
122
|
+
WORKFLOW_DIR="$WS_BASE"
|
|
123
|
+
for phase in 01_analysis 02_plan 03_implementation 04_verification 05_release_notes 06_shipped; do
|
|
124
|
+
if [ -f "$WORKFLOW_DIR/${phase}.md" ]; then
|
|
125
|
+
echo -e " ${GREEN}✓${NC} $phase"
|
|
126
|
+
else
|
|
127
|
+
echo -e " ${YELLOW}○${NC} $phase (not started)"
|
|
128
|
+
fi
|
|
129
|
+
done
|
|
130
|
+
else
|
|
131
|
+
for aid in "${ACTIVE_IDS[@]}"; do
|
|
132
|
+
dir="$(get_workflow_state_dir "$aid")"
|
|
133
|
+
[ -z "$dir" ] && continue
|
|
134
|
+
echo " [$aid]"
|
|
135
|
+
for phase in 01_analysis 02_plan 03_implementation 04_verification 05_release_notes 06_shipped; do
|
|
136
|
+
if [ -f "$dir/${phase}.md" ]; then
|
|
137
|
+
echo -e " ${GREEN}✓${NC} $phase"
|
|
138
|
+
else
|
|
139
|
+
echo -e " ${YELLOW}○${NC} $phase (not started)"
|
|
140
|
+
fi
|
|
141
|
+
done
|
|
142
|
+
done
|
|
143
|
+
fi
|
|
144
|
+
echo ""
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# Workflow state sanity check (top-level required files)
|
|
148
|
+
required_top_files=( "active.md" "blocked.md" "validating.md" "shipped.md" "disagreements.md" )
|
|
149
|
+
required_phase_files=( "01_analysis.md" "02_plan.md" "03_implementation.md" "04_verification.md" "05_release_notes.md" )
|
|
150
|
+
if [ -d "$WS_BASE" ]; then
|
|
151
|
+
missing_top=()
|
|
152
|
+
for file in "${required_top_files[@]}"; do
|
|
153
|
+
[ ! -f "$WS_BASE/$file" ] && missing_top+=("$file")
|
|
154
|
+
done
|
|
155
|
+
for aid in "${ACTIVE_IDS[@]}"; do
|
|
156
|
+
dir="$(get_workflow_state_dir "$aid")"
|
|
157
|
+
[ -z "$dir" ] && continue
|
|
158
|
+
for file in "${required_phase_files[@]}"; do
|
|
159
|
+
[ ! -f "$dir/$file" ] && missing_top+=("$aid/$file")
|
|
160
|
+
done
|
|
161
|
+
done
|
|
162
|
+
if [ "${#ACTIVE_IDS[@]}" -eq 0 ]; then
|
|
163
|
+
for file in "${required_phase_files[@]}"; do
|
|
164
|
+
[ ! -f "$WS_BASE/$file" ] && missing_top+=("$file")
|
|
165
|
+
done
|
|
166
|
+
fi
|
|
167
|
+
if [ "${#missing_top[@]}" -gt 0 ]; then
|
|
168
|
+
echo -e "${YELLOW}⚠ Workflow State Check:${NC}"
|
|
169
|
+
echo " Missing: ${missing_top[*]}"
|
|
170
|
+
echo " 💡 Run /ship <id> or re-run /init-project to seed files"
|
|
171
|
+
echo ""
|
|
172
|
+
fi
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
# Recent intents
|
|
176
|
+
if [ -d "$INTENT_DIR" ] && [ "${TOTAL:-0}" -gt 0 ]; then
|
|
177
|
+
echo -e "${CYAN}Recent Intents:${NC}"
|
|
178
|
+
if [ "${#intent_files[@]}" -gt 0 ]; then
|
|
179
|
+
ls -t "${intent_files[@]}" 2>/dev/null | head -5 | while read -r file; do
|
|
180
|
+
ID=$(basename "$file" .md)
|
|
181
|
+
TITLE=$(grep "^# " "$file" 2>/dev/null | head -1 | sed 's/^# [^:]*: //' || echo "$ID")
|
|
182
|
+
STATUS=$(awk '
|
|
183
|
+
$0 ~ /^## Status/ {found=1; next}
|
|
184
|
+
found && $0 ~ /^## / {exit}
|
|
185
|
+
found && $0 ~ /[^[:space:]]/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0); print tolower($0); exit}
|
|
186
|
+
' "$file" 2>/dev/null || echo "unknown")
|
|
187
|
+
echo " $ID: $TITLE ($STATUS)"
|
|
188
|
+
done
|
|
189
|
+
fi
|
|
190
|
+
echo ""
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# Pending approvals (check flat or each active's plan)
|
|
194
|
+
check_pending_approval() {
|
|
195
|
+
local f="$1"
|
|
196
|
+
[ ! -f "$f" ] && return 0
|
|
197
|
+
if grep -q "\[ \].*approval\|\[ \].*Approval" "$f" 2>/dev/null && ! grep -q "\[x\].*approval\|\[x\].*Approval\|Approved\|APPROVE" "$f" 2>/dev/null; then
|
|
198
|
+
echo -e "${YELLOW}⚠ Pending Approval:${NC}"
|
|
199
|
+
echo " Plan approval required in $f"
|
|
200
|
+
echo ""
|
|
201
|
+
return 1
|
|
202
|
+
fi
|
|
203
|
+
return 0
|
|
204
|
+
}
|
|
205
|
+
if [ "${#ACTIVE_IDS[@]}" -eq 0 ] && [ -f "$WS_BASE/02_plan.md" ]; then
|
|
206
|
+
check_pending_approval "$WS_BASE/02_plan.md" || true
|
|
207
|
+
else
|
|
208
|
+
for aid in "${ACTIVE_IDS[@]}"; do
|
|
209
|
+
dir="$(get_workflow_state_dir "$aid")"
|
|
210
|
+
[ -n "$dir" ] && [ -f "$dir/02_plan.md" ] && check_pending_approval "$dir/02_plan.md" || true
|
|
211
|
+
done
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
# Test results summary
|
|
215
|
+
if command -v pnpm >/dev/null 2>&1 && [ -f "package.json" ]; then
|
|
216
|
+
echo -e "${CYAN}Test Status:${NC}"
|
|
217
|
+
if [ -f "package.json" ] && grep -q '"test"' package.json; then
|
|
218
|
+
# Try to get test results (non-blocking, timeout to avoid hanging)
|
|
219
|
+
test_output=$(timeout 10 pnpm test 2>&1 | tail -5 || echo "")
|
|
220
|
+
if [ -n "$test_output" ] && echo "$test_output" | grep -q "PASS\|pass\|✓\|Test Files.*passed"; then
|
|
221
|
+
test_count=$(echo "$test_output" | grep -oE "[0-9]+ passing\|[0-9]+ passed\|Test Files.*[0-9]+" | head -1 || echo "")
|
|
222
|
+
echo -e " ${GREEN}✓${NC} Tests passing${test_count:+ ($test_count)}"
|
|
223
|
+
elif [ -n "$test_output" ] && echo "$test_output" | grep -q "FAIL\|fail\|✗\|failed"; then
|
|
224
|
+
echo -e " ${RED}✗${NC} Tests failing (run 'pnpm test' for details)"
|
|
225
|
+
else
|
|
226
|
+
echo " ${YELLOW}○${NC} Run 'pnpm test' to check test status"
|
|
227
|
+
fi
|
|
228
|
+
else
|
|
229
|
+
echo " ${YELLOW}○${NC} No test script configured"
|
|
230
|
+
fi
|
|
231
|
+
echo ""
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
# Token/usage summary (if usage.json exists)
|
|
235
|
+
USAGE_FILE="_system/artifacts/usage.json"
|
|
236
|
+
if [ -f "$USAGE_FILE" ] && command -v jq >/dev/null 2>&1; then
|
|
237
|
+
ENTRY_COUNT=$(jq '.entries | length' "$USAGE_FILE" 2>/dev/null || echo "0")
|
|
238
|
+
if [ "${ENTRY_COUNT:-0}" -gt 0 ]; then
|
|
239
|
+
echo -e "${CYAN}Usage (tokens/cost):${NC}"
|
|
240
|
+
LAST=$(jq -r '.entries[-1] | "\(.intent_id) \(.phase): \(.tokens_in + .tokens_out) tokens" + (if .estimated_cost_usd != null then " (~$" + (.estimated_cost_usd|tostring) + ")" else " (estimate)" end)' "$USAGE_FILE" 2>/dev/null || true)
|
|
241
|
+
if [ -n "$LAST" ]; then
|
|
242
|
+
echo " Last: $LAST"
|
|
243
|
+
fi
|
|
244
|
+
echo " Run \`pnpm usage-report\` for full table"
|
|
245
|
+
echo ""
|
|
246
|
+
fi
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
# Recent changes (git)
|
|
250
|
+
if command -v git >/dev/null 2>&1 && git rev-parse --git-dir >/dev/null 2>&1; then
|
|
251
|
+
echo -e "${CYAN}Recent Changes:${NC}"
|
|
252
|
+
recent_commits=$(git log --oneline -5 2>/dev/null | head -5 || true)
|
|
253
|
+
if [ -n "$recent_commits" ]; then
|
|
254
|
+
echo "$recent_commits" | while IFS= read -r line; do
|
|
255
|
+
[ -n "$line" ] && echo " $line"
|
|
256
|
+
done
|
|
257
|
+
else
|
|
258
|
+
echo " (No commits yet)"
|
|
259
|
+
fi
|
|
260
|
+
echo ""
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# Suggestions
|
|
264
|
+
echo -e "${CYAN}Next Steps:${NC}"
|
|
265
|
+
if [ "$TOTAL" -eq 0 ]; then
|
|
266
|
+
echo " 💡 Run /scope-project to break down your project"
|
|
267
|
+
echo " 💡 Run /new_intent to create your first intent"
|
|
268
|
+
elif [ "$ACTIVE" -eq 0 ] && [ "$PLANNED" -gt 0 ]; then
|
|
269
|
+
echo " 💡 Run /ship <id> to start working on a planned intent"
|
|
270
|
+
echo " 💡 Run /new_intent to create another intent"
|
|
271
|
+
elif [ "$ACTIVE" -gt 0 ]; then
|
|
272
|
+
echo " 💡 Continue workflow for active intent"
|
|
273
|
+
echo " 💡 Run /status to see current phase"
|
|
274
|
+
else
|
|
275
|
+
echo " 💡 Run /generate-release-plan to update release plan"
|
|
276
|
+
echo " 💡 Run /generate-roadmap to update roadmap"
|
|
277
|
+
fi
|
|
278
|
+
echo ""
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Suggest next actions based on current project state
|
|
4
|
+
# Supports flat and per-intent workflow state; considers all active intents.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
# shellcheck source=lib/workflow_state.sh
|
|
10
|
+
[ -f "$SCRIPT_DIR/lib/workflow_state.sh" ] && source "$SCRIPT_DIR/lib/workflow_state.sh"
|
|
11
|
+
|
|
12
|
+
# Colors
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
YELLOW='\033[1;33m'
|
|
15
|
+
BLUE='\033[0;34m'
|
|
16
|
+
CYAN='\033[0;36m'
|
|
17
|
+
RED='\033[0;31m'
|
|
18
|
+
NC='\033[0m'
|
|
19
|
+
|
|
20
|
+
INTENT_DIR="work/intent"
|
|
21
|
+
WS_BASE="work/workflow-state"
|
|
22
|
+
|
|
23
|
+
echo -e "${BLUE}ShipIt Suggestions${NC}"
|
|
24
|
+
echo ""
|
|
25
|
+
|
|
26
|
+
# Check if project exists
|
|
27
|
+
if [ ! -f "project.json" ]; then
|
|
28
|
+
echo -e "${YELLOW}→ Initialize a project:${NC} /init-project \"My Project\""
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Count intents by status (initialize to 0)
|
|
33
|
+
PLANNED=0
|
|
34
|
+
ACTIVE=0
|
|
35
|
+
SHIPPED=0
|
|
36
|
+
KILLED=0
|
|
37
|
+
TOTAL=0
|
|
38
|
+
|
|
39
|
+
if [ -d "$INTENT_DIR" ]; then
|
|
40
|
+
intent_files=()
|
|
41
|
+
while IFS= read -r file; do
|
|
42
|
+
intent_files+=("$file")
|
|
43
|
+
done < <(find "$INTENT_DIR" -type f -name "*.md" ! -name "_TEMPLATE.md" 2>/dev/null)
|
|
44
|
+
|
|
45
|
+
for file in "${intent_files[@]}"; do
|
|
46
|
+
[ -f "$file" ] || continue
|
|
47
|
+
|
|
48
|
+
STATUS=$(awk '
|
|
49
|
+
$0 ~ /^## Status/ {found=1; next}
|
|
50
|
+
found && $0 ~ /^## / {exit}
|
|
51
|
+
found && $0 ~ /[^[:space:]]/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0); print tolower($0); exit}
|
|
52
|
+
' "$file" 2>/dev/null || echo "planned")
|
|
53
|
+
|
|
54
|
+
case "$STATUS" in
|
|
55
|
+
planned) PLANNED=$((PLANNED + 1)) ;;
|
|
56
|
+
active) ACTIVE=$((ACTIVE + 1)) ;;
|
|
57
|
+
shipped) SHIPPED=$((SHIPPED + 1)) ;;
|
|
58
|
+
killed) KILLED=$((KILLED + 1)) ;;
|
|
59
|
+
esac
|
|
60
|
+
done
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
TOTAL=$((PLANNED + ACTIVE + SHIPPED + KILLED))
|
|
64
|
+
|
|
65
|
+
# Active intent(s) — use first for phase-based suggestions; show all if multiple
|
|
66
|
+
ACTIVE_IDS=($(list_active_intent_ids))
|
|
67
|
+
ACTIVE_ID=""
|
|
68
|
+
ACTIVE_PHASE=""
|
|
69
|
+
WORKFLOW_DIR=""
|
|
70
|
+
if [ "${#ACTIVE_IDS[@]}" -gt 0 ]; then
|
|
71
|
+
ACTIVE_ID="${ACTIVE_IDS[0]}"
|
|
72
|
+
WORKFLOW_DIR="$(get_workflow_state_dir "$ACTIVE_ID")"
|
|
73
|
+
[ -z "$WORKFLOW_DIR" ] && WORKFLOW_DIR="$WS_BASE"
|
|
74
|
+
if [ -n "$WORKFLOW_DIR" ]; then
|
|
75
|
+
if [ -f "$WORKFLOW_DIR/02_plan.md" ]; then ACTIVE_PHASE="Plan"; fi
|
|
76
|
+
if [ -f "$WORKFLOW_DIR/03_implementation.md" ]; then ACTIVE_PHASE="Implementation"; fi
|
|
77
|
+
if [ -f "$WORKFLOW_DIR/04_verification.md" ]; then ACTIVE_PHASE="Verification"; fi
|
|
78
|
+
if [ -f "$WORKFLOW_DIR/01_analysis.md" ] && [ -z "$ACTIVE_PHASE" ]; then ACTIVE_PHASE="Analysis"; fi
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Generate suggestions
|
|
83
|
+
SUGGESTIONS=()
|
|
84
|
+
|
|
85
|
+
# No intents at all
|
|
86
|
+
if [ "$TOTAL" -eq 0 ]; then
|
|
87
|
+
SUGGESTIONS+=("${CYAN}1. Scope your project:${NC} /scope-project \"project description\"")
|
|
88
|
+
SUGGESTIONS+=("${CYAN}2. Create your first intent:${NC} /new_intent")
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Has planned intents but no active
|
|
92
|
+
if [ "$PLANNED" -gt 0 ] && [ -z "$ACTIVE_ID" ]; then
|
|
93
|
+
# Find first planned intent (actually check status)
|
|
94
|
+
FIRST_PLANNED=""
|
|
95
|
+
for file in "${intent_files[@]}"; do
|
|
96
|
+
[ -f "$file" ] || continue
|
|
97
|
+
STATUS=$(awk '
|
|
98
|
+
$0 ~ /^## Status/ {found=1; next}
|
|
99
|
+
found && $0 ~ /^## / {exit}
|
|
100
|
+
found && $0 ~ /[^[:space:]]/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0); print tolower($0); exit}
|
|
101
|
+
' "$file" 2>/dev/null || echo "")
|
|
102
|
+
if [ "$STATUS" = "planned" ]; then
|
|
103
|
+
FIRST_PLANNED=$(basename "$file" .md)
|
|
104
|
+
break
|
|
105
|
+
fi
|
|
106
|
+
done
|
|
107
|
+
if [ -n "$FIRST_PLANNED" ]; then
|
|
108
|
+
SUGGESTIONS+=("${CYAN}1. Start working on an intent:${NC} /ship $FIRST_PLANNED")
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Has active intent(s)
|
|
113
|
+
if [ -n "$ACTIVE_ID" ]; then
|
|
114
|
+
if [ "${#ACTIVE_IDS[@]}" -gt 1 ]; then
|
|
115
|
+
SUGGESTIONS+=("${CYAN}Multiple active intents:${NC} ${ACTIVE_IDS[*]} — use /ship <id> or /verify <id>")
|
|
116
|
+
fi
|
|
117
|
+
case "$ACTIVE_PHASE" in
|
|
118
|
+
*analysis*|*Analysis*)
|
|
119
|
+
SUGGESTIONS+=("${CYAN}1. Continue analysis:${NC} Fill in $WORKFLOW_DIR/01_analysis.md")
|
|
120
|
+
SUGGESTIONS+=("${CYAN}2. Move to planning:${NC} Run /ship $ACTIVE_ID (will proceed to Phase 2)")
|
|
121
|
+
;;
|
|
122
|
+
*plan*|*Plan*)
|
|
123
|
+
if [ -n "$WORKFLOW_DIR" ] && [ -f "$WORKFLOW_DIR/02_plan.md" ] && grep -q "\[ \].*approval\|\[ \].*Approval" "$WORKFLOW_DIR/02_plan.md" 2>/dev/null && ! grep -q "\[x\].*approval\|\[x\].*Approval\|Approved\|APPROVE" "$WORKFLOW_DIR/02_plan.md" 2>/dev/null; then
|
|
124
|
+
SUGGESTIONS+=("${YELLOW}1. ⚠ Approval required:${NC} Review and approve $WORKFLOW_DIR/02_plan.md")
|
|
125
|
+
else
|
|
126
|
+
SUGGESTIONS+=("${CYAN}1. Continue to implementation:${NC} Run /ship $ACTIVE_ID")
|
|
127
|
+
fi
|
|
128
|
+
;;
|
|
129
|
+
*implementation*|*Implementation*)
|
|
130
|
+
SUGGESTIONS+=("${CYAN}1. Continue implementation:${NC} Fill in $WORKFLOW_DIR/03_implementation.md")
|
|
131
|
+
SUGGESTIONS+=("${CYAN}2. Move to verification:${NC} Run /verify $ACTIVE_ID")
|
|
132
|
+
;;
|
|
133
|
+
*verification*|*Verification*)
|
|
134
|
+
SUGGESTIONS+=("${CYAN}1. Complete verification:${NC} Fill in $WORKFLOW_DIR/04_verification.md")
|
|
135
|
+
SUGGESTIONS+=("${CYAN}2. Move to release:${NC} Run /ship $ACTIVE_ID")
|
|
136
|
+
;;
|
|
137
|
+
*)
|
|
138
|
+
SUGGESTIONS+=("${CYAN}1. Continue workflow:${NC} Run /ship $ACTIVE_ID")
|
|
139
|
+
;;
|
|
140
|
+
esac
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Release plan needs update
|
|
144
|
+
if [ "$PLANNED" -gt 0 ] || [ "$ACTIVE" -gt 0 ]; then
|
|
145
|
+
if [ ! -f "work/release/plan.md" ] || [ -n "$(find "$INTENT_DIR" -type f -name "*.md" ! -name "_TEMPLATE.md" -newer "work/release/plan.md" -print -quit 2>/dev/null)" ]; then
|
|
146
|
+
SUGGESTIONS+=("${CYAN}Update release plan:${NC} /generate-release-plan")
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# Roadmap needs update
|
|
151
|
+
if [ "$PLANNED" -gt 0 ] || [ "$ACTIVE" -gt 0 ]; then
|
|
152
|
+
if [ ! -f "work/roadmap/now.md" ] || [ -n "$(find "$INTENT_DIR" -type f -name "*.md" ! -name "_TEMPLATE.md" -newer "work/roadmap/now.md" -print -quit 2>/dev/null)" ]; then
|
|
153
|
+
SUGGESTIONS+=("${CYAN}Update roadmap:${NC} /generate-roadmap")
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# Show suggestions
|
|
158
|
+
if [ ${#SUGGESTIONS[@]} -eq 0 ]; then
|
|
159
|
+
echo -e "${GREEN}✓ Everything looks good!${NC}"
|
|
160
|
+
echo ""
|
|
161
|
+
echo "You might want to:"
|
|
162
|
+
echo " → Create a new intent: /new_intent"
|
|
163
|
+
echo " → Check project status: /status"
|
|
164
|
+
echo " → Review release plan: /generate-release-plan"
|
|
165
|
+
else
|
|
166
|
+
for suggestion in "${SUGGESTIONS[@]}"; do
|
|
167
|
+
echo -e "$suggestion"
|
|
168
|
+
done
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
echo ""
|
|
172
|
+
echo -e "${CYAN}More help:${NC} /help [command]"
|
|
173
|
+
echo -e "${CYAN}Project status:${NC} /status"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Smoke test for headless mode (issue #65). Ensures run-phase.sh runs and fails cleanly without API keys.
|
|
3
|
+
# With OPENAI_API_KEY or ANTHROPIC_API_KEY set, you can run phase 1 and assert output file exists.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
9
|
+
cd "$REPO_ROOT"
|
|
10
|
+
|
|
11
|
+
RED='\033[0;31m'
|
|
12
|
+
GREEN='\033[0;32m'
|
|
13
|
+
NC='\033[0m'
|
|
14
|
+
fail() { echo -e "${RED}FAIL: $*${NC}" >&2; exit 1; }
|
|
15
|
+
ok() { echo -e "${GREEN}OK: $*${NC}"; }
|
|
16
|
+
|
|
17
|
+
# Need an intent that exists and has workflow state (or we'll get "run workflow-orchestrator first")
|
|
18
|
+
INTENT_ID="${1:-F-DUMMY}"
|
|
19
|
+
if [ ! -f "work/intent/features/$INTENT_ID.md" ] && [ ! -f "work/intent/$INTENT_ID.md" ]; then
|
|
20
|
+
# Try to find any intent
|
|
21
|
+
INTENT_ID=""
|
|
22
|
+
for f in work/intent/features/*.md work/intent/*.md; do
|
|
23
|
+
[ -f "$f" ] && INTENT_ID="$(basename "$f" .md)" && break
|
|
24
|
+
done 2>/dev/null || true
|
|
25
|
+
fi
|
|
26
|
+
[ -n "$INTENT_ID" ] || { echo "Skip: no intent file found (create one or pass intent-id)"; exit 0; }
|
|
27
|
+
|
|
28
|
+
# 1) Without API keys, run-phase.sh must exit 1 with clear message
|
|
29
|
+
unset OPENAI_API_KEY ANTHROPIC_API_KEY
|
|
30
|
+
out="$(bash scripts/headless/run-phase.sh "$INTENT_ID" 1 2>&1)" || true
|
|
31
|
+
if [ "${#out}" -eq 0 ]; then
|
|
32
|
+
fail "run-phase.sh should print message when API keys unset"
|
|
33
|
+
fi
|
|
34
|
+
if ! echo "$out" | grep -q "OPENAI_API_KEY\|ANTHROPIC_API_KEY"; then
|
|
35
|
+
fail "run-phase.sh should mention API key env vars when unset"
|
|
36
|
+
fi
|
|
37
|
+
ok "run-phase.sh exits with clear message when API keys unset"
|
|
38
|
+
|
|
39
|
+
# 2) Invalid phase number must exit 1
|
|
40
|
+
out2="$(bash scripts/headless/run-phase.sh "$INTENT_ID" 99 2>&1)" || true
|
|
41
|
+
if ! echo "$out2" | grep -q "Phase must be 1-5\|error\|Error"; then
|
|
42
|
+
fail "run-phase.sh should reject invalid phase number"
|
|
43
|
+
fi
|
|
44
|
+
ok "run-phase.sh rejects invalid phase"
|
|
45
|
+
|
|
46
|
+
echo ""
|
|
47
|
+
echo -e "${GREEN}Headless smoke test passed.${NC}"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Automated ShipIt framework E2E test: create test project and run basic assertions.
|
|
3
|
+
# Headless (no editor). For full workflow use .cursor/commands/test_shipit.md.
|
|
4
|
+
# Usage: ./scripts/test-shipit.sh [--clean]
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
9
|
+
cd "$ROOT_DIR"
|
|
10
|
+
|
|
11
|
+
# Optional: remove existing test project first
|
|
12
|
+
if [[ "${1:-}" == "--clean" ]]; then
|
|
13
|
+
rm -rf tests/test-project
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Source common for error_exit
|
|
17
|
+
# shellcheck source=scripts/lib/common.sh
|
|
18
|
+
source "$ROOT_DIR/scripts/lib/common.sh"
|
|
19
|
+
|
|
20
|
+
echo "ShipIt framework E2E test (headless)"
|
|
21
|
+
echo " Root: $ROOT_DIR"
|
|
22
|
+
echo ""
|
|
23
|
+
|
|
24
|
+
# 1) Create test project (non-interactive; reads tests/fixtures.json)
|
|
25
|
+
echo "Step 1: Create test project..."
|
|
26
|
+
./scripts/init-project.sh shipit-test || error_exit "init-project.sh failed" 1
|
|
27
|
+
|
|
28
|
+
# 2) Assert key files and dirs exist
|
|
29
|
+
TP="$ROOT_DIR/tests/test-project"
|
|
30
|
+
assert() {
|
|
31
|
+
local path="$1"
|
|
32
|
+
local desc="${2:-$path}"
|
|
33
|
+
if [[ -e "$path" ]]; then
|
|
34
|
+
echo " ok: $desc"
|
|
35
|
+
else
|
|
36
|
+
error_exit "Missing: $desc ($path)" 1
|
|
37
|
+
fi
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
echo "Step 2: Assert project structure..."
|
|
41
|
+
assert "$TP/project.json" "project.json"
|
|
42
|
+
assert "$TP/AGENTS.md" "AGENTS.md"
|
|
43
|
+
assert "$TP/scripts" "scripts/"
|
|
44
|
+
assert "$TP/scripts/verify.sh" "scripts/verify.sh"
|
|
45
|
+
assert "$TP/_system/architecture/CANON.md" "_system/architecture/CANON.md"
|
|
46
|
+
assert "$TP/work/intent" "work/intent/"
|
|
47
|
+
# .override/ is created by CLI init; init-project.sh may or may not create it
|
|
48
|
+
[[ -d "$TP/.override" ]] && echo " ok: .override/" || true
|
|
49
|
+
|
|
50
|
+
echo ""
|
|
51
|
+
echo "PASS: ShipIt framework E2E (test project creation and structure)"
|
|
52
|
+
exit 0
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Smoke test for workflow-state flat vs per-intent layout (issue #60).
|
|
3
|
+
# Run from repo root: bash scripts/test-workflow-state.sh
|
|
4
|
+
# Requires: project.json, work/intent (with at least one intent).
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
10
|
+
cd "$REPO_ROOT"
|
|
11
|
+
|
|
12
|
+
RED='\033[0;31m'
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
NC='\033[0m'
|
|
15
|
+
|
|
16
|
+
fail() { echo -e "${RED}FAIL: $*${NC}" >&2; exit 1; }
|
|
17
|
+
ok() { echo -e "${GREEN}OK: $*${NC}"; }
|
|
18
|
+
|
|
19
|
+
# Source the lib (use ORCHESTRATOR_DIR pattern so SCRIPT_DIR in lib is lib dir)
|
|
20
|
+
source "$SCRIPT_DIR/lib/workflow_state.sh"
|
|
21
|
+
|
|
22
|
+
# 1) list_active_intent_ids: should not crash; may be empty or list ids
|
|
23
|
+
ids=($(list_active_intent_ids))
|
|
24
|
+
ok "list_active_intent_ids returned ${#ids[@]} active(s)"
|
|
25
|
+
|
|
26
|
+
# 2) get_workflow_state_dir with no arg: when flat in use, returns WS_BASE
|
|
27
|
+
dir="$(get_workflow_state_dir)"
|
|
28
|
+
if flat_in_use; then
|
|
29
|
+
[ "$dir" = "$WS_BASE" ] || fail "get_workflow_state_dir (no arg) should be WS_BASE when flat in use, got: $dir"
|
|
30
|
+
ok "get_workflow_state_dir () returns flat when flat in use"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# 3) get_workflow_state_dir with intent id: returns path (flat or per-intent)
|
|
34
|
+
if [ ${#ids[@]} -gt 0 ]; then
|
|
35
|
+
first="${ids[0]}"
|
|
36
|
+
dir="$(get_workflow_state_dir "$first")"
|
|
37
|
+
[ -n "$dir" ] || fail "get_workflow_state_dir $first returned empty"
|
|
38
|
+
[[ "$dir" == *"workflow-state"* ]] || fail "get_workflow_state_dir $first should contain workflow-state, got: $dir"
|
|
39
|
+
ok "get_workflow_state_dir $first = $dir"
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# 4) ensure_workflow_state_dir with new id: creates per-intent when flat already has one active
|
|
43
|
+
# Skip if we don't have two intents - just ensure ensure_workflow_state_dir runs
|
|
44
|
+
d="$(ensure_workflow_state_dir "F-DUMMY")"
|
|
45
|
+
[ -n "$d" ] || fail "ensure_workflow_state_dir F-DUMMY returned empty"
|
|
46
|
+
ok "ensure_workflow_state_dir F-DUMMY = $d"
|
|
47
|
+
|
|
48
|
+
echo ""
|
|
49
|
+
echo -e "${GREEN}Workflow state layout smoke test passed.${NC}"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Print token/cost usage report from _system/artifacts/usage.json.
|
|
4
|
+
# Usage: usage-report.sh [--last N]
|
|
5
|
+
# Default: show all entries. --last N: show last N entries only.
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
+
CD="${CD:-$REPO_ROOT}"
|
|
12
|
+
cd "$CD"
|
|
13
|
+
|
|
14
|
+
# shellcheck source=scripts/lib/common.sh
|
|
15
|
+
. "$SCRIPT_DIR/lib/common.sh"
|
|
16
|
+
|
|
17
|
+
require_cmd jq
|
|
18
|
+
|
|
19
|
+
USAGE_FILE="_system/artifacts/usage.json"
|
|
20
|
+
LAST_N=""
|
|
21
|
+
|
|
22
|
+
if [ "${1:-}" = "--last" ] && [ -n "${2:-}" ]; then
|
|
23
|
+
LAST_N="$2"
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [ ! -f "$USAGE_FILE" ]; then
|
|
27
|
+
echo "No usage data (missing $USAGE_FILE). Record with: pnpm usage-record <intent_id> <phase> <tokens_in> <tokens_out>" >&2
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
ENTRIES=$(jq -r '.entries // []' "$USAGE_FILE")
|
|
32
|
+
if [ "$ENTRIES" = "[]" ] || [ -z "$ENTRIES" ]; then
|
|
33
|
+
echo "No usage entries yet."
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
if [ -n "$LAST_N" ]; then
|
|
38
|
+
DATA=$(jq --argjson n "$LAST_N" '.entries | if length > $n then .[length-$n:] else . end' "$USAGE_FILE")
|
|
39
|
+
else
|
|
40
|
+
DATA=$(jq '.entries' "$USAGE_FILE")
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# Table: intent_id, phase, tokens_in, tokens_out, total, estimated_cost_usd, date, source
|
|
44
|
+
printf "%-12s %-14s %10s %10s %10s %12s %-25s %s\n" "intent_id" "phase" "tokens_in" "tokens_out" "total" "cost_usd" "timestamp_iso" "source"
|
|
45
|
+
echo "$DATA" | jq -r '.[] | [.intent_id, .phase, (.tokens_in|tostring), (.tokens_out|tostring), ((.tokens_in + .tokens_out)|tostring), (if .estimated_cost_usd == null then "-" else (.estimated_cost_usd | tostring) end), .timestamp_iso, .source] | @tsv' 2>/dev/null | while IFS=$'\t' read -r intent_id phase tokens_in tokens_out total cost ts source; do
|
|
46
|
+
printf "%-12s %-14s %10s %10s %10s %12s %-25s %s\n" "$intent_id" "$phase" "$tokens_in" "$tokens_out" "$total" "$cost" "$ts" "$source"
|
|
47
|
+
done
|