forge-workflow 0.0.4 โ 0.0.6
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/.claude/commands/dev.md +345 -340
- package/.claude/commands/plan.md +566 -521
- package/.claude/commands/premerge.md +186 -176
- package/.claude/commands/research.md +42 -42
- package/.claude/commands/review.md +448 -442
- package/.claude/commands/rollback.md +721 -721
- package/.claude/commands/ship.md +212 -164
- package/.claude/commands/sonarcloud.md +152 -152
- package/.claude/commands/status.md +90 -48
- package/.claude/commands/validate.md +288 -282
- package/.claude/commands/verify.md +269 -221
- package/.claude/rules/greptile-review-process.md +285 -285
- package/.claude/rules/workflow.md +121 -105
- package/.claude/scripts/greptile-resolve.sh +558 -526
- package/.claude/scripts/load-env.sh +32 -32
- package/.cline/workflows/dev.md +342 -337
- package/.cline/workflows/plan.md +563 -518
- package/.cline/workflows/premerge.md +183 -173
- package/.cline/workflows/research.md +39 -39
- package/.cline/workflows/review.md +445 -439
- package/.cline/workflows/rollback.md +718 -718
- package/.cline/workflows/ship.md +209 -161
- package/.cline/workflows/sonarcloud.md +146 -146
- package/.cline/workflows/status.md +87 -45
- package/.cline/workflows/validate.md +285 -279
- package/.cline/workflows/verify.md +266 -218
- package/.codex/config.toml +11 -11
- package/.codex/skills/dev/SKILL.md +345 -340
- package/.codex/skills/plan/SKILL.md +566 -521
- package/.codex/skills/premerge/SKILL.md +186 -176
- package/.codex/skills/research/SKILL.md +42 -42
- package/.codex/skills/review/SKILL.md +448 -442
- package/.codex/skills/rollback/SKILL.md +721 -721
- package/.codex/skills/ship/SKILL.md +212 -164
- package/.codex/skills/sonarcloud/SKILL.md +149 -149
- package/.codex/skills/status/SKILL.md +90 -48
- package/.codex/skills/validate/SKILL.md +288 -282
- package/.codex/skills/verify/SKILL.md +269 -221
- package/.cursor/commands/dev.md +342 -337
- package/.cursor/commands/plan.md +563 -518
- package/.cursor/commands/premerge.md +183 -173
- package/.cursor/commands/research.md +39 -39
- package/.cursor/commands/review.md +445 -439
- package/.cursor/commands/rollback.md +718 -718
- package/.cursor/commands/ship.md +209 -161
- package/.cursor/commands/sonarcloud.md +146 -146
- package/.cursor/commands/status.md +87 -45
- package/.cursor/commands/validate.md +285 -279
- package/.cursor/commands/verify.md +266 -218
- package/.cursor/rules/permissions-guidance.mdc +37 -37
- package/.forge/hooks/check-tdd.js +240 -240
- package/.github/PLUGIN_TEMPLATE.json +32 -32
- package/.github/prompts/dev.prompt.md +347 -342
- package/.github/prompts/plan.prompt.md +568 -523
- package/.github/prompts/premerge.prompt.md +188 -178
- package/.github/prompts/research.prompt.md +44 -44
- package/.github/prompts/review.prompt.md +450 -444
- package/.github/prompts/rollback.prompt.md +723 -723
- package/.github/prompts/ship.prompt.md +214 -166
- package/.github/prompts/sonarcloud.prompt.md +151 -151
- package/.github/prompts/status.prompt.md +92 -50
- package/.github/prompts/validate.prompt.md +290 -284
- package/.github/prompts/verify.prompt.md +271 -223
- package/.github/workflows/beads-to-github.yml +56 -0
- package/.github/workflows/github-to-beads.yml +97 -0
- package/.kilocode/workflows/dev.md +346 -341
- package/.kilocode/workflows/plan.md +567 -522
- package/.kilocode/workflows/premerge.md +187 -177
- package/.kilocode/workflows/research.md +43 -43
- package/.kilocode/workflows/review.md +449 -443
- package/.kilocode/workflows/rollback.md +722 -722
- package/.kilocode/workflows/ship.md +213 -165
- package/.kilocode/workflows/sonarcloud.md +150 -150
- package/.kilocode/workflows/status.md +91 -49
- package/.kilocode/workflows/validate.md +289 -283
- package/.kilocode/workflows/verify.md +270 -222
- package/.mcp.json.example +12 -12
- package/.opencode/commands/dev.md +345 -340
- package/.opencode/commands/plan.md +566 -521
- package/.opencode/commands/premerge.md +186 -176
- package/.opencode/commands/research.md +42 -42
- package/.opencode/commands/review.md +448 -442
- package/.opencode/commands/rollback.md +721 -721
- package/.opencode/commands/ship.md +212 -164
- package/.opencode/commands/sonarcloud.md +149 -149
- package/.opencode/commands/status.md +90 -48
- package/.opencode/commands/validate.md +288 -282
- package/.opencode/commands/verify.md +269 -221
- package/.roo/commands/dev.md +346 -341
- package/.roo/commands/plan.md +567 -522
- package/.roo/commands/premerge.md +187 -177
- package/.roo/commands/research.md +43 -43
- package/.roo/commands/review.md +449 -443
- package/.roo/commands/rollback.md +722 -722
- package/.roo/commands/ship.md +213 -165
- package/.roo/commands/sonarcloud.md +150 -150
- package/.roo/commands/status.md +91 -49
- package/.roo/commands/validate.md +289 -283
- package/.roo/commands/verify.md +270 -222
- package/AGENTS.md +272 -175
- package/CLAUDE.md +110 -100
- package/README.md +429 -416
- package/bin/forge-cmd.js +317 -313
- package/bin/forge-preflight.js +322 -309
- package/bin/forge.js +4765 -4303
- package/docs/AGENT_INSTALL_PROMPT.md +342 -342
- package/docs/BEADS_GITHUB_SYNC.md +251 -251
- package/docs/ENHANCED_ONBOARDING.md +612 -602
- package/docs/EXAMPLES.md +482 -482
- package/docs/GREPTILE_SETUP.md +400 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +106 -106
- package/docs/ROADMAP.md +359 -359
- package/docs/SETUP.md +663 -631
- package/docs/TOOLCHAIN.md +653 -630
- package/docs/VALIDATION.md +363 -363
- package/install.sh +40 -1056
- package/lefthook.yml +50 -39
- package/lib/agents/README.md +198 -198
- package/lib/agents/claude.plugin.json +28 -28
- package/lib/agents/cline.plugin.json +22 -22
- package/lib/agents/codex.plugin.json +19 -19
- package/lib/agents/copilot.plugin.json +24 -24
- package/lib/agents/cursor.plugin.json +25 -25
- package/lib/agents/kilocode.plugin.json +22 -22
- package/lib/agents/opencode.plugin.json +20 -20
- package/lib/agents/roo.plugin.json +23 -23
- package/lib/agents-config.js +2112 -2112
- package/lib/beads-health-check.js +143 -0
- package/lib/beads-setup.js +341 -0
- package/lib/beads-sync-scaffold.js +260 -0
- package/lib/commands/_registry.js +134 -0
- package/lib/commands/clean.js +181 -0
- package/lib/commands/dev.js +571 -513
- package/lib/commands/plan.js +692 -692
- package/lib/commands/push.js +196 -0
- package/lib/commands/recommend.js +119 -119
- package/lib/commands/ship.js +377 -377
- package/lib/commands/status.js +378 -378
- package/lib/commands/sync.js +55 -0
- package/lib/commands/team.js +37 -0
- package/lib/commands/test.js +207 -0
- package/lib/commands/validate.js +602 -602
- package/lib/commands/worktree.js +310 -0
- package/lib/context-merge.js +359 -359
- package/lib/dep-guard/analyzer.js +294 -294
- package/lib/dep-guard/behavior-detector.js +98 -98
- package/lib/dep-guard/contract-detector.js +162 -162
- package/lib/dep-guard/import-detector.js +498 -498
- package/lib/dep-guard/path-utils.js +13 -13
- package/lib/dep-guard/rubric.js +120 -120
- package/lib/dep-guard/task-parser.js +318 -318
- package/lib/detect-agent.js +191 -191
- package/lib/detect-worktree.js +47 -47
- package/lib/docs-command.js +51 -0
- package/lib/docs-copy.js +50 -0
- package/lib/file-hash.js +26 -26
- package/lib/freshness-token.js +148 -0
- package/lib/greptile-match.js +80 -0
- package/lib/husky-migration.js +450 -0
- package/lib/lefthook-check.js +65 -0
- package/lib/pat-setup.js +207 -0
- package/lib/plugin-catalog.js +350 -350
- package/lib/plugin-manager.js +166 -166
- package/lib/plugin-recommender.js +141 -141
- package/lib/project-discovery.js +491 -491
- package/lib/reset.js +309 -0
- package/lib/setup-action-log.js +139 -139
- package/lib/setup-summary-renderer.js +106 -106
- package/lib/setup-utils.js +96 -0
- package/lib/setup.js +192 -192
- package/lib/smart-merge.js +64 -0
- package/lib/symlink-utils.js +81 -0
- package/lib/task-ownership.js +117 -0
- package/lib/workflow-profiles.js +197 -197
- package/package.json +131 -128
- package/scripts/beads-context.sh +426 -0
- package/scripts/beads-context.test.js +567 -0
- package/scripts/behavioral-judge.sh +378 -0
- package/scripts/benchmark.js +85 -0
- package/scripts/branch-protection.js +183 -0
- package/scripts/check-agents.js +172 -0
- package/scripts/check-forge-token.js +98 -0
- package/scripts/commitlint.js +42 -0
- package/scripts/conflict-detect.sh +323 -0
- package/scripts/dep-guard-analyze.js +71 -0
- package/scripts/dep-guard.sh +789 -0
- package/scripts/eval_win.py +249 -0
- package/scripts/file-index.sh +493 -0
- package/scripts/forge-team/index.sh +86 -0
- package/scripts/forge-team/lib/agent-prompt.sh +52 -0
- package/scripts/forge-team/lib/claim.sh +256 -0
- package/scripts/forge-team/lib/dashboard.sh +341 -0
- package/scripts/forge-team/lib/epic.sh +332 -0
- package/scripts/forge-team/lib/hooks.sh +253 -0
- package/scripts/forge-team/lib/identity.sh +235 -0
- package/scripts/forge-team/lib/sync-github.sh +317 -0
- package/scripts/forge-team/lib/verify.sh +284 -0
- package/scripts/forge-team/lib/workload.sh +296 -0
- package/scripts/forge-team/tests/agent-prompt.test.sh +72 -0
- package/scripts/forge-team/tests/claim.test.sh +179 -0
- package/scripts/forge-team/tests/dashboard.test.sh +170 -0
- package/scripts/forge-team/tests/dispatcher.test.sh +79 -0
- package/scripts/forge-team/tests/epic.test.sh +176 -0
- package/scripts/forge-team/tests/hooks.test.sh +239 -0
- package/scripts/forge-team/tests/identity.test.sh +176 -0
- package/scripts/forge-team/tests/integration.test.sh +371 -0
- package/scripts/forge-team/tests/sync-github.test.sh +209 -0
- package/scripts/forge-team/tests/verify.test.sh +314 -0
- package/scripts/forge-team/tests/workflow-integration.test.sh +43 -0
- package/scripts/forge-team/tests/workload.test.sh +209 -0
- package/scripts/github-beads-sync/comment.mjs +64 -0
- package/scripts/github-beads-sync/config.mjs +148 -0
- package/scripts/github-beads-sync/github-api.mjs +131 -0
- package/scripts/github-beads-sync/index.mjs +332 -0
- package/scripts/github-beads-sync/label-mapper.mjs +54 -0
- package/scripts/github-beads-sync/mapping.mjs +78 -0
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +31 -0
- package/scripts/github-beads-sync/reverse-sync.mjs +138 -0
- package/scripts/github-beads-sync/run-bd.mjs +159 -0
- package/scripts/github-beads-sync/sanitize.mjs +121 -0
- package/scripts/github-beads-sync.config.json +26 -0
- package/scripts/improve-command.js +375 -0
- package/scripts/lib/eval-runner.js +268 -0
- package/scripts/lib/eval-schema.js +135 -0
- package/scripts/lib/eval-storage.js +78 -0
- package/scripts/lib/grading.js +203 -0
- package/scripts/lib/jsonl-lock.sh +48 -0
- package/scripts/lib/sanitize.sh +116 -0
- package/scripts/lib/transcript-parser.js +63 -0
- package/scripts/lint.js +47 -0
- package/scripts/migrate-to-bun-test.js +412 -0
- package/scripts/pr-coordinator.sh +706 -0
- package/scripts/run-command-eval.js +236 -0
- package/scripts/smart-status.sh +809 -0
- package/scripts/sync-commands.js +571 -0
- package/scripts/sync-utils.sh +455 -0
- package/scripts/test-dashboard.js +123 -0
- package/scripts/test.js +46 -0
- package/scripts/validate.sh +94 -0
- package/skills/parallel-deep-research/SKILL.md +108 -108
- package/skills/parallel-deep-research/evals/README.md +27 -27
- package/skills/parallel-deep-research/evals/evals.json +62 -62
- package/skills/sonarcloud-analysis/SKILL.md +171 -171
- package/skills/sonarcloud-analysis/evals/README.md +27 -27
- package/skills/sonarcloud-analysis/evals/evals.json +50 -50
- package/skills/sonarcloud-analysis/references/api-reference.md +466 -466
- package/.cursor/hooks/state/continual-learning-index.json +0 -19
- package/.cursor/hooks/state/continual-learning.json +0 -8
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# sync-utils.sh โ Shared utilities for multi-dev-awareness sync scripts.
|
|
3
|
+
#
|
|
4
|
+
# Source this file or execute directly with a subcommand.
|
|
5
|
+
# Cross-platform: works on Windows (Git Bash), macOS, and Linux.
|
|
6
|
+
# OWASP A03: All identity strings validated against strict allowlist regex.
|
|
7
|
+
# OWASP A03: All config values sanitized before use (no injection vectors).
|
|
8
|
+
|
|
9
|
+
# Only set errexit/pipefail when run as a script, not when sourced
|
|
10
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Source shared sanitize library
|
|
15
|
+
_SU_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
source "$_SU_SCRIPT_DIR/lib/sanitize.sh"
|
|
17
|
+
|
|
18
|
+
# โโ Session Identity โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
19
|
+
|
|
20
|
+
# Validates a session identity string against the allowlist regex.
|
|
21
|
+
# Returns 0 if valid, 1 if invalid.
|
|
22
|
+
# Usage: validate_session_identity "user@hostname"
|
|
23
|
+
validate_session_identity() {
|
|
24
|
+
local identity="${1:-}"
|
|
25
|
+
if [[ -z "$identity" ]]; then
|
|
26
|
+
return 1
|
|
27
|
+
fi
|
|
28
|
+
# OWASP A03: strict allowlist โ only alphanumeric, dot, underscore, @, +, hyphen
|
|
29
|
+
if [[ "$identity" =~ ^[a-zA-Z0-9._@+-]+$ ]]; then
|
|
30
|
+
return 0
|
|
31
|
+
fi
|
|
32
|
+
return 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Returns a session identity string in the format: <user>@<hostname>
|
|
36
|
+
# Uses git config user.email if available, falls back to git config user.name.
|
|
37
|
+
# Validates the result against OWASP A03 allowlist regex.
|
|
38
|
+
# Outputs the identity via printf (not echo) and returns 0 on success, 1 on failure.
|
|
39
|
+
# Usage: identity="$(get_session_identity)"
|
|
40
|
+
get_session_identity() {
|
|
41
|
+
local user_part=""
|
|
42
|
+
local host_part=""
|
|
43
|
+
|
|
44
|
+
# Try email first, fall back to user.name
|
|
45
|
+
user_part="$(git config user.email 2>/dev/null)" || true
|
|
46
|
+
if [[ -z "$user_part" ]]; then
|
|
47
|
+
user_part="$(git config user.name 2>/dev/null)" || true
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
if [[ -z "$user_part" ]]; then
|
|
51
|
+
echo "Error: No git user.email or user.name configured" >&2
|
|
52
|
+
return 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# Get short hostname โ cross-platform:
|
|
56
|
+
# Linux/macOS: hostname -s works
|
|
57
|
+
# Windows Git Bash: hostname -s may fail, fall back to hostname + strip domain
|
|
58
|
+
host_part="$(hostname -s 2>/dev/null)" || true
|
|
59
|
+
if [[ -z "$host_part" ]]; then
|
|
60
|
+
host_part="$(hostname 2>/dev/null)" || true
|
|
61
|
+
# Strip domain suffix if present (e.g., "host.domain.com" -> "host")
|
|
62
|
+
host_part="${host_part%%.*}"
|
|
63
|
+
fi
|
|
64
|
+
if [[ -z "$host_part" ]]; then
|
|
65
|
+
echo "Error: Could not determine hostname" >&2
|
|
66
|
+
return 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Replace spaces with hyphens (common in git user.name like "John Doe")
|
|
70
|
+
user_part="${user_part// /-}"
|
|
71
|
+
host_part="${host_part// /-}"
|
|
72
|
+
local identity="${user_part}@${host_part}"
|
|
73
|
+
|
|
74
|
+
# OWASP A03: validate before returning
|
|
75
|
+
if ! validate_session_identity "$identity"; then
|
|
76
|
+
echo "Error: Session identity contains invalid characters: $identity" >&2
|
|
77
|
+
return 1
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
printf '%s' "$identity"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# โโ Config Reading โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
84
|
+
|
|
85
|
+
# Read a key from .beads/config.json (JSON format).
|
|
86
|
+
# Usage: value="$(_read_config_json "/path/to/repo" "sync_branch")"
|
|
87
|
+
# Returns empty string if file missing or key absent.
|
|
88
|
+
_read_config_json() {
|
|
89
|
+
local repo_dir="$1"
|
|
90
|
+
local key="$2"
|
|
91
|
+
local config_file="$repo_dir/.beads/config.json"
|
|
92
|
+
if [ -f "$config_file" ] && command -v jq &>/dev/null; then
|
|
93
|
+
local raw
|
|
94
|
+
raw="$(jq -r --arg k "$key" '.[$k] // empty' -- "$config_file" 2>/dev/null)" || true
|
|
95
|
+
if [ -n "$raw" ]; then
|
|
96
|
+
sanitize_config_value "$raw"
|
|
97
|
+
return 0
|
|
98
|
+
fi
|
|
99
|
+
fi
|
|
100
|
+
printf ''
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Read a key from .beads/config.yaml (YAML format, simple grep-based).
|
|
104
|
+
# Only supports top-level scalar keys (no nesting).
|
|
105
|
+
# Usage: value="$(_read_config_yaml "/path/to/repo" "sync-branch")"
|
|
106
|
+
# Returns empty string if file missing or key absent.
|
|
107
|
+
_read_config_yaml() {
|
|
108
|
+
local repo_dir="$1"
|
|
109
|
+
local key="$2"
|
|
110
|
+
local config_file="$repo_dir/.beads/config.yaml"
|
|
111
|
+
if [ -f "$config_file" ]; then
|
|
112
|
+
local raw
|
|
113
|
+
# Escape regex metacharacters in key for safe use in grep/sed
|
|
114
|
+
local escaped_key
|
|
115
|
+
escaped_key="$(printf '%s' "$key" | sed 's/[][\\.^$*+?(){}|/]/\\&/g')"
|
|
116
|
+
# Match: key: "value" or key: value (strip quotes)
|
|
117
|
+
raw="$(grep -E "^${escaped_key}:" -- "$config_file" 2>/dev/null | head -1 | sed -e "s/^${escaped_key}:[[:space:]]*//" -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//")" || true
|
|
118
|
+
if [ -n "$raw" ]; then
|
|
119
|
+
sanitize_config_value "$raw"
|
|
120
|
+
return 0
|
|
121
|
+
fi
|
|
122
|
+
fi
|
|
123
|
+
printf ''
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Read a sync config value with fallback: config.json > config.yaml.
|
|
127
|
+
# Supports both JSON key (underscore) and YAML key (hyphen) conventions.
|
|
128
|
+
# Usage: value="$(_read_sync_config "/path/to/repo" "sync_branch" "sync-branch")"
|
|
129
|
+
_read_sync_config() {
|
|
130
|
+
local repo_dir="$1"
|
|
131
|
+
local json_key="$2"
|
|
132
|
+
local yaml_key="$3"
|
|
133
|
+
local value
|
|
134
|
+
|
|
135
|
+
# Try JSON config first
|
|
136
|
+
value="$(_read_config_json "$repo_dir" "$json_key")"
|
|
137
|
+
if [ -n "$value" ]; then
|
|
138
|
+
printf '%s' "$value"
|
|
139
|
+
return 0
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# Try YAML config
|
|
143
|
+
value="$(_read_config_yaml "$repo_dir" "$yaml_key")"
|
|
144
|
+
if [ -n "$value" ]; then
|
|
145
|
+
printf '%s' "$value"
|
|
146
|
+
return 0
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
printf ''
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# โโ Sync Branch Detection โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
153
|
+
|
|
154
|
+
# Detect the sync branch with a fallback chain:
|
|
155
|
+
# 1. Config file (.beads/config.json sync_branch or .beads/config.yaml sync-branch)
|
|
156
|
+
# 2. Environment variable BD_SYNC_BRANCH
|
|
157
|
+
# 3. git symbolic-ref refs/remotes/<remote>/HEAD
|
|
158
|
+
# 4. Try 'main' branch on remote
|
|
159
|
+
# 5. Try 'master' branch on remote
|
|
160
|
+
#
|
|
161
|
+
# Usage: branch="$(get_sync_branch "/path/to/repo")"
|
|
162
|
+
# Arguments:
|
|
163
|
+
# $1 โ repo directory (optional, defaults to current directory)
|
|
164
|
+
get_sync_branch() {
|
|
165
|
+
local repo_dir="${1:-.}"
|
|
166
|
+
local branch
|
|
167
|
+
|
|
168
|
+
# 1. Config file takes priority
|
|
169
|
+
branch="$(_read_sync_config "$repo_dir" "sync_branch" "sync-branch")"
|
|
170
|
+
if [ -n "$branch" ]; then
|
|
171
|
+
printf '%s' "$branch"
|
|
172
|
+
return 0
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
# 2. Environment variable
|
|
176
|
+
if [ -n "${BD_SYNC_BRANCH:-}" ]; then
|
|
177
|
+
printf '%s' "$BD_SYNC_BRANCH"
|
|
178
|
+
return 0
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# Determine which remote to check (respects sync_remote config)
|
|
182
|
+
local remote
|
|
183
|
+
remote="$(get_sync_remote "$repo_dir")"
|
|
184
|
+
|
|
185
|
+
# 3. git symbolic-ref refs/remotes/<remote>/HEAD
|
|
186
|
+
# Note: symbolic-ref accepts -- before ref names, but rev-parse --verify
|
|
187
|
+
# does NOT โ after --, rev-parse treats arguments as paths, not revisions.
|
|
188
|
+
branch="$(git -C "$repo_dir" symbolic-ref "refs/remotes/${remote}/HEAD" 2>/dev/null)" || true
|
|
189
|
+
if [ -n "$branch" ]; then
|
|
190
|
+
# Strip refs/remotes/<remote>/ prefix
|
|
191
|
+
branch="${branch#refs/remotes/${remote}/}"
|
|
192
|
+
printf '%s' "$branch"
|
|
193
|
+
return 0
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# 4. Try 'main' โ check if remote has it
|
|
197
|
+
# Note: no -- before ref; rev-parse --verify treats post--- args as paths
|
|
198
|
+
if git -C "$repo_dir" rev-parse --verify "refs/remotes/${remote}/main" &>/dev/null; then
|
|
199
|
+
printf '%s' "main"
|
|
200
|
+
return 0
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# 5. Try 'master'
|
|
204
|
+
if git -C "$repo_dir" rev-parse --verify "refs/remotes/${remote}/master" &>/dev/null; then
|
|
205
|
+
printf '%s' "master"
|
|
206
|
+
return 0
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
# Last resort: return 'main' as default
|
|
210
|
+
printf '%s' "main"
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# โโ Sync Remote Detection โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
214
|
+
|
|
215
|
+
# Detect the sync remote with a fallback chain:
|
|
216
|
+
# 1. Config file (.beads/config.json sync_remote or .beads/config.yaml sync-remote)
|
|
217
|
+
# 2. Environment variable BD_SYNC_REMOTE
|
|
218
|
+
# 3. Detect 'upstream' remote (fork-based workflow)
|
|
219
|
+
# 4. Default to 'origin'
|
|
220
|
+
#
|
|
221
|
+
# Usage: remote="$(get_sync_remote "/path/to/repo")"
|
|
222
|
+
# Arguments:
|
|
223
|
+
# $1 โ repo directory (optional, defaults to current directory)
|
|
224
|
+
get_sync_remote() {
|
|
225
|
+
local repo_dir="${1:-.}"
|
|
226
|
+
local remote
|
|
227
|
+
|
|
228
|
+
# 1. Config file takes priority
|
|
229
|
+
remote="$(_read_sync_config "$repo_dir" "sync_remote" "sync-remote")"
|
|
230
|
+
if [ -n "$remote" ]; then
|
|
231
|
+
printf '%s' "$remote"
|
|
232
|
+
return 0
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# 2. Environment variable
|
|
236
|
+
if [ -n "${BD_SYNC_REMOTE:-}" ]; then
|
|
237
|
+
printf '%s' "$BD_SYNC_REMOTE"
|
|
238
|
+
return 0
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# 3. Detect 'upstream' remote (common in fork-based workflows)
|
|
242
|
+
if git -C "$repo_dir" remote get-url -- upstream &>/dev/null; then
|
|
243
|
+
printf '%s' "upstream"
|
|
244
|
+
return 0
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
# 4. Default to 'origin'
|
|
248
|
+
printf '%s' "origin"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
# โโ Auto Sync โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
252
|
+
|
|
253
|
+
# Staleness threshold in seconds (15 minutes)
|
|
254
|
+
_SYNC_STALENESS_THRESHOLD=900
|
|
255
|
+
|
|
256
|
+
# auto_sync [repo_dir]
|
|
257
|
+
# Runs `bd sync` to pull/push latest beads state.
|
|
258
|
+
# On failure: warns but continues (non-blocking, always returns 0).
|
|
259
|
+
# On success: records Unix epoch timestamp to .beads/.last-sync
|
|
260
|
+
# and updates file index from all in-progress issues' task files.
|
|
261
|
+
#
|
|
262
|
+
# _run_sync โ execute beads sync (pull + push).
|
|
263
|
+
# If BD_SYNC_CMD is set, run it as a single command (for testing).
|
|
264
|
+
# Otherwise, run the default two-step pull+push sequence.
|
|
265
|
+
# shellcheck disable=SC2086
|
|
266
|
+
_run_sync() {
|
|
267
|
+
if [[ -n "${BD_SYNC_CMD:-}" ]]; then
|
|
268
|
+
$BD_SYNC_CMD
|
|
269
|
+
else
|
|
270
|
+
bd dolt pull && bd dolt push
|
|
271
|
+
fi
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
# Environment overrides (for testing):
|
|
275
|
+
# BD_SYNC_CMD โ single command to run instead of default pull+push (e.g. "echo mock-sync")
|
|
276
|
+
# FILE_INDEX_ROOT โ root directory for file-index.sh (default: repo_dir)
|
|
277
|
+
auto_sync() {
|
|
278
|
+
local repo_dir="${1:-.}"
|
|
279
|
+
local last_sync_file="$repo_dir/.beads/.last-sync"
|
|
280
|
+
|
|
281
|
+
# Ensure .beads directory exists
|
|
282
|
+
mkdir -p "$repo_dir/.beads"
|
|
283
|
+
|
|
284
|
+
# Run sync โ helper function avoids eval while supporting compound default
|
|
285
|
+
if _run_sync >/dev/null 2>&1; then
|
|
286
|
+
# Success: record timestamp
|
|
287
|
+
date +%s > "$last_sync_file"
|
|
288
|
+
|
|
289
|
+
# Update file index from in-progress issues' task files
|
|
290
|
+
_auto_sync_update_file_index "$repo_dir" || {
|
|
291
|
+
echo "Warning: file index update failed after sync" >&2
|
|
292
|
+
}
|
|
293
|
+
else
|
|
294
|
+
# Failure: warn but continue (non-blocking)
|
|
295
|
+
local last_ts="unknown"
|
|
296
|
+
if [[ -f "$last_sync_file" ]]; then
|
|
297
|
+
last_ts="$(cat "$last_sync_file")"
|
|
298
|
+
if [[ "$last_ts" =~ ^[0-9]+$ ]]; then
|
|
299
|
+
local mins=$(( ($(date +%s) - last_ts) / 60 ))
|
|
300
|
+
last_ts="${mins}m ago"
|
|
301
|
+
fi
|
|
302
|
+
fi
|
|
303
|
+
echo "Warning: sync failed, working with local data (last sync: $last_ts)" >&2
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
return 0
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# _auto_sync_update_file_index [repo_dir]
|
|
310
|
+
# After a successful sync, iterate over in-progress issues and update the file index.
|
|
311
|
+
# Uses file_index_update_from_tasks from file-index.sh.
|
|
312
|
+
_auto_sync_update_file_index() {
|
|
313
|
+
local repo_dir="${1:-.}"
|
|
314
|
+
|
|
315
|
+
# Source file-index.sh if file_index_update_from_tasks is not defined
|
|
316
|
+
if ! command -v file_index_update_from_tasks &>/dev/null; then
|
|
317
|
+
local script_dir
|
|
318
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
319
|
+
if [[ -f "$script_dir/file-index.sh" ]]; then
|
|
320
|
+
source "$script_dir/file-index.sh"
|
|
321
|
+
else
|
|
322
|
+
return 0
|
|
323
|
+
fi
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# Export FILE_INDEX_ROOT so file-index.sh uses the right directory
|
|
327
|
+
export FILE_INDEX_ROOT="${FILE_INDEX_ROOT:-$repo_dir}"
|
|
328
|
+
|
|
329
|
+
# Find in-progress issues by scanning .beads/issues.jsonl
|
|
330
|
+
local issues_file="$repo_dir/.beads/issues.jsonl"
|
|
331
|
+
if [[ ! -f "$issues_file" ]] || [[ ! -s "$issues_file" ]]; then
|
|
332
|
+
return 0
|
|
333
|
+
fi
|
|
334
|
+
|
|
335
|
+
# Check jq is available
|
|
336
|
+
if ! command -v jq &>/dev/null; then
|
|
337
|
+
echo "Warning: jq not found โ file index update skipped" >&2
|
|
338
|
+
return 1
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
# Extract in-progress issue IDs (LWW resolution: group by id, take last, filter in_progress)
|
|
342
|
+
local issue_ids
|
|
343
|
+
issue_ids="$(jq -s -r '
|
|
344
|
+
sort_by(.id)
|
|
345
|
+
| group_by(.id)
|
|
346
|
+
| map(sort_by(.updated_at) | last)
|
|
347
|
+
| map(select(.status == "in_progress"))
|
|
348
|
+
| .[].id
|
|
349
|
+
' "$issues_file" 2>/dev/null)" || true
|
|
350
|
+
|
|
351
|
+
if [[ -z "$issue_ids" ]]; then
|
|
352
|
+
return 0
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
# For each in-progress issue, find its task file and update the file index
|
|
356
|
+
local issue_id
|
|
357
|
+
while IFS= read -r issue_id; do
|
|
358
|
+
if [[ -z "$issue_id" ]]; then
|
|
359
|
+
continue
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
# Look for this issue's task file
|
|
363
|
+
# 1. Try beads design metadata (set by beads-context.sh set-design)
|
|
364
|
+
local task_file=""
|
|
365
|
+
local design_meta
|
|
366
|
+
design_meta="$(bd show "$issue_id" 2>/dev/null | grep -A1 'DESIGN' | tail -1 | sed 's/.*| //')" || true
|
|
367
|
+
# OWASP A04: reject paths that escape the repo root
|
|
368
|
+
case "$design_meta" in
|
|
369
|
+
*../*|..*) design_meta="" ;;
|
|
370
|
+
esac
|
|
371
|
+
if [[ -n "$design_meta" ]] && [[ -f "$repo_dir/$design_meta" ]]; then
|
|
372
|
+
task_file="$repo_dir/$design_meta"
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
# 2. Fall back to known locations with issue-specific naming
|
|
376
|
+
if [[ -z "$task_file" ]]; then
|
|
377
|
+
for candidate in \
|
|
378
|
+
"$repo_dir/.beads/tasks/${issue_id}.md" \
|
|
379
|
+
"$repo_dir/.beads/tasks/${issue_id}-tasks.md"; do
|
|
380
|
+
if [[ -f "$candidate" ]]; then
|
|
381
|
+
task_file="$candidate"
|
|
382
|
+
break
|
|
383
|
+
fi
|
|
384
|
+
done
|
|
385
|
+
fi
|
|
386
|
+
|
|
387
|
+
# Update file index (file_index_update_from_tasks handles missing task files gracefully)
|
|
388
|
+
file_index_update_from_tasks "$issue_id" "$task_file" "in_progress" 2>/dev/null || true
|
|
389
|
+
done <<< "$issue_ids"
|
|
390
|
+
|
|
391
|
+
# Tombstone closed issues: find indexed entries no longer in_progress
|
|
392
|
+
local indexed_ids
|
|
393
|
+
indexed_ids="$(file_index_read 2>/dev/null | jq -r '.[].issue_id' 2>/dev/null)" || true
|
|
394
|
+
if [[ -n "$indexed_ids" ]]; then
|
|
395
|
+
while IFS= read -r indexed_id; do
|
|
396
|
+
[[ -z "$indexed_id" ]] && continue
|
|
397
|
+
# If this issue is not in the in_progress list, tombstone it
|
|
398
|
+
if ! printf '%s' "$issue_ids" | grep -qF "$indexed_id"; then
|
|
399
|
+
file_index_remove "$indexed_id" 2>/dev/null || true
|
|
400
|
+
fi
|
|
401
|
+
done <<< "$indexed_ids"
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
return 0
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
# check_sync_staleness [repo_dir]
|
|
408
|
+
# Reads .beads/.last-sync and warns if the last sync is older than 15 minutes.
|
|
409
|
+
# Always returns 0 (non-blocking).
|
|
410
|
+
# Outputs:
|
|
411
|
+
# - Nothing if sync is fresh (<= 900 seconds)
|
|
412
|
+
# - Warning to stderr if stale (> 900 seconds)
|
|
413
|
+
# - Warning to stderr if .last-sync is missing (never synced)
|
|
414
|
+
check_sync_staleness() {
|
|
415
|
+
local repo_dir="${1:-.}"
|
|
416
|
+
local last_sync_file="$repo_dir/.beads/.last-sync"
|
|
417
|
+
|
|
418
|
+
if [[ ! -f "$last_sync_file" ]]; then
|
|
419
|
+
echo "Warning: beads sync has never been run (no .last-sync record)" >&2
|
|
420
|
+
return 0
|
|
421
|
+
fi
|
|
422
|
+
|
|
423
|
+
local last_ts
|
|
424
|
+
last_ts="$(cat "$last_sync_file")"
|
|
425
|
+
|
|
426
|
+
# Validate it's a number
|
|
427
|
+
if [[ ! "$last_ts" =~ ^[0-9]+$ ]]; then
|
|
428
|
+
echo "Warning: .last-sync contains invalid timestamp" >&2
|
|
429
|
+
return 0
|
|
430
|
+
fi
|
|
431
|
+
|
|
432
|
+
local now
|
|
433
|
+
now="$(date +%s)"
|
|
434
|
+
local age=$(( now - last_ts ))
|
|
435
|
+
|
|
436
|
+
if [[ "$age" -gt "$_SYNC_STALENESS_THRESHOLD" ]]; then
|
|
437
|
+
local minutes=$(( age / 60 ))
|
|
438
|
+
echo "Warning: beads data is stale (last sync: ${minutes}m ago, threshold: 15m)" >&2
|
|
439
|
+
fi
|
|
440
|
+
|
|
441
|
+
return 0
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
# โโ CLI Dispatcher โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
445
|
+
|
|
446
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
447
|
+
case "${1:-}" in
|
|
448
|
+
auto-sync) shift; auto_sync "$@" ;;
|
|
449
|
+
check-staleness) shift; check_sync_staleness "$@" ;;
|
|
450
|
+
identity) get_session_identity ;;
|
|
451
|
+
sync-branch) shift; get_sync_branch "$@" ;;
|
|
452
|
+
sync-remote) shift; get_sync_remote "$@" ;;
|
|
453
|
+
*) echo "Usage: sync-utils.sh <auto-sync|check-staleness|identity|sync-branch|sync-remote>" >&2; exit 1 ;;
|
|
454
|
+
esac
|
|
455
|
+
fi
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test Quality Dashboard โ collects test metrics into a JSON summary.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/test-dashboard.js # Human-readable output
|
|
8
|
+
* node scripts/test-dashboard.js --json # JSON output (for CI/tests)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('node:fs');
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
const { execFileSync } = require('node:child_process');
|
|
14
|
+
|
|
15
|
+
const rootDir = path.join(__dirname, '..');
|
|
16
|
+
|
|
17
|
+
function getTestCount() {
|
|
18
|
+
// Count test cases by scanning test files for test() calls
|
|
19
|
+
// This avoids running the full test suite (which would be slow/recursive)
|
|
20
|
+
const testDirs = [
|
|
21
|
+
path.join(rootDir, 'test'),
|
|
22
|
+
path.join(rootDir, 'test-env'),
|
|
23
|
+
path.join(rootDir, 'packages', 'skills', 'test')
|
|
24
|
+
];
|
|
25
|
+
let count = 0;
|
|
26
|
+
for (const dir of testDirs) {
|
|
27
|
+
if (!fs.existsSync(dir)) continue;
|
|
28
|
+
const files = fs.readdirSync(dir, { recursive: true });
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
const filePath = path.join(dir, String(file));
|
|
31
|
+
if (!filePath.endsWith('.test.js') && !filePath.endsWith('.spec.js')) continue;
|
|
32
|
+
if (!fs.statSync(filePath).isFile()) continue;
|
|
33
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
34
|
+
// Count test() and it() calls (excluding test.skip)
|
|
35
|
+
const testMatches = content.match(/\btest\s*\(/g) || [];
|
|
36
|
+
const itMatches = content.match(/\bit\s*\(/g) || [];
|
|
37
|
+
const skipMatches = content.match(/\btest\.skip\s*\(/g) || [];
|
|
38
|
+
count += testMatches.length + itMatches.length - skipMatches.length;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return count;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getCoverageThreshold() {
|
|
45
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8'));
|
|
46
|
+
return pkg.c8 ? pkg.c8.lines : 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getEslintWarnings() {
|
|
50
|
+
// We enforce --max-warnings 0 in pre-push hooks (lefthook.yml),
|
|
51
|
+
// so this is always 0 when code passes CI. Report 0 to avoid
|
|
52
|
+
// slow ESLint execution during dashboard generation.
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getSkippedTestCount() {
|
|
57
|
+
try {
|
|
58
|
+
const output = execFileSync('grep', ['-r', 'test.skip', 'test/', 'test-env/'], {
|
|
59
|
+
cwd: rootDir,
|
|
60
|
+
encoding: 'utf-8',
|
|
61
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
62
|
+
});
|
|
63
|
+
return output.trim().split('\n').filter(Boolean).length;
|
|
64
|
+
} catch (_e) {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getMutationScore() {
|
|
70
|
+
const reportPath = path.join(rootDir, 'stryker-report', 'mutation.json');
|
|
71
|
+
if (!fs.existsSync(reportPath)) return null;
|
|
72
|
+
try {
|
|
73
|
+
const report = JSON.parse(fs.readFileSync(reportPath, 'utf-8'));
|
|
74
|
+
if (report.schemaVersion) {
|
|
75
|
+
// Stryker v8+ JSON report format
|
|
76
|
+
const files = Object.values(report.files || {});
|
|
77
|
+
let killed = 0;
|
|
78
|
+
let total = 0;
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
for (const mutant of (file.mutants || [])) {
|
|
81
|
+
total++;
|
|
82
|
+
if (mutant.status === 'Killed') killed++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return total > 0 ? Math.round((killed / total) * 100) : null;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
} catch (_e) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const dashboard = {
|
|
94
|
+
testCount: getTestCount(),
|
|
95
|
+
coverageThreshold: getCoverageThreshold(),
|
|
96
|
+
eslintWarnings: getEslintWarnings(),
|
|
97
|
+
skippedTests: getSkippedTestCount(),
|
|
98
|
+
mutationScore: getMutationScore(),
|
|
99
|
+
timestamp: new Date().toISOString()
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const jsonMode = process.argv.includes('--json');
|
|
103
|
+
|
|
104
|
+
if (jsonMode) {
|
|
105
|
+
process.stdout.write(JSON.stringify(dashboard));
|
|
106
|
+
} else {
|
|
107
|
+
console.log('\n Test Quality Dashboard');
|
|
108
|
+
console.log(' =====================\n');
|
|
109
|
+
console.log(` Tests: ${dashboard.testCount}`);
|
|
110
|
+
console.log(` Coverage threshold: ${dashboard.coverageThreshold}%`);
|
|
111
|
+
console.log(` ESLint warnings: ${dashboard.eslintWarnings}`);
|
|
112
|
+
console.log(` Skipped tests: ${dashboard.skippedTests}`);
|
|
113
|
+
console.log(` Mutation score: ${dashboard.mutationScore !== null ? dashboard.mutationScore + '%' : 'N/A (run test:mutation first)'}`);
|
|
114
|
+
console.log(` Generated: ${dashboard.timestamp}`);
|
|
115
|
+
console.log('');
|
|
116
|
+
|
|
117
|
+
// Write results file
|
|
118
|
+
const outputPath = path.join(rootDir, 'test-dashboard.json');
|
|
119
|
+
fs.writeFileSync(outputPath, JSON.stringify(dashboard, null, 2));
|
|
120
|
+
console.log(` Results saved to: test-dashboard.json\n`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = dashboard;
|
package/scripts/test.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Cross-platform test runner for lefthook pre-push hook.
|
|
4
|
+
* Replaces bash-only: bun/pnpm/yarn/npm test detection with if/elif.
|
|
5
|
+
* Works on Windows CMD, PowerShell, macOS, Linux.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawnSync } = require('node:child_process');
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
|
|
11
|
+
// On Windows, package manager CLIs are .cmd files โ shell: true resolves them
|
|
12
|
+
const isWindows = process.platform === 'win32';
|
|
13
|
+
|
|
14
|
+
// Detect package manager from lock files (same priority as forge.js)
|
|
15
|
+
function detectPackageManager() {
|
|
16
|
+
if (fs.existsSync('bun.lockb') || fs.existsSync('bun.lock')) return 'bun';
|
|
17
|
+
if (fs.existsSync('pnpm-lock.yaml')) return 'pnpm';
|
|
18
|
+
if (fs.existsSync('yarn.lock')) return 'yarn';
|
|
19
|
+
return 'npm';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pkgManager = detectPackageManager();
|
|
23
|
+
console.log(`๐งช Running test suite (${pkgManager} test)...`);
|
|
24
|
+
|
|
25
|
+
// Use 'run test' to invoke the package.json script (which may include --timeout flags)
|
|
26
|
+
// 'bun test' is a built-in that ignores package.json scripts
|
|
27
|
+
const result = spawnSync(pkgManager, ['run', 'test'], { stdio: 'inherit', shell: isWindows });
|
|
28
|
+
|
|
29
|
+
if (result.error) {
|
|
30
|
+
console.error('');
|
|
31
|
+
console.error(`โ Failed to run ${pkgManager} test: ${result.error.message}`);
|
|
32
|
+
console.error(` Is '${pkgManager}' installed and on PATH?`);
|
|
33
|
+
console.error('');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (result.status !== 0) {
|
|
38
|
+
console.error('');
|
|
39
|
+
console.error('โ Tests failed. Fix them before pushing.');
|
|
40
|
+
console.error('');
|
|
41
|
+
console.error('Fix the failing checks. Do not bypass hooks.');
|
|
42
|
+
console.error('');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log('โ
All tests passed');
|