@boyingliu01/xp-gate 0.18.5 → 0.19.1
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/adapter-common.sh +27 -0
- package/adapters/gate-3.sh +0 -0
- package/adapters/gate-4.sh +0 -0
- package/adapters/gate-7.sh +0 -0
- package/adapters/gate-8.sh +0 -0
- package/adapters/gate-9.sh +0 -0
- package/adapters/powershell.sh +114 -35
- package/adapters/typescript.sh +8 -8
- package/build-integrity/__tests__/import-resolver.test.ts +22 -0
- package/build-integrity/__tests__/orchestrator.test.ts +1 -2
- package/build-integrity/gate-10.ts +7 -0
- package/gate-12-file-hygiene.sh +230 -0
- package/hooks/adapter-common.sh +27 -0
- package/hooks/gate-10.sh +105 -0
- package/hooks/gate-12-file-hygiene.sh +0 -0
- package/hooks/lib/validate-code-walkthrough.cjs +125 -0
- package/hooks/pre-commit +130 -33
- package/hooks/pre-push +15 -91
- package/lib/__tests__/check-version.test.js +67 -46
- package/lib/__tests__/doctor.test.js +7 -6
- package/lib/__tests__/gate-runner.test.js +267 -0
- package/lib/__tests__/init.test.js +32 -0
- package/lib/__tests__/npm-package.test.js +13 -0
- package/lib/__tests__/phase-transition.test.js +604 -12
- package/lib/__tests__/sprint-discovery.test.ts +0 -1
- package/lib/__tests__/sprint-status-rework.test.js +100 -1
- package/lib/__tests__/update-hooks.test.js +143 -10
- package/lib/__tests__/upgrade-exec.test.js +73 -45
- package/lib/__tests__/upgrade-skill-sync.test.js +1 -1
- package/lib/__tests__/upgrade.test.js +12 -11
- package/lib/doctor.js +2 -1
- package/lib/gate-runner.js +68 -23
- package/lib/init.js +34 -5
- package/lib/install-skill.js +1 -0
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/phase-transition.js +203 -23
- package/lib/shared-paths.js +2 -0
- package/lib/sprint-status.js +15 -4
- package/lib/test-alignment.ts +3 -13
- package/lib/update-hooks.js +129 -15
- package/mock-policy/AGENTS.md +4 -4
- package/mock-policy/__tests__/gate-m3.test.ts +0 -5
- package/mock-policy/__tests__/scope-scanner.test.ts +8 -2
- package/mutation/AGENTS.md +3 -3
- package/mutation/__tests__/mutmut-runner.test.ts +443 -2
- package/mutation/__tests__/update-baseline.test.ts +0 -2
- package/mutation/runners/mutmut-runner.ts +67 -17
- package/package.json +9 -3
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/claude-code/skills/delphi-review/.delphi-config.json.example +24 -27
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +12 -11
- package/plugins/claude-code/skills/delphi-review/INSTALL.md +15 -17
- package/plugins/claude-code/skills/delphi-review/SKILL.md +30 -29
- package/plugins/claude-code/skills/delphi-review/opencode.json.delphi.example +6 -10
- package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/plugins/claude-code/skills/delphi-review/references/requirements.md +66 -54
- package/plugins/claude-code/skills/delphi-review/references/round-templates.md +7 -7
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/SKILL.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/references/components/skill-invocations.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/references/force-levels.md +37 -35
- package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +24 -14
- package/plugins/claude-code/skills/sprint-flow/references/phase-4-verify.md +6 -6
- package/plugins/claude-code/skills/sprint-flow/references/phase-overview.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/README.md +25 -0
- package/plugins/dsh/cordis.patch.yml +7 -0
- package/plugins/dsh/package.json +70 -0
- package/plugins/dsh/scripts/prepack.cjs +82 -0
- package/plugins/dsh/skills/admin-template-guidelines/SKILL.md +113 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-1.md +91 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-2.md +155 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-3.md +70 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-4.md +154 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-5.md +126 -0
- package/plugins/dsh/skills/admin-template-guidelines/references/rule-6.md +180 -0
- package/plugins/dsh/skills/batch-grill-me/SKILL.md +15 -0
- package/plugins/dsh/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/dsh/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/plugins/dsh/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/dsh/skills/delphi-review/.delphi-config.json.example +70 -0
- package/plugins/dsh/skills/delphi-review/AGENTS.md +67 -0
- package/plugins/dsh/skills/delphi-review/INSTALL.md +150 -0
- package/plugins/dsh/skills/delphi-review/SKILL.md +559 -0
- package/plugins/dsh/skills/delphi-review/hooks.md +19 -0
- package/plugins/dsh/skills/delphi-review/opencode.json.delphi.example +52 -0
- package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +470 -0
- package/plugins/dsh/skills/delphi-review/references/orchestrator-dispatch.md +64 -0
- package/plugins/dsh/skills/delphi-review/references/requirements.md +402 -0
- package/plugins/dsh/skills/delphi-review/references/round-templates.md +134 -0
- package/plugins/dsh/skills/delphi-review/scripts/verify-consensus.sh +43 -0
- package/plugins/dsh/skills/delphi-review/tools.md +29 -0
- package/plugins/dsh/skills/domain-modeling/ADR-FORMAT.md +64 -0
- package/plugins/dsh/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/plugins/dsh/skills/domain-modeling/SKILL.md +74 -0
- package/plugins/dsh/skills/grill-with-docs/SKILL.md +7 -0
- package/plugins/dsh/skills/grilling/SKILL.md +12 -0
- package/plugins/dsh/skills/improve-codebase-architecture/SKILL.md +156 -0
- package/plugins/dsh/skills/ralph-loop/SKILL.md +198 -0
- package/plugins/dsh/skills/ralph-loop/references/components.md +113 -0
- package/plugins/dsh/skills/ralph-loop/references/phase-2-build-ralph.md +89 -0
- package/plugins/dsh/skills/ralph-loop/references/workflow-details.md +99 -0
- package/plugins/dsh/skills/ralph-loop/templates/progress-log.md +36 -0
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +103 -0
- package/plugins/dsh/skills/sprint-flow/SKILL.md +411 -0
- package/plugins/dsh/skills/sprint-flow/__tests__/sprint-flow.test.cjs +457 -0
- package/plugins/dsh/skills/sprint-flow/hooks.md +18 -0
- package/plugins/dsh/skills/sprint-flow/references/components/memory.md +87 -0
- package/plugins/dsh/skills/sprint-flow/references/components/middleware.md +75 -0
- package/plugins/dsh/skills/sprint-flow/references/components/skill-invocations.md +105 -0
- package/plugins/dsh/skills/sprint-flow/references/components/system-prompt.md +27 -0
- package/plugins/dsh/skills/sprint-flow/references/components/tool-descriptions.md +95 -0
- package/plugins/dsh/skills/sprint-flow/references/force-levels.md +204 -0
- package/plugins/dsh/skills/sprint-flow/references/orchestration-rules.md +236 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-1-prep.md +294 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +273 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-3-build.md +348 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-4-verify.md +225 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-5-ship.md +222 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-6-close.md +266 -0
- package/plugins/dsh/skills/sprint-flow/references/phase-overview.md +486 -0
- package/plugins/dsh/skills/sprint-flow/scripts/verify-sprint-state.sh +38 -0
- package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-learning-log.md +136 -0
- package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-output-template.md +110 -0
- package/plugins/dsh/skills/sprint-flow/templates/emergent-issues-template.md +120 -0
- package/plugins/dsh/skills/sprint-flow/templates/pain-document-template.md +115 -0
- package/plugins/dsh/skills/sprint-flow/templates/sprint-progress-template.md +140 -0
- package/plugins/dsh/skills/sprint-flow/templates/sprint-summary-template.md +121 -0
- package/plugins/dsh/skills/sprint-flow/tools.md +29 -0
- package/plugins/dsh/skills/test-driven-development/SKILL.md +371 -0
- package/plugins/dsh/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +56 -0
- package/plugins/dsh/skills/test-specification-alignment/SKILL.md +205 -0
- package/plugins/dsh/skills/test-specification-alignment/references/alignment-verification-algorithm.md +493 -0
- package/plugins/dsh/skills/test-specification-alignment/references/phase2-constraint-enforcement.md +431 -0
- package/plugins/dsh/skills/test-specification-alignment/references/specification-format.md +348 -0
- package/plugins/dsh/skills/to-issues/SKILL.md +277 -0
- package/plugins/dsh/src/command.test.ts +102 -0
- package/plugins/dsh/src/command.ts +75 -0
- package/plugins/dsh/src/contract.test.ts +62 -0
- package/plugins/dsh/src/gate-runner.test.ts +100 -0
- package/plugins/dsh/src/gate-runner.ts +58 -0
- package/plugins/dsh/src/index.test.ts +97 -0
- package/plugins/dsh/src/index.ts +94 -0
- package/plugins/dsh/src/skills.test.ts +58 -0
- package/plugins/dsh/tsconfig.build.json +13 -0
- package/plugins/dsh/tsconfig.json +19 -0
- package/plugins/dsh/vitest.config.ts +8 -0
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/scripts/prepack.cjs +1 -0
- package/plugins/opencode/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/opencode/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/plugins/opencode/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/opencode/skills/delphi-review/.delphi-config.json.example +24 -27
- package/plugins/opencode/skills/delphi-review/AGENTS.md +12 -11
- package/plugins/opencode/skills/delphi-review/INSTALL.md +15 -17
- package/plugins/opencode/skills/delphi-review/SKILL.md +30 -29
- package/plugins/opencode/skills/delphi-review/opencode.json.delphi.example +6 -10
- package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/plugins/opencode/skills/delphi-review/references/requirements.md +66 -54
- package/plugins/opencode/skills/delphi-review/references/round-templates.md +7 -7
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/opencode/skills/sprint-flow/SKILL.md +4 -4
- package/plugins/opencode/skills/sprint-flow/references/components/skill-invocations.md +2 -2
- package/plugins/opencode/skills/sprint-flow/references/force-levels.md +37 -35
- package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +24 -14
- package/plugins/opencode/skills/sprint-flow/references/phase-4-verify.md +6 -6
- package/plugins/opencode/skills/sprint-flow/references/phase-overview.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/clipboard-vision/SKILL.md +47 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/SKILL.md +47 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/plugins/qoder/skills/delphi-review/.delphi-config.json.example +3 -11
- package/plugins/qoder/skills/delphi-review/AGENTS.md +14 -16
- package/plugins/qoder/skills/delphi-review/INSTALL.md +12 -12
- package/plugins/qoder/skills/delphi-review/SKILL.md +58 -25
- package/plugins/qoder/skills/delphi-review/opencode.json.delphi.example +1 -1
- package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/qoder/skills/sprint-flow/SKILL.md +2 -2
- package/plugins/qoder/skills/sprint-flow/references/force-levels.md +38 -35
- package/plugins/qoder/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +48 -6
- package/plugins/qoder/skills/sprint-flow/references/phase-4-verify.md +5 -6
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/scripts/__tests__/sync-package-content.test.js +30 -0
- package/scripts/delphi-external-review.cjs +194 -63
- package/scripts/sync-package-content.js +9 -2
- package/skills/clipboard-vision/SKILL.md +47 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
- package/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
- package/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
- package/skills/clipboard-vision/describe-clipboard.sh +161 -0
- package/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
- package/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
- package/skills/delphi-review/.delphi-config.json.example +24 -27
- package/skills/delphi-review/AGENTS.md +12 -11
- package/skills/delphi-review/INSTALL.md +15 -17
- package/skills/delphi-review/SKILL.md +30 -29
- package/skills/delphi-review/opencode.json.delphi.example +6 -10
- package/skills/delphi-review/references/code-walkthrough.md +33 -49
- package/skills/delphi-review/references/requirements.md +66 -54
- package/skills/delphi-review/references/round-templates.md +7 -7
- package/skills/sprint-flow/AGENTS.md +4 -4
- package/skills/sprint-flow/SKILL.md +4 -4
- package/skills/sprint-flow/references/components/skill-invocations.md +2 -2
- package/skills/sprint-flow/references/force-levels.md +37 -35
- package/skills/sprint-flow/references/orchestration-rules.md +1 -1
- package/skills/sprint-flow/references/phase-1-prep.md +1 -1
- package/skills/sprint-flow/references/phase-2-design.md +24 -14
- package/skills/sprint-flow/references/phase-4-verify.md +6 -6
- package/skills/sprint-flow/references/phase-overview.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +3 -3
- package/sprint-gate.sh +291 -0
package/hooks/pre-push
CHANGED
|
@@ -867,116 +867,47 @@ if [ ! -f "$RESULT_FILE" ]; then
|
|
|
867
867
|
exit 1
|
|
868
868
|
fi
|
|
869
869
|
|
|
870
|
-
# Check
|
|
871
|
-
if ! command -v
|
|
870
|
+
# Check Node.js availability (MANDATORY - zero degradation)
|
|
871
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
872
872
|
echo ""
|
|
873
873
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
874
874
|
echo " ❌ ENVIRONMENT ERROR - PUSH BLOCKED"
|
|
875
875
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
876
876
|
echo ""
|
|
877
|
-
echo "
|
|
877
|
+
echo "Node.js is NOT installed. Required for JSON validation."
|
|
878
878
|
echo ""
|
|
879
|
-
echo "Install
|
|
880
|
-
echo " apt-get install jq (Debian/Ubuntu)"
|
|
881
|
-
echo " brew install jq (macOS)"
|
|
882
|
-
echo " choco install jq (Windows)"
|
|
883
|
-
echo ""
|
|
884
|
-
echo "Zero degradation principle: Cannot proceed without jq."
|
|
879
|
+
echo "Install Node.js 18 or newer, then retry the push."
|
|
885
880
|
echo ""
|
|
886
881
|
exit 1
|
|
887
882
|
fi
|
|
888
883
|
|
|
889
|
-
#
|
|
890
|
-
|
|
891
|
-
# HEAD, its content references HEAD^ (the commit being reviewed), not HEAD.
|
|
892
|
-
CURRENT_COMMIT=$(git rev-parse HEAD)
|
|
893
|
-
if git diff-tree --no-commit-id --name-only -r HEAD 2>/dev/null | grep -qx "$RESULT_FILE"; then
|
|
894
|
-
# Result file is part of the latest commit — compare against the reviewed commit
|
|
895
|
-
EXPECTED_COMMIT=$(git rev-parse HEAD^ 2>/dev/null || echo "unknown")
|
|
896
|
-
else
|
|
897
|
-
# Result file is working-tree only — compare against current HEAD
|
|
898
|
-
EXPECTED_COMMIT="$CURRENT_COMMIT"
|
|
899
|
-
fi
|
|
884
|
+
# Walkthrough evidence must bind to the exact feature-branch HEAD being pushed.
|
|
885
|
+
EXPECTED_COMMIT=$(git rev-parse HEAD)
|
|
900
886
|
|
|
901
|
-
#
|
|
902
|
-
|
|
887
|
+
# Validate the complete evidence contract without interpolating JSON into shell.
|
|
888
|
+
HOOK_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
889
|
+
MW_VALIDATOR="$HOOK_DIR/lib/validate-code-walkthrough.cjs"
|
|
890
|
+
if [ ! -f "$MW_VALIDATOR" ]; then
|
|
903
891
|
echo ""
|
|
904
892
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
905
893
|
echo " ❌ RESULT FILE INVALID - PUSH BLOCKED"
|
|
906
894
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
907
895
|
echo ""
|
|
908
|
-
echo "
|
|
896
|
+
echo "Gate MW validator is missing: $MW_VALIDATOR"
|
|
909
897
|
echo ""
|
|
910
898
|
echo "Re-run: /delphi-review --mode code-walkthrough"
|
|
911
899
|
echo ""
|
|
912
900
|
exit 1
|
|
913
901
|
fi
|
|
914
902
|
|
|
915
|
-
RESULT_COMMIT=$(jq -r '.commit' "$RESULT_FILE")
|
|
916
|
-
RESULT_VERDICT=$(jq -r '.verdict' "$RESULT_FILE")
|
|
917
|
-
RESULT_EXPIRES=$(jq -r '.expires' "$RESULT_FILE")
|
|
918
|
-
RESULT_BRANCH=$(jq -r '.branch' "$RESULT_FILE")
|
|
919
903
|
CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
920
|
-
|
|
921
|
-
# Branch match check (optional but recommended)
|
|
922
|
-
if [ "$RESULT_BRANCH" != "$CURRENT_BRANCH" ]; then
|
|
923
|
-
echo ""
|
|
924
|
-
echo "⚠️ WARNING: Result file is for different branch"
|
|
925
|
-
echo " Expected: $CURRENT_BRANCH"
|
|
926
|
-
echo " Found: $RESULT_BRANCH"
|
|
927
|
-
echo ""
|
|
928
|
-
echo "Proceeding with commit verification..."
|
|
929
|
-
fi
|
|
930
|
-
|
|
931
|
-
# Commit match check
|
|
932
|
-
if [ "$RESULT_COMMIT" != "$EXPECTED_COMMIT" ]; then
|
|
904
|
+
if ! VALIDATION_ERROR=$(node "$MW_VALIDATOR" "$RESULT_FILE" "$EXPECTED_COMMIT" "$CURRENT_BRANCH" "$CURRENT_TIME" 2>&1); then
|
|
933
905
|
echo ""
|
|
934
906
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
935
907
|
echo " ❌ RESULT FILE OUTDATED - PUSH BLOCKED"
|
|
936
908
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
937
909
|
echo ""
|
|
938
|
-
echo "
|
|
939
|
-
echo " Expected: $EXPECTED_COMMIT"
|
|
940
|
-
echo " Found: $RESULT_COMMIT"
|
|
941
|
-
echo ""
|
|
942
|
-
echo "Re-run: /delphi-review --mode code-walkthrough"
|
|
943
|
-
echo ""
|
|
944
|
-
exit 1
|
|
945
|
-
fi
|
|
946
|
-
|
|
947
|
-
# Verdict check
|
|
948
|
-
if [ "$RESULT_VERDICT" != "APPROVED" ]; then
|
|
949
|
-
echo ""
|
|
950
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
951
|
-
echo " ❌ GATE MW: CODE WALKTHROUGH NOT APPROVED - PUSH BLOCKED"
|
|
952
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
953
|
-
echo ""
|
|
954
|
-
echo "Verdict: $RESULT_VERDICT"
|
|
955
|
-
echo ""
|
|
956
|
-
|
|
957
|
-
# Show issues if available
|
|
958
|
-
ISSUES=$(jq -r '.issues[] | "- [\(.severity)] \(.description)"' "$RESULT_FILE" 2>/dev/null)
|
|
959
|
-
if [ -n "$ISSUES" ]; then
|
|
960
|
-
echo "Issues found:"
|
|
961
|
-
echo "$ISSUES"
|
|
962
|
-
echo ""
|
|
963
|
-
fi
|
|
964
|
-
|
|
965
|
-
echo "Fix issues and re-run: /delphi-review --mode code-walkthrough"
|
|
966
|
-
echo ""
|
|
967
|
-
exit 1
|
|
968
|
-
fi
|
|
969
|
-
|
|
970
|
-
# Expiration check
|
|
971
|
-
if [[ "$CURRENT_TIME" > "$RESULT_EXPIRES" ]]; then
|
|
972
|
-
echo ""
|
|
973
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
974
|
-
echo " ❌ RESULT FILE EXPIRED - PUSH BLOCKED"
|
|
975
|
-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
976
|
-
echo ""
|
|
977
|
-
echo "Code walkthrough result has expired:"
|
|
978
|
-
echo " Expired: $RESULT_EXPIRES"
|
|
979
|
-
echo " Current: $CURRENT_TIME"
|
|
910
|
+
echo "$VALIDATION_ERROR"
|
|
980
911
|
echo ""
|
|
981
912
|
echo "Re-run: /delphi-review --mode code-walkthrough"
|
|
982
913
|
echo ""
|
|
@@ -996,16 +927,9 @@ echo "Files changed: $FILES_CHANGED"
|
|
|
996
927
|
echo "Lines: +$LINES_ADDED -$LINES_DELETED"
|
|
997
928
|
echo ""
|
|
998
929
|
echo "Code walkthrough result:"
|
|
999
|
-
echo " Commit: $
|
|
930
|
+
echo " Commit: $EXPECTED_COMMIT"
|
|
1000
931
|
echo " Verdict: APPROVED"
|
|
1001
|
-
echo " Expires: $RESULT_EXPIRES"
|
|
1002
|
-
|
|
1003
|
-
# Show confidence if available
|
|
1004
|
-
CONFIDENCE=$(jq -r '.confidence' "$RESULT_FILE" 2>/dev/null)
|
|
1005
|
-
if [ -n "$CONFIDENCE" ] && [ "$CONFIDENCE" != "null" ]; then
|
|
1006
|
-
echo " Confidence: $CONFIDENCE/10"
|
|
1007
|
-
fi
|
|
1008
932
|
|
|
1009
933
|
echo ""
|
|
1010
934
|
echo "Proceeding with push..."
|
|
1011
|
-
exit 0
|
|
935
|
+
exit 0
|
|
@@ -25,20 +25,58 @@ function cleanupDir(dir) {
|
|
|
25
25
|
describe('check-version.js — REQ-001-01', () => {
|
|
26
26
|
let mod;
|
|
27
27
|
let origHome;
|
|
28
|
+
let origCacheDir;
|
|
28
29
|
let tmpHome;
|
|
30
|
+
let tmpCacheDir;
|
|
29
31
|
let origReadFileSync;
|
|
32
|
+
let origHttpsGet;
|
|
33
|
+
|
|
34
|
+
function installFakeHttpsGet(latestVersion) {
|
|
35
|
+
const https = require('https');
|
|
36
|
+
const saved = https.get;
|
|
37
|
+
https.get = (_url, options, callbackArg) => {
|
|
38
|
+
const callback = typeof options === 'function' ? options : callbackArg;
|
|
39
|
+
if (!callback) return { on: () => undefined, destroy: () => undefined };
|
|
40
|
+
const response = {
|
|
41
|
+
statusCode: 200,
|
|
42
|
+
on: (event, handler) => {
|
|
43
|
+
const eventArgs = {
|
|
44
|
+
data: [JSON.stringify({ latest: latestVersion })],
|
|
45
|
+
end: [],
|
|
46
|
+
};
|
|
47
|
+
if (Object.hasOwn(eventArgs, event)) handler(...eventArgs[event]);
|
|
48
|
+
return response;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
callback(response);
|
|
52
|
+
return { on: () => undefined, destroy: () => undefined };
|
|
53
|
+
};
|
|
54
|
+
return () => {
|
|
55
|
+
https.get = saved;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
30
58
|
|
|
31
59
|
beforeEach(() => {
|
|
32
60
|
vi.resetModules();
|
|
33
61
|
origHome = process.env.HOME;
|
|
34
|
-
|
|
62
|
+
origCacheDir = process.env.XP_GATE_CACHE_DIR;
|
|
35
63
|
origReadFileSync = fs.readFileSync;
|
|
64
|
+
origHttpsGet = require('https').get;
|
|
65
|
+
tmpHome = undefined;
|
|
66
|
+
tmpCacheDir = undefined;
|
|
67
|
+
tmpHome = fakeHome();
|
|
68
|
+
tmpCacheDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ckv-cache-'));
|
|
69
|
+
process.env.XP_GATE_CACHE_DIR = tmpCacheDir;
|
|
36
70
|
});
|
|
37
71
|
|
|
38
72
|
afterEach(() => {
|
|
39
73
|
process.env.HOME = origHome;
|
|
74
|
+
if (origCacheDir === undefined) delete process.env.XP_GATE_CACHE_DIR;
|
|
75
|
+
else process.env.XP_GATE_CACHE_DIR = origCacheDir;
|
|
40
76
|
cleanupDir(tmpHome);
|
|
41
|
-
|
|
77
|
+
cleanupDir(tmpCacheDir);
|
|
78
|
+
if (origReadFileSync) fs.readFileSync = origReadFileSync;
|
|
79
|
+
if (origHttpsGet) require('https').get = origHttpsGet;
|
|
42
80
|
});
|
|
43
81
|
|
|
44
82
|
// ──────────────────────────────────────────
|
|
@@ -159,6 +197,14 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
159
197
|
expect(() => mod.clearCache()).not.toThrow();
|
|
160
198
|
});
|
|
161
199
|
|
|
200
|
+
it('does not touch the home-directory cache when XP_GATE_CACHE_DIR is set', () => {
|
|
201
|
+
const homeCache = path.join(tmpHome, '.xp-gate', 'version-cache.json');
|
|
202
|
+
fs.mkdirSync(path.dirname(homeCache), { recursive: true });
|
|
203
|
+
fs.writeFileSync(homeCache, 'sentinel');
|
|
204
|
+
mod.clearCache();
|
|
205
|
+
expect(fs.readFileSync(homeCache, 'utf8')).toBe('sentinel');
|
|
206
|
+
});
|
|
207
|
+
|
|
162
208
|
it('cachePath returns null when XP_GATE_DIR is inaccessible', () => {
|
|
163
209
|
// Verify clearCache handles null cachePath gracefully
|
|
164
210
|
expect(() => mod.clearCache()).not.toThrow();
|
|
@@ -216,31 +262,14 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
216
262
|
delete require.cache[resolved];
|
|
217
263
|
}
|
|
218
264
|
|
|
219
|
-
function withMockedHttps(latestVersion, fn) {
|
|
265
|
+
async function withMockedHttps(latestVersion, fn) {
|
|
220
266
|
const fsm = require('fs');
|
|
221
|
-
const
|
|
222
|
-
const cpPath = require('path').join(osm.homedir(), '.xp-gate', 'version-cache.json');
|
|
267
|
+
const cpPath = require('path').join(process.env.XP_GATE_CACHE_DIR, 'version-cache.json');
|
|
223
268
|
if (fsm.existsSync(cpPath)) {
|
|
224
269
|
try { fsm.unlinkSync(cpPath); } catch { }
|
|
225
270
|
}
|
|
226
271
|
evictCache();
|
|
227
|
-
const
|
|
228
|
-
const saved = https.get;
|
|
229
|
-
const body = JSON.stringify({ latest: latestVersion });
|
|
230
|
-
https.get = (_url, _opts, cb) => {
|
|
231
|
-
const callback = typeof _opts === 'function' ? _opts : cb;
|
|
232
|
-
if (!callback) return { on: () => undefined, destroy: () => undefined };
|
|
233
|
-
const mockRes = {
|
|
234
|
-
statusCode: 200,
|
|
235
|
-
on: (evt, handler) => {
|
|
236
|
-
if (evt === 'data') handler(body);
|
|
237
|
-
if (evt === 'end') handler();
|
|
238
|
-
return mockRes;
|
|
239
|
-
},
|
|
240
|
-
};
|
|
241
|
-
callback(mockRes);
|
|
242
|
-
return { on: () => undefined, destroy: () => undefined };
|
|
243
|
-
};
|
|
272
|
+
const restoreHttpsGet = installFakeHttpsGet(latestVersion);
|
|
244
273
|
// Mock getLocalVersion to return a fixed version so tests don't
|
|
245
274
|
// depend on the real package.json version (which drifts over time).
|
|
246
275
|
const savedReadFileSync = fsm.readFileSync;
|
|
@@ -256,9 +285,9 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
256
285
|
};
|
|
257
286
|
try {
|
|
258
287
|
const m = require('../check-version');
|
|
259
|
-
return fn(m);
|
|
288
|
+
return await fn(m);
|
|
260
289
|
} finally {
|
|
261
|
-
|
|
290
|
+
restoreHttpsGet();
|
|
262
291
|
fsm.readFileSync = savedReadFileSync;
|
|
263
292
|
}
|
|
264
293
|
}
|
|
@@ -301,39 +330,31 @@ describe('check-version.js — REQ-001-01', () => {
|
|
|
301
330
|
delete require.cache[resolved];
|
|
302
331
|
}
|
|
303
332
|
|
|
304
|
-
function withMockedHttpsBox(latestVersion, fn) {
|
|
333
|
+
async function withMockedHttpsBox(latestVersion, fn) {
|
|
305
334
|
const fsm = require('fs');
|
|
306
|
-
const
|
|
307
|
-
const cpPath = require('path').join(osm.homedir(), '.xp-gate', 'version-cache.json');
|
|
335
|
+
const cpPath = require('path').join(process.env.XP_GATE_CACHE_DIR, 'version-cache.json');
|
|
308
336
|
if (fsm.existsSync(cpPath)) {
|
|
309
337
|
try { fsm.unlinkSync(cpPath); } catch { }
|
|
310
338
|
}
|
|
311
339
|
evictCache();
|
|
312
|
-
const
|
|
313
|
-
const saved = https.get;
|
|
314
|
-
const body = JSON.stringify({ latest: latestVersion });
|
|
315
|
-
https.get = (_url, _opts, cb) => {
|
|
316
|
-
const callback = typeof _opts === 'function' ? _opts : cb;
|
|
317
|
-
if (!callback) return { on: () => undefined, destroy: () => undefined };
|
|
318
|
-
const mockRes = {
|
|
319
|
-
statusCode: 200,
|
|
320
|
-
on: (evt, handler) => {
|
|
321
|
-
if (evt === 'data') handler(body);
|
|
322
|
-
if (evt === 'end') handler();
|
|
323
|
-
return mockRes;
|
|
324
|
-
},
|
|
325
|
-
};
|
|
326
|
-
callback(mockRes);
|
|
327
|
-
return { on: () => undefined, destroy: () => undefined };
|
|
328
|
-
};
|
|
340
|
+
const restoreHttpsGet = installFakeHttpsGet(latestVersion);
|
|
329
341
|
try {
|
|
330
342
|
const m = require('../check-version');
|
|
331
|
-
return fn(m, cpPath);
|
|
343
|
+
return await fn(m, cpPath);
|
|
332
344
|
} finally {
|
|
333
|
-
|
|
345
|
+
restoreHttpsGet();
|
|
334
346
|
}
|
|
335
347
|
}
|
|
336
348
|
|
|
349
|
+
it('restores HTTPS after an asynchronous callback rejects', async () => {
|
|
350
|
+
const savedGet = require('https').get;
|
|
351
|
+
await expect(withMockedHttpsBox('0.18.0', async () => {
|
|
352
|
+
await Promise.resolve();
|
|
353
|
+
throw new Error('callback failed');
|
|
354
|
+
})).rejects.toThrow('callback failed');
|
|
355
|
+
expect(require('https').get).toBe(savedGet);
|
|
356
|
+
});
|
|
357
|
+
|
|
337
358
|
it('rejects null version from registry — cache NOT written', async () => {
|
|
338
359
|
await withMockedHttpsBox(null, async (m, cpPath) => {
|
|
339
360
|
const r = await m.getRemoteVersion('@test/pkg');
|
|
@@ -66,7 +66,7 @@ describe('doctor', () => {
|
|
|
66
66
|
// and reloading it would restore the full tool list, breaking the test mock setup.
|
|
67
67
|
const modulesToInvalidate = [
|
|
68
68
|
'../doctor', '../uninstall', '../init', '../shared-paths',
|
|
69
|
-
'../check-version.js',
|
|
69
|
+
'../check-version.js', '../doctor-tui',
|
|
70
70
|
];
|
|
71
71
|
for (const mod of modulesToInvalidate) {
|
|
72
72
|
try { delete require.cache[require.resolve(mod)]; } catch { /* first load */ }
|
|
@@ -126,7 +126,7 @@ describe('doctor', () => {
|
|
|
126
126
|
fs.mkdirSync(dir, { recursive: true });
|
|
127
127
|
fs.writeFileSync(
|
|
128
128
|
path.join(dir, 'pre-push'),
|
|
129
|
-
'#!/bin/bash\n# Pre-push Hook
|
|
129
|
+
'#!/bin/bash\n# Pre-push Hook — 8 gates:\n'
|
|
130
130
|
);
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -148,7 +148,7 @@ describe('doctor', () => {
|
|
|
148
148
|
path.join(dir, 'adapters', 'python.sh'),
|
|
149
149
|
'#!/usr/bin/env bash\necho "py adapter"\n'
|
|
150
150
|
);
|
|
151
|
-
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh'];
|
|
151
|
+
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh', 'gate-10.sh'];
|
|
152
152
|
for (const script of gateScripts) {
|
|
153
153
|
fs.writeFileSync(path.join(dir, 'adapters', script), `#!/bin/bash\n# ${script}\n`);
|
|
154
154
|
}
|
|
@@ -187,7 +187,7 @@ describe('doctor', () => {
|
|
|
187
187
|
fs.mkdirSync(globalAdaptersDir(), { recursive: true });
|
|
188
188
|
fs.writeFileSync(path.join(globalAdaptersDir(), 'typescript.sh'), '#!/usr/bin/env bash\necho "ts adapter"\n');
|
|
189
189
|
fs.writeFileSync(path.join(globalAdaptersDir(), 'python.sh'), '#!/usr/bin/env bash\necho "py adapter"\n');
|
|
190
|
-
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh'];
|
|
190
|
+
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh', 'gate-10.sh'];
|
|
191
191
|
for (const script of gateScripts) {
|
|
192
192
|
fs.writeFileSync(path.join(globalAdaptersDir(), script), `#!/bin/bash\n# ${script}\n`);
|
|
193
193
|
}
|
|
@@ -941,7 +941,7 @@ describe('doctor', () => {
|
|
|
941
941
|
|
|
942
942
|
function createGateScripts(dir) {
|
|
943
943
|
fs.mkdirSync(dir, { recursive: true });
|
|
944
|
-
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh'];
|
|
944
|
+
const gateScripts = ['gate-3.sh', 'gate-4.sh', 'gate-7.sh', 'gate-8.sh', 'gate-9.sh', 'gate-10.sh'];
|
|
945
945
|
for (const script of gateScripts) {
|
|
946
946
|
fs.writeFileSync(path.join(dir, script), `#!/bin/bash\n# ${script}\n`);
|
|
947
947
|
}
|
|
@@ -962,7 +962,7 @@ describe('doctor', () => {
|
|
|
962
962
|
expect.stringContaining('Gate scripts')
|
|
963
963
|
);
|
|
964
964
|
expect(logSpy).toHaveBeenCalledWith(
|
|
965
|
-
expect.stringContaining('
|
|
965
|
+
expect.stringContaining('6 gate script(s)')
|
|
966
966
|
);
|
|
967
967
|
});
|
|
968
968
|
|
|
@@ -1005,6 +1005,7 @@ describe('doctor', () => {
|
|
|
1005
1005
|
expect(fs.existsSync(path.join(projectAdaptersDir(), 'gate-7.sh'))).toBe(true);
|
|
1006
1006
|
expect(fs.existsSync(path.join(projectAdaptersDir(), 'gate-8.sh'))).toBe(true);
|
|
1007
1007
|
expect(fs.existsSync(path.join(projectAdaptersDir(), 'gate-9.sh'))).toBe(true);
|
|
1008
|
+
expect(fs.existsSync(path.join(projectAdaptersDir(), 'gate-10.sh'))).toBe(true);
|
|
1008
1009
|
expect(logSpy).toHaveBeenCalledWith(
|
|
1009
1010
|
expect.stringContaining('Restored')
|
|
1010
1011
|
);
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-standalone-gates gate-runner.js
|
|
3
|
+
* @intent 验证 runGateAdapter 独立运行 gate 适配器片段时能提供钩子上下文
|
|
4
|
+
* (gate_start_ms / record_gate_audit / now_ms / PROJECT_LANG / CHANGED_FILES),
|
|
5
|
+
* 避免 "command not found" 误报。
|
|
6
|
+
* @covers AC-STANDALONE-GATES-01, AC-STANDALONE-GATES-02, AC-STANDALONE-GATES-03, AC-STANDALONE-GATES-04, AC-STANDALONE-GATES-05
|
|
7
|
+
*/
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const os = require('node:os');
|
|
11
|
+
const { execFileSync, spawnSync } = require('node:child_process');
|
|
12
|
+
|
|
13
|
+
let tmpProject;
|
|
14
|
+
let tmpAdapters;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
tmpProject = fs.mkdtempSync(path.join(os.tmpdir(), 'xpgate-runner-'));
|
|
18
|
+
tmpAdapters = path.join(tmpProject, 'githooks', 'adapters');
|
|
19
|
+
fs.mkdirSync(tmpAdapters, { recursive: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
if (tmpProject && fs.existsSync(tmpProject)) {
|
|
24
|
+
fs.rmSync(tmpProject, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function writeAdapterCommon() {
|
|
29
|
+
fs.writeFileSync(
|
|
30
|
+
path.join(tmpAdapters, 'adapter-common.sh'),
|
|
31
|
+
'detect_project_lang() { echo "typescript"; }'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeGateFragment(fragmentLines) {
|
|
36
|
+
fs.writeFileSync(
|
|
37
|
+
path.join(tmpAdapters, 'gate-9.sh'),
|
|
38
|
+
['# gate-9 fragment', ...fragmentLines, 'exit 0'].join('\n')
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function shellParameter(expression) {
|
|
43
|
+
return ['$', '{', expression, '}'].join('');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function initializeGitProject() {
|
|
47
|
+
execFileSync('git', ['init', '-q'], { cwd: tmpProject });
|
|
48
|
+
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: tmpProject });
|
|
49
|
+
execFileSync('git', ['config', 'user.name', 'XP-Gate Test'], { cwd: tmpProject });
|
|
50
|
+
fs.writeFileSync(path.join(tmpProject, 'tracked.ts'), 'const value = 1;\n');
|
|
51
|
+
execFileSync('git', ['add', 'tracked.ts'], { cwd: tmpProject });
|
|
52
|
+
execFileSync('git', ['-c', 'core.hooksPath=/dev/null', 'commit', '-qm', 'initial'], {
|
|
53
|
+
cwd: tmpProject,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Runs runGateAdapter in a child node process (worker threads forbid process
|
|
58
|
+
// chdir, and runGateAdapter execs bash), capturing stdout/exit status.
|
|
59
|
+
function runChildAdapter(gateRunnerAbsPath, fragmentPath, cwd = tmpProject) {
|
|
60
|
+
const script = [
|
|
61
|
+
`const { runGateAdapter } = require(${JSON.stringify(gateRunnerAbsPath)});`,
|
|
62
|
+
`runGateAdapter(${JSON.stringify(fragmentPath)}, ${JSON.stringify(cwd)});`,
|
|
63
|
+
"console.log('__ADAPTER_DONE__');",
|
|
64
|
+
].join('\n');
|
|
65
|
+
try {
|
|
66
|
+
const stdout = execFileSync(process.execPath, ['-e', script], {
|
|
67
|
+
cwd,
|
|
68
|
+
encoding: 'utf8',
|
|
69
|
+
timeout: 30000,
|
|
70
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
71
|
+
});
|
|
72
|
+
return { status: 0, stdout };
|
|
73
|
+
} catch (err) {
|
|
74
|
+
return { status: err.status ?? 1, stdout: (err.stdout || '').toString() };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe('gate-runner.js — runGateAdapter', () => {
|
|
79
|
+
it('sources the fragment with hook helpers available (REQ-standalone-gates-01)', () => {
|
|
80
|
+
const runnerAbsPath = path.resolve(__dirname, '../gate-runner.js');
|
|
81
|
+
writeAdapterCommon();
|
|
82
|
+
writeGateFragment([
|
|
83
|
+
'USES_START=$(gate_start_ms)',
|
|
84
|
+
`if [ -z "${shellParameter('PROJECT_LANG:-')}" ]; then exit 99; fi`,
|
|
85
|
+
`if [ -z "${shellParameter('CHANGED_FILES+x')}" ]; then exit 98; fi`,
|
|
86
|
+
'record_gate_audit "gate-9" "sast" "PASS" "0" "$USES_START"',
|
|
87
|
+
]);
|
|
88
|
+
const fragPath = path.join(tmpAdapters, 'gate-9.sh');
|
|
89
|
+
const { status, stdout } = runChildAdapter(runnerAbsPath, fragPath);
|
|
90
|
+
expect(status).toBe(0);
|
|
91
|
+
expect(stdout).toContain('__ADAPTER_DONE__');
|
|
92
|
+
}, 30000);
|
|
93
|
+
|
|
94
|
+
it('defines now_ms/gate_start_ms/record_gate_audit/PROJECT_LANG/CHANGED_FILES before sourcing (REQ-standalone-gates-02)', () => {
|
|
95
|
+
const runnerAbsPath = path.resolve(__dirname, '../gate-runner.js');
|
|
96
|
+
writeAdapterCommon();
|
|
97
|
+
writeGateFragment([
|
|
98
|
+
'if ! command -v gate_start_ms >/dev/null 2>&1; then exit 97; fi',
|
|
99
|
+
'if ! command -v record_gate_audit >/dev/null 2>&1; then exit 96; fi',
|
|
100
|
+
'if ! command -v now_ms >/dev/null 2>&1; then exit 95; fi',
|
|
101
|
+
`if [ -z "${shellParameter('PROJECT_LANG:-')}" ]; then exit 94; fi`,
|
|
102
|
+
`if [ -z "${shellParameter('CHANGED_FILES+x')}" ]; then exit 93; fi`,
|
|
103
|
+
]);
|
|
104
|
+
const fragPath = path.join(tmpAdapters, 'gate-9.sh');
|
|
105
|
+
const { status, stdout } = runChildAdapter(runnerAbsPath, fragPath);
|
|
106
|
+
expect(status).toBe(0);
|
|
107
|
+
expect(stdout).toContain('__ADAPTER_DONE__');
|
|
108
|
+
}, 30000);
|
|
109
|
+
|
|
110
|
+
it('sources adapter-common.sh to provide detect_project_lang (REQ-standalone-gates-03)', () => {
|
|
111
|
+
const runnerAbsPath = path.resolve(__dirname, '../gate-runner.js');
|
|
112
|
+
writeAdapterCommon();
|
|
113
|
+
writeGateFragment([
|
|
114
|
+
`if [ "${shellParameter('PROJECT_LANG:-')}" != "typescript" ]; then exit 92; fi`,
|
|
115
|
+
]);
|
|
116
|
+
const fragPath = path.join(tmpAdapters, 'gate-9.sh');
|
|
117
|
+
const { status, stdout } = runChildAdapter(runnerAbsPath, fragPath);
|
|
118
|
+
expect(status).toBe(0);
|
|
119
|
+
expect(stdout).toContain('__ADAPTER_DONE__');
|
|
120
|
+
}, 30000);
|
|
121
|
+
|
|
122
|
+
it('propagates changed files from the project git working tree (REQ-standalone-gates-04)', () => {
|
|
123
|
+
const runnerAbsPath = path.resolve(__dirname, '../gate-runner.js');
|
|
124
|
+
writeAdapterCommon();
|
|
125
|
+
initializeGitProject();
|
|
126
|
+
fs.writeFileSync(path.join(tmpProject, 'tracked.ts'), 'const value = 2;\n');
|
|
127
|
+
writeGateFragment([
|
|
128
|
+
`if [ "${shellParameter('CHANGED_FILES')}" != "tracked.ts" ]; then exit 91; fi`,
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
const fragPath = path.join(tmpAdapters, 'gate-9.sh');
|
|
132
|
+
const { status, stdout } = runChildAdapter(runnerAbsPath, fragPath);
|
|
133
|
+
|
|
134
|
+
expect(status).toBe(0);
|
|
135
|
+
expect(stdout).toContain('__ADAPTER_DONE__');
|
|
136
|
+
}, 30000);
|
|
137
|
+
|
|
138
|
+
it('runs a standalone gate bash fallback through the public CLI (REQ-standalone-gates-05)', () => {
|
|
139
|
+
const packageFixture = path.join(tmpProject, 'package');
|
|
140
|
+
fs.cpSync(path.resolve(__dirname, '../..'), packageFixture, { recursive: true });
|
|
141
|
+
const cliPath = path.join(packageFixture, 'bin', 'xp-gate.js');
|
|
142
|
+
const gatesDir = path.join(tmpProject, 'githooks', 'gates');
|
|
143
|
+
fs.mkdirSync(gatesDir, { recursive: true });
|
|
144
|
+
fs.writeFileSync(
|
|
145
|
+
path.join(gatesDir, 'gate-9-public-fallback.sh'),
|
|
146
|
+
[
|
|
147
|
+
'if ! command -v gate_start_ms >/dev/null 2>&1; then exit 90; fi',
|
|
148
|
+
`if [ -z "${shellParameter('CHANGED_FILES+x')}" ]; then exit 89; fi`,
|
|
149
|
+
'echo "__PUBLIC_FALLBACK__"',
|
|
150
|
+
].join('\n')
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const result = spawnSync(process.execPath, [cliPath, 'check', '9'], {
|
|
154
|
+
cwd: tmpProject,
|
|
155
|
+
encoding: 'utf8',
|
|
156
|
+
timeout: 30000,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result.status).toBe(0);
|
|
160
|
+
expect(result.stdout).toContain('__PUBLIC_FALLBACK__');
|
|
161
|
+
}, 30000);
|
|
162
|
+
|
|
163
|
+
it('isolates fallback resolution and git context to the requested target path (REQ-standalone-gates-06)', () => {
|
|
164
|
+
const packageFixture = path.join(tmpProject, 'package');
|
|
165
|
+
const callerProject = path.join(tmpProject, 'caller');
|
|
166
|
+
const targetProject = path.join(tmpProject, 'target');
|
|
167
|
+
fs.cpSync(path.resolve(__dirname, '../..'), packageFixture, { recursive: true });
|
|
168
|
+
fs.mkdirSync(path.join(callerProject, 'githooks', 'gates'), { recursive: true });
|
|
169
|
+
fs.mkdirSync(path.join(targetProject, 'githooks', 'gates'), { recursive: true });
|
|
170
|
+
fs.writeFileSync(
|
|
171
|
+
path.join(callerProject, 'githooks', 'gates', 'gate-9-caller.sh'),
|
|
172
|
+
'echo "__CALLER_REPO__"\n'
|
|
173
|
+
);
|
|
174
|
+
fs.writeFileSync(
|
|
175
|
+
path.join(targetProject, 'githooks', 'gates', 'gate-9-target.sh'),
|
|
176
|
+
[
|
|
177
|
+
'if [ "$PWD" != "$EXPECTED_TARGET" ]; then exit 88; fi',
|
|
178
|
+
'echo "__TARGET_REPO__"',
|
|
179
|
+
].join('\n')
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const result = spawnSync(
|
|
183
|
+
process.execPath,
|
|
184
|
+
[path.join(packageFixture, 'bin', 'xp-gate.js'), 'check', '9', targetProject],
|
|
185
|
+
{
|
|
186
|
+
cwd: callerProject,
|
|
187
|
+
env: { ...process.env, EXPECTED_TARGET: targetProject },
|
|
188
|
+
encoding: 'utf8',
|
|
189
|
+
timeout: 30000,
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
expect(result.status).toBe(0);
|
|
194
|
+
expect(result.stdout).toContain('__TARGET_REPO__');
|
|
195
|
+
expect(result.stdout).not.toContain('__CALLER_REPO__');
|
|
196
|
+
}, 30000);
|
|
197
|
+
|
|
198
|
+
it('sources fragment and helper paths containing shell metacharacters literally (REQ-standalone-gates-07)', () => {
|
|
199
|
+
const runnerAbsPath = path.resolve(__dirname, '../gate-runner.js');
|
|
200
|
+
const hostileRoot = path.join(tmpProject, 'space $HOME `marker` "quoted"');
|
|
201
|
+
tmpAdapters = path.join(hostileRoot, 'githooks', 'adapters');
|
|
202
|
+
fs.mkdirSync(tmpAdapters, { recursive: true });
|
|
203
|
+
writeAdapterCommon();
|
|
204
|
+
writeGateFragment([
|
|
205
|
+
`if [ "${shellParameter('PROJECT_LANG')}" != "typescript" ]; then exit 87; fi`,
|
|
206
|
+
'echo "__HOSTILE_PATH_OK__"',
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
const result = runChildAdapter(
|
|
210
|
+
runnerAbsPath,
|
|
211
|
+
path.join(tmpAdapters, 'gate-9.sh'),
|
|
212
|
+
hostileRoot
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(result.status).toBe(0);
|
|
216
|
+
expect(result.stdout).toContain('__HOSTILE_PATH_OK__');
|
|
217
|
+
}, 30000);
|
|
218
|
+
|
|
219
|
+
it('loads parent-layout adapter-common and packaged now-ms through the public CLI (REQ-standalone-gates-08)', () => {
|
|
220
|
+
const packageFixture = path.join(tmpProject, 'package');
|
|
221
|
+
fs.cpSync(path.resolve(__dirname, '../..'), packageFixture, { recursive: true });
|
|
222
|
+
const gatesDir = path.join(tmpProject, 'githooks', 'gates');
|
|
223
|
+
fs.mkdirSync(gatesDir, { recursive: true });
|
|
224
|
+
fs.writeFileSync(
|
|
225
|
+
path.join(tmpProject, 'githooks', 'adapter-common.sh'),
|
|
226
|
+
'detect_project_lang() { echo "parent-layout-language"; }\n'
|
|
227
|
+
);
|
|
228
|
+
fs.writeFileSync(
|
|
229
|
+
path.join(gatesDir, 'gate-9-parent-helper.sh'),
|
|
230
|
+
[
|
|
231
|
+
`if [ "${shellParameter('PROJECT_LANG')}" != "parent-layout-language" ]; then exit 86; fi`,
|
|
232
|
+
'if [ "$(now_ms)" != "424242" ]; then exit 85; fi',
|
|
233
|
+
'echo "__PARENT_HELPER_OK__"',
|
|
234
|
+
].join('\n')
|
|
235
|
+
);
|
|
236
|
+
fs.writeFileSync(
|
|
237
|
+
path.join(packageFixture, 'hooks', 'lib', 'now-ms.sh'),
|
|
238
|
+
'now_ms() { echo 424242; }\n'
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const result = spawnSync(
|
|
242
|
+
process.execPath,
|
|
243
|
+
[path.join(packageFixture, 'bin', 'xp-gate.js'), 'check', '9'],
|
|
244
|
+
{ cwd: tmpProject, encoding: 'utf8', timeout: 30000 }
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect(result.status).toBe(0);
|
|
248
|
+
expect(result.stdout).toContain('__PARENT_HELPER_OK__');
|
|
249
|
+
}, 30000);
|
|
250
|
+
|
|
251
|
+
it('propagates a failing fragment exit through the public CLI (REQ-standalone-gates-09)', () => {
|
|
252
|
+
const packageFixture = path.join(tmpProject, 'package');
|
|
253
|
+
const targetProject = path.join(tmpProject, 'failing-target');
|
|
254
|
+
fs.cpSync(path.resolve(__dirname, '../..'), packageFixture, { recursive: true });
|
|
255
|
+
const gatesDir = path.join(targetProject, 'githooks', 'gates');
|
|
256
|
+
fs.mkdirSync(gatesDir, { recursive: true });
|
|
257
|
+
fs.writeFileSync(path.join(gatesDir, 'gate-9-failure.sh'), 'exit 23\n');
|
|
258
|
+
|
|
259
|
+
const result = spawnSync(
|
|
260
|
+
process.execPath,
|
|
261
|
+
[path.join(packageFixture, 'bin', 'xp-gate.js'), 'check', '9', targetProject],
|
|
262
|
+
{ cwd: tmpProject, encoding: 'utf8', timeout: 30000 }
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
expect(result.status).toBe(1);
|
|
266
|
+
}, 30000);
|
|
267
|
+
});
|