@boyingliu01/xp-gate 0.15.1 ā 0.15.3
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/bin/xp-gate.js +24 -0
- package/hooks/pre-commit +24 -2
- package/hooks/pre-push +204 -14
- package/lib/doctor.js +50 -0
- package/lib/init.js +91 -0
- package/lib/language-tools.js +833 -0
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +2 -2
package/bin/xp-gate.js
CHANGED
|
@@ -211,6 +211,30 @@ const COMMANDS = {
|
|
|
211
211
|
handleSprintAudit(subargs).then(code => process.exit(code));
|
|
212
212
|
},
|
|
213
213
|
usage: 'xp-gate sprint-audit [--dir <path>] [--json]'
|
|
214
|
+
},
|
|
215
|
+
'detect-languages': {
|
|
216
|
+
description: 'Detect project languages and generate .xp-gate-config.json',
|
|
217
|
+
run: subargs => {
|
|
218
|
+
const { handleDetectLanguages } = require('../lib/language-tools.js');
|
|
219
|
+
handleDetectLanguages(subargs).then(code => process.exit(code));
|
|
220
|
+
},
|
|
221
|
+
usage: 'xp-gate detect-languages [--json] [--write]'
|
|
222
|
+
},
|
|
223
|
+
'check-tools': {
|
|
224
|
+
description: 'Check quality gate tool availability per language',
|
|
225
|
+
run: subargs => {
|
|
226
|
+
const { handleCheckTools } = require('../lib/language-tools.js');
|
|
227
|
+
handleCheckTools(subargs).then(code => process.exit(code));
|
|
228
|
+
},
|
|
229
|
+
usage: 'xp-gate check-tools [--languages <langs>] [--json]'
|
|
230
|
+
},
|
|
231
|
+
'install-tools': {
|
|
232
|
+
description: 'Install missing quality gate tools for detected languages',
|
|
233
|
+
run: subargs => {
|
|
234
|
+
const { handleInstallTools } = require('../lib/language-tools.js');
|
|
235
|
+
handleInstallTools(subargs).then(code => process.exit(code));
|
|
236
|
+
},
|
|
237
|
+
usage: 'xp-gate install-tools [--languages <langs>] [--dry-run] [--yes]'
|
|
214
238
|
}
|
|
215
239
|
};
|
|
216
240
|
|
package/hooks/pre-commit
CHANGED
|
@@ -375,7 +375,29 @@ if [ -n "$_RESOLVED_LANG_OVERRIDE" ]; then
|
|
|
375
375
|
echo " š§ LANGUAGE OVERRIDE: $PROJECT_LANG"
|
|
376
376
|
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
377
377
|
echo ""
|
|
378
|
-
|
|
378
|
+
elif [ -f ".xp-gate-config.json" ]; then
|
|
379
|
+
# ---- Read from project config file (v0.15.2+) ----
|
|
380
|
+
# .xp-gate-config.json is generated by `xp-gate init` or `xp-gate detect-languages`
|
|
381
|
+
# It contains detected languages and their tool status.
|
|
382
|
+
_CONFIG_LANGS=$(node -e "try{const c=JSON.parse(require('fs').readFileSync('.xp-gate-config.json','utf8'));console.log(Object.keys(c.languages||{}).join(' '));}catch(e){console.log('');}" 2>/dev/null || echo "")
|
|
383
|
+
if [ -n "$_CONFIG_LANGS" ]; then
|
|
384
|
+
PROJECT_LANGS="$_CONFIG_LANGS"
|
|
385
|
+
PROJECT_LANG=$(echo "$_CONFIG_LANGS" | awk '{print $1}')
|
|
386
|
+
echo ""
|
|
387
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
388
|
+
echo " š LANGUAGES FROM .xp-gate-config.json"
|
|
389
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
390
|
+
echo ""
|
|
391
|
+
echo "Detected languages: $PROJECT_LANGS"
|
|
392
|
+
echo ""
|
|
393
|
+
else
|
|
394
|
+
# Config file exists but no languages detected ā fall through to extension detection
|
|
395
|
+
_CONFIG_LANGS=""
|
|
396
|
+
fi
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
# If no languages from override or config, detect from changed files
|
|
400
|
+
if [ -z "$PROJECT_LANGS" ] && [ -z "$_CONFIG_LANGS" ]; then
|
|
379
401
|
# ---- Extension ā language mapping ----
|
|
380
402
|
# Extracts unique languages from changed file extensions
|
|
381
403
|
PROJECT_LANGS=""
|
|
@@ -676,7 +698,7 @@ CHANGEDFILELIST
|
|
|
676
698
|
done
|
|
677
699
|
echo ""
|
|
678
700
|
fi
|
|
679
|
-
fi
|
|
701
|
+
fi # close language override if/else
|
|
680
702
|
|
|
681
703
|
# Helper: check if a language is in PROJECT_LANGS
|
|
682
704
|
has_project_lang() {
|
package/hooks/pre-push
CHANGED
|
@@ -865,16 +865,11 @@ if ! command -v jq &> /dev/null; then
|
|
|
865
865
|
fi
|
|
866
866
|
|
|
867
867
|
# Validate result file content
|
|
868
|
-
# SHA self-reference paradox fix
|
|
869
|
-
#
|
|
868
|
+
# SHA self-reference paradox fix (Issue #346):
|
|
869
|
+
# The walkthrough file may reference an earlier commit if it was generated before
|
|
870
|
+
# subsequent walkthrough-only commits. We check if the referenced commit is in
|
|
871
|
+
# the current branch's history and if no functional commits occurred after it.
|
|
870
872
|
CURRENT_COMMIT=$(git rev-parse HEAD)
|
|
871
|
-
if git diff-tree --no-commit-id --name-only -r HEAD 2>/dev/null | grep -qx "$RESULT_FILE"; then
|
|
872
|
-
# Result file is part of the latest commit ā compare against the reviewed commit
|
|
873
|
-
EXPECTED_COMMIT=$(git rev-parse HEAD^ 2>/dev/null || echo "unknown")
|
|
874
|
-
else
|
|
875
|
-
# Result file is working-tree only ā compare against current HEAD
|
|
876
|
-
EXPECTED_COMMIT="$CURRENT_COMMIT"
|
|
877
|
-
fi
|
|
878
873
|
|
|
879
874
|
# Check JSON validity
|
|
880
875
|
if ! jq empty "$RESULT_FILE" 2>/dev/null; then
|
|
@@ -906,16 +901,42 @@ if [ "$RESULT_BRANCH" != "$CURRENT_BRANCH" ]; then
|
|
|
906
901
|
echo "Proceeding with commit verification..."
|
|
907
902
|
fi
|
|
908
903
|
|
|
909
|
-
# Commit match check
|
|
910
|
-
if
|
|
904
|
+
# Commit match check (Issue #346: flexible matching to avoid circular dependency)
|
|
905
|
+
# Check if RESULT_COMMIT is in the current branch's history
|
|
906
|
+
if ! git merge-base --is-ancestor "$RESULT_COMMIT" HEAD 2>/dev/null; then
|
|
907
|
+
echo ""
|
|
908
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
909
|
+
echo " ā RESULT FILE INVALID - PUSH BLOCKED"
|
|
910
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
911
|
+
echo ""
|
|
912
|
+
echo "Result file references a commit not in current branch:"
|
|
913
|
+
echo " Referenced: $RESULT_COMMIT"
|
|
914
|
+
echo " Current HEAD: $CURRENT_COMMIT"
|
|
915
|
+
echo ""
|
|
916
|
+
echo "Re-run: /delphi-review --mode code-walkthrough"
|
|
917
|
+
echo ""
|
|
918
|
+
exit 1
|
|
919
|
+
fi
|
|
920
|
+
|
|
921
|
+
# Check if there are any functional commits after RESULT_COMMIT
|
|
922
|
+
# Functional commits are those that are NOT walkthrough-only updates or Gate MW fixes
|
|
923
|
+
FUNCTIONAL_COMMITS_AFTER=$(git log --oneline "$RESULT_COMMIT"..HEAD -- . \
|
|
924
|
+
':!.code-walkthrough-result.json' \
|
|
925
|
+
':!.delphi/code-walkthrough.md' \
|
|
926
|
+
':!githooks/pre-push' \
|
|
927
|
+
':!src/npm-package/hooks/pre-push' 2>/dev/null | wc -l)
|
|
928
|
+
if [ "$FUNCTIONAL_COMMITS_AFTER" -gt 0 ]; then
|
|
911
929
|
echo ""
|
|
912
930
|
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
913
931
|
echo " ā RESULT FILE OUTDATED - PUSH BLOCKED"
|
|
914
932
|
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
915
933
|
echo ""
|
|
916
|
-
echo "
|
|
917
|
-
|
|
918
|
-
|
|
934
|
+
echo "Functional commits found after walkthrough:"
|
|
935
|
+
git log --oneline "$RESULT_COMMIT"..HEAD -- . \
|
|
936
|
+
':!.code-walkthrough-result.json' \
|
|
937
|
+
':!.delphi/code-walkthrough.md' \
|
|
938
|
+
':!githooks/pre-push' \
|
|
939
|
+
':!src/npm-package/hooks/pre-push' 2>/dev/null | head -5
|
|
919
940
|
echo ""
|
|
920
941
|
echo "Re-run: /delphi-review --mode code-walkthrough"
|
|
921
942
|
echo ""
|
|
@@ -961,9 +982,178 @@ if [[ "$CURRENT_TIME" > "$RESULT_EXPIRES" ]]; then
|
|
|
961
982
|
exit 1
|
|
962
983
|
fi
|
|
963
984
|
|
|
985
|
+
# ============================================================================
|
|
986
|
+
# PROVENANCE VALIDATION (Issue #339)
|
|
987
|
+
# Validate that the walkthrough was genuinely produced by Delphi review,
|
|
988
|
+
# not fabricated by an LLM.
|
|
989
|
+
# ============================================================================
|
|
990
|
+
|
|
991
|
+
# Check provenance fields exist
|
|
992
|
+
EXPERTS_COUNT=$(jq -r '.experts // [] | length' "$RESULT_FILE")
|
|
993
|
+
CONSENSUS=$(jq -r '.consensus // 0' "$RESULT_FILE")
|
|
994
|
+
WALKTHROUGH_HASH=$(jq -r '.walkthroughHash // empty' "$RESULT_FILE")
|
|
995
|
+
GENERATED_AT=$(jq -r '.generatedAt // empty' "$RESULT_FILE")
|
|
996
|
+
|
|
997
|
+
# Grace period: configurable via env var (default 30 days)
|
|
998
|
+
GRACE_DAYS="${XP_GATE_MW_GRACE_DAYS:-30}"
|
|
999
|
+
|
|
1000
|
+
# Check if this is a "new" walkthrough (has provenance fields)
|
|
1001
|
+
HAS_PROVENANCE="false"
|
|
1002
|
+
if [ -n "$WALKTHROUGH_HASH" ] && [ -n "$GENERATED_AT" ] && [ "$EXPERTS_COUNT" -ge 3 ]; then
|
|
1003
|
+
HAS_PROVENANCE="true"
|
|
1004
|
+
fi
|
|
1005
|
+
|
|
1006
|
+
if [ "$HAS_PROVENANCE" = "true" ]; then
|
|
1007
|
+
# Full provenance validation
|
|
1008
|
+
|
|
1009
|
+
# 1. Experts count check (minimum 3)
|
|
1010
|
+
if [ "$EXPERTS_COUNT" -lt 3 ]; then
|
|
1011
|
+
echo ""
|
|
1012
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1013
|
+
echo " ā INSUFFICIENT EXPERTS - PUSH BLOCKED"
|
|
1014
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1015
|
+
echo ""
|
|
1016
|
+
echo "Walkthrough requires minimum 3 experts:"
|
|
1017
|
+
echo " Found: $EXPERTS_COUNT experts"
|
|
1018
|
+
echo ""
|
|
1019
|
+
echo "Re-run: /delphi-review --mode code-walkthrough"
|
|
1020
|
+
echo ""
|
|
1021
|
+
exit 1
|
|
1022
|
+
fi
|
|
1023
|
+
|
|
1024
|
+
# 2. Consensus check (ā„90%)
|
|
1025
|
+
# Use integer math: multiply by 10 to avoid bc dependency
|
|
1026
|
+
CONSENSUS_X10=$(echo "$CONSENSUS * 10" | bc -l | cut -d'.' -f1)
|
|
1027
|
+
if [ "$CONSENSUS_X10" -lt 900 ]; then
|
|
1028
|
+
echo ""
|
|
1029
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1030
|
+
echo " ā CONSENSUS TOO LOW - PUSH BLOCKED"
|
|
1031
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1032
|
+
echo ""
|
|
1033
|
+
echo "Walkthrough requires ā„90% consensus:"
|
|
1034
|
+
echo " Found: ${CONSENSUS}%"
|
|
1035
|
+
echo ""
|
|
1036
|
+
echo "Re-run: /delphi-review --mode code-walkthrough"
|
|
1037
|
+
echo ""
|
|
1038
|
+
exit 1
|
|
1039
|
+
fi
|
|
1040
|
+
|
|
1041
|
+
# 3. Walkthrough hash verification
|
|
1042
|
+
WALKTHROUGH_DOC=".delphi/code-walkthrough.md"
|
|
1043
|
+
if [ ! -f "$WALKTHROUGH_DOC" ]; then
|
|
1044
|
+
echo ""
|
|
1045
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1046
|
+
echo " ā WALKTHROUGH DOC MISSING - PUSH BLOCKED"
|
|
1047
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1048
|
+
echo ""
|
|
1049
|
+
echo "Walkthrough document not found:"
|
|
1050
|
+
echo " Expected: $WALKTHROUGH_DOC"
|
|
1051
|
+
echo ""
|
|
1052
|
+
echo "Re-run: /delphi-review --mode code-walkthrough"
|
|
1053
|
+
echo ""
|
|
1054
|
+
exit 1
|
|
1055
|
+
fi
|
|
1056
|
+
|
|
1057
|
+
# Cross-platform SHA-256 computation
|
|
1058
|
+
COMPUTED_HASH=""
|
|
1059
|
+
if command -v sha256sum &> /dev/null; then
|
|
1060
|
+
COMPUTED_HASH=$(sha256sum "$WALKTHROUGH_DOC" | cut -d' ' -f1)
|
|
1061
|
+
elif command -v shasum &> /dev/null; then
|
|
1062
|
+
COMPUTED_HASH=$(shasum -a 256 "$WALKTHROUGH_DOC" | cut -d' ' -f1)
|
|
1063
|
+
elif command -v openssl &> /dev/null; then
|
|
1064
|
+
COMPUTED_HASH=$(openssl sha256 -r "$WALKTHROUGH_DOC" | cut -d' ' -f1)
|
|
1065
|
+
fi
|
|
1066
|
+
|
|
1067
|
+
if [ -z "$COMPUTED_HASH" ]; then
|
|
1068
|
+
echo ""
|
|
1069
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1070
|
+
echo " ā NO SHA-256 TOOL - PUSH BLOCKED"
|
|
1071
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1072
|
+
echo ""
|
|
1073
|
+
echo "No SHA-256 tool available (install sha256sum, shasum, or openssl)"
|
|
1074
|
+
echo ""
|
|
1075
|
+
exit 1
|
|
1076
|
+
fi
|
|
1077
|
+
|
|
1078
|
+
if [ "$WALKTHROUGH_HASH" != "$COMPUTED_HASH" ]; then
|
|
1079
|
+
echo ""
|
|
1080
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1081
|
+
echo " ā WALKTHROUGH HASH MISMATCH - PUSH BLOCKED"
|
|
1082
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1083
|
+
echo ""
|
|
1084
|
+
echo "Walkthrough document has been modified after review:"
|
|
1085
|
+
echo " Expected: $WALKTHROUGH_HASH"
|
|
1086
|
+
echo " Found: $COMPUTED_HASH"
|
|
1087
|
+
echo ""
|
|
1088
|
+
echo "Re-run: /delphi-review --mode code-walkthrough"
|
|
1089
|
+
echo ""
|
|
1090
|
+
exit 1
|
|
1091
|
+
fi
|
|
1092
|
+
|
|
1093
|
+
# 4. generatedAt check (replay protection)
|
|
1094
|
+
if [ -z "$GENERATED_AT" ]; then
|
|
1095
|
+
echo ""
|
|
1096
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1097
|
+
echo " ā MISSING generatedAt - PUSH BLOCKED"
|
|
1098
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1099
|
+
echo ""
|
|
1100
|
+
echo "Walkthrough result missing generatedAt timestamp"
|
|
1101
|
+
echo ""
|
|
1102
|
+
echo "Re-run: /delphi-review --mode code-walkthrough"
|
|
1103
|
+
echo ""
|
|
1104
|
+
exit 1
|
|
1105
|
+
fi
|
|
1106
|
+
else
|
|
1107
|
+
# Grace period: old walkthroughs without provenance get WARNING
|
|
1108
|
+
echo ""
|
|
1109
|
+
echo "ā ļø WARNING: Legacy walkthrough format (no provenance fields)"
|
|
1110
|
+
echo " Provenance fields will be required after grace period (${GRACE_DAYS} days)"
|
|
1111
|
+
echo " Consider re-running: /delphi-review --mode code-walkthrough"
|
|
1112
|
+
echo ""
|
|
1113
|
+
fi
|
|
1114
|
+
|
|
964
1115
|
# ============================================================================
|
|
965
1116
|
# ALL CHECKS PASSED
|
|
966
1117
|
# ============================================================================
|
|
1118
|
+
echo ""
|
|
1119
|
+
|
|
1120
|
+
# ============================================================================
|
|
1121
|
+
# SPRINT-STATE BACKUP CHECK (WARNING ONLY ā Issue #318)
|
|
1122
|
+
# Scan for worktree sprint-state directories that haven't been backed up
|
|
1123
|
+
# to .sprint-history/ before push. WARN only (doesn't block) ā sprint-state
|
|
1124
|
+
# inside worktrees is deleted on `git worktree remove`, causing permanent
|
|
1125
|
+
# data loss if orchestrator forgets to back up during CLOSE phase.
|
|
1126
|
+
# ============================================================================
|
|
1127
|
+
if [ -d ".worktrees/sprint" ]; then
|
|
1128
|
+
for worktree_dir in .worktrees/sprint/sprint-*; do
|
|
1129
|
+
[ -d "$worktree_dir" ] || continue
|
|
1130
|
+
[ -d "$worktree_dir/.sprint-state" ] || continue
|
|
1131
|
+
sprint_id=$(basename "$worktree_dir")
|
|
1132
|
+
if [ -f "$worktree_dir/.sprint-state/sprint-state.json" ]; then
|
|
1133
|
+
if [ ! -d ".sprint-history/$sprint_id" ]; then
|
|
1134
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1135
|
+
echo " ā ļø SPRINT-STATE NOT BACKED UP: $sprint_id"
|
|
1136
|
+
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
1137
|
+
echo ""
|
|
1138
|
+
echo "Unarchived sprint state found in worktree:"
|
|
1139
|
+
echo " Path: $worktree_dir/.sprint-state/"
|
|
1140
|
+
echo " Backup: .sprint-history/$sprint_id/ (MISSING)"
|
|
1141
|
+
echo ""
|
|
1142
|
+
echo "RISK: If this worktree is removed before CLOSE backup,"
|
|
1143
|
+
echo " design docs, Delphi verdicts, and phase summaries"
|
|
1144
|
+
echo " are permanently lost."
|
|
1145
|
+
echo ""
|
|
1146
|
+
echo "Backup now:"
|
|
1147
|
+
echo " mkdir -p .sprint-history/$sprint_id"
|
|
1148
|
+
echo " cp -r $worktree_dir/.sprint-state/* .sprint-history/$sprint_id/"
|
|
1149
|
+
echo ""
|
|
1150
|
+
echo "See: https://github.com/boyingliu01/xp-gate/issues/318"
|
|
1151
|
+
echo ""
|
|
1152
|
+
fi
|
|
1153
|
+
fi
|
|
1154
|
+
done
|
|
1155
|
+
fi
|
|
1156
|
+
|
|
967
1157
|
echo ""
|
|
968
1158
|
echo "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
|
|
969
1159
|
echo " ā
GATE MW: CODE WALKTHROUGH VERIFIED"
|
package/lib/doctor.js
CHANGED
|
@@ -1058,6 +1058,53 @@ function ensureTuiRegistration() {
|
|
|
1058
1058
|
}
|
|
1059
1059
|
}
|
|
1060
1060
|
|
|
1061
|
+
/**
|
|
1062
|
+
* Diagnose language-specific tool availability.
|
|
1063
|
+
* @returns {number} Number of issues found (missing required tools)
|
|
1064
|
+
*/
|
|
1065
|
+
function diagnoseLanguageTools() {
|
|
1066
|
+
try {
|
|
1067
|
+
const { detectProjectLanguages, getToolsForLanguages, LANGUAGE_REGISTRY } = require('./language-tools.js');
|
|
1068
|
+
const projectRoot = process.cwd();
|
|
1069
|
+
const { detected } = detectProjectLanguages(projectRoot);
|
|
1070
|
+
|
|
1071
|
+
if (detected.length === 0) return 0;
|
|
1072
|
+
|
|
1073
|
+
console.log('');
|
|
1074
|
+
console.log('Language-Specific Tools:');
|
|
1075
|
+
console.log('------------------------');
|
|
1076
|
+
|
|
1077
|
+
const toolStatus = getToolsForLanguages(detected);
|
|
1078
|
+
let issues = 0;
|
|
1079
|
+
|
|
1080
|
+
for (const [lang, tools] of Object.entries(toolStatus)) {
|
|
1081
|
+
const langDef = LANGUAGE_REGISTRY[lang];
|
|
1082
|
+
console.log(` ${langDef?.name || lang}:`);
|
|
1083
|
+
|
|
1084
|
+
for (const t of tools) {
|
|
1085
|
+
const statusIcon = t.status === 'available' ? 'ā' : 'ā';
|
|
1086
|
+
const optMark = t.tool.optional ? ' (optional)' : '';
|
|
1087
|
+
const version = t.version || 'N/A';
|
|
1088
|
+
console.log(` ${statusIcon} ${t.tool.name}${optMark} ā ${version}`);
|
|
1089
|
+
|
|
1090
|
+
if (t.status === 'missing' && !t.tool.optional) {
|
|
1091
|
+
issues++;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (issues > 0) {
|
|
1097
|
+
console.log('');
|
|
1098
|
+
console.log(` ${issues} required tool(s) missing ā gates will SKIP.`);
|
|
1099
|
+
console.log(' Run "xp-gate install-tools --yes" to install.');
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
return issues;
|
|
1103
|
+
} catch {
|
|
1104
|
+
return 0;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1061
1108
|
async function doctor(args) {
|
|
1062
1109
|
const fixMode = args.includes('--fix');
|
|
1063
1110
|
|
|
@@ -1101,6 +1148,9 @@ async function doctor(args) {
|
|
|
1101
1148
|
|
|
1102
1149
|
issues += diagnoseOpenCodePlugin(checks);
|
|
1103
1150
|
|
|
1151
|
+
// Language-specific tool status
|
|
1152
|
+
issues += diagnoseLanguageTools();
|
|
1153
|
+
|
|
1104
1154
|
if (issues === 0) {
|
|
1105
1155
|
console.log('\nā All checks passed');
|
|
1106
1156
|
return 0;
|
package/lib/init.js
CHANGED
|
@@ -354,6 +354,93 @@ async function init(args) {
|
|
|
354
354
|
return code;
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Detect project languages and check/install language-specific tools.
|
|
359
|
+
* @param {string} projectRoot - Project root directory
|
|
360
|
+
* @param {boolean} autoYes - Auto-install missing tools
|
|
361
|
+
*/
|
|
362
|
+
async function detectAndReportLanguages(projectRoot, autoYes) {
|
|
363
|
+
const { detectProjectLanguages, getToolsForLanguages, generateProjectConfig, LANGUAGE_REGISTRY } = require('./language-tools.js');
|
|
364
|
+
|
|
365
|
+
console.log('');
|
|
366
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
367
|
+
console.log(' Multi-Language Quality Gates');
|
|
368
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
369
|
+
console.log('');
|
|
370
|
+
|
|
371
|
+
const { detected, configFiles } = detectProjectLanguages(projectRoot);
|
|
372
|
+
|
|
373
|
+
if (detected.length === 0) {
|
|
374
|
+
console.log(' No project languages detected.');
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
console.log(` Detected ${detected.length} language(s):`);
|
|
379
|
+
for (const lang of detected) {
|
|
380
|
+
const langDef = LANGUAGE_REGISTRY[lang];
|
|
381
|
+
console.log(` ⢠${langDef?.name || lang}`);
|
|
382
|
+
}
|
|
383
|
+
console.log('');
|
|
384
|
+
|
|
385
|
+
const toolStatus = getToolsForLanguages(detected);
|
|
386
|
+
const missingRequired = [];
|
|
387
|
+
const missingOptional = [];
|
|
388
|
+
|
|
389
|
+
for (const [lang, tools] of Object.entries(toolStatus)) {
|
|
390
|
+
for (const t of tools) {
|
|
391
|
+
if (t.status === 'missing') {
|
|
392
|
+
if (t.tool.optional) {
|
|
393
|
+
missingOptional.push({ lang, tool: t.tool });
|
|
394
|
+
} else {
|
|
395
|
+
missingRequired.push({ lang, tool: t.tool });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (missingRequired.length === 0 && missingOptional.length === 0) {
|
|
402
|
+
console.log(' ā All language-specific tools are available.');
|
|
403
|
+
} else {
|
|
404
|
+
if (missingRequired.length > 0) {
|
|
405
|
+
console.log(` ā ${missingRequired.length} required tool(s) missing:`);
|
|
406
|
+
for (const { lang, tool } of missingRequired) {
|
|
407
|
+
console.log(` ⢠${tool.name} (${lang}) ā gates will SKIP`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (missingOptional.length > 0) {
|
|
411
|
+
console.log(` ā ${missingOptional.length} optional tool(s) missing (fallback available):`);
|
|
412
|
+
for (const { lang, tool } of missingOptional) {
|
|
413
|
+
console.log(` ⢠${tool.name} (${lang})`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
console.log('');
|
|
417
|
+
|
|
418
|
+
if (autoYes || (process.stdin.isTTY && await askYesNo(' Install missing language-specific tools now?'))) {
|
|
419
|
+
console.log('');
|
|
420
|
+
const { installTool } = require('./language-tools.js');
|
|
421
|
+
let installed = 0;
|
|
422
|
+
for (const { lang, tool } of [...missingRequired, ...missingOptional]) {
|
|
423
|
+
const result = installTool(tool);
|
|
424
|
+
if (result.success) {
|
|
425
|
+
console.log(` ā ${tool.name} installed`);
|
|
426
|
+
installed++;
|
|
427
|
+
} else {
|
|
428
|
+
console.log(` ā ${tool.name}: ${result.message}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (installed > 0) {
|
|
432
|
+
console.log(`\n ${installed} tool(s) installed successfully.`);
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
console.log(' Skipped. Run "xp-gate install-tools --yes" later.');
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Generate project config
|
|
440
|
+
generateProjectConfig(projectRoot, detected, toolStatus);
|
|
441
|
+
console.log(`\n Generated .xp-gate-config.json`);
|
|
442
|
+
}
|
|
443
|
+
|
|
357
444
|
async function installLocal(args) {
|
|
358
445
|
const gitDir = getGitDir();
|
|
359
446
|
if (!gitDir) {
|
|
@@ -416,6 +503,10 @@ async function installLocal(args) {
|
|
|
416
503
|
// Auto-install missing CLI tools (prompt user)
|
|
417
504
|
const autoYes = args.includes('--yes') || args.includes('--auto-install');
|
|
418
505
|
await promptBootstrap(autoYes);
|
|
506
|
+
|
|
507
|
+
// Detect project languages and check language-specific tools
|
|
508
|
+
await detectAndReportLanguages(projectRoot, autoYes);
|
|
509
|
+
|
|
419
510
|
console.log('āāā FIRST-CLASS TEST QUALITY āāā');
|
|
420
511
|
console.log('XP-Gate treats test code as a first-class citizen:');
|
|
421
512
|
console.log(' ⢠TypeScript: test files are type-checked, not excluded from tsconfig.json');
|