@boyingliu01/xp-gate 0.15.5 → 0.18.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 +0 -0
- package/adapters/cpp.sh +0 -0
- package/adapters/dart.sh +0 -0
- package/adapters/flutter.sh +0 -0
- package/adapters/go.sh +0 -0
- package/adapters/iac.sh +0 -0
- package/adapters/java.sh +0 -0
- package/adapters/kotlin.sh +0 -0
- package/adapters/objectivec.sh +0 -0
- package/adapters/plugins/book299-20132-python/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-20132-python/scripts/install-maven.sh +0 -0
- package/adapters/plugins/book299-4081-c/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-4081-c/scripts/install-maven.sh +0 -0
- package/adapters/plugins/book299-4083-javascriptes5/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-4083-javascriptes5/scripts/install-maven.sh +0 -0
- package/adapters/powershell.sh +0 -0
- package/adapters/python.sh +0 -0
- package/adapters/shell.sh +0 -0
- package/adapters/swift.sh +0 -0
- package/adapters/typescript.sh +0 -0
- package/bin/xp-gate.js +29 -1
- package/gate-3.sh +0 -0
- package/gate-4.sh +0 -0
- package/gate-7.sh +0 -0
- package/gate-8.sh +0 -0
- package/gate-9.sh +0 -0
- package/hooks/adapter-common.sh +0 -0
- package/hooks/gate-3.sh +0 -0
- package/hooks/gate-4.sh +0 -0
- package/hooks/gate-7.sh +0 -0
- package/hooks/gate-8.sh +0 -0
- package/hooks/gate-9.sh +0 -0
- package/hooks/lib/now-ms.sh +33 -0
- package/hooks/pre-commit +63 -47
- package/hooks/pre-push +0 -0
- package/hooks/sprint-gate.sh +291 -0
- package/lib/__tests__/check-version.test.js +122 -0
- package/lib/__tests__/doctor.test.js +13 -15
- package/lib/__tests__/gate-audit.test.ts +38 -0
- package/lib/__tests__/phase-transition.test.js +512 -272
- package/lib/__tests__/retro.test.js +131 -0
- package/lib/__tests__/sprint-audit.test.js +207 -207
- package/lib/__tests__/sprint-status-rework.test.js +184 -0
- package/lib/check-version.js +9 -0
- package/lib/doctor-tui.js +268 -198
- package/lib/doctor.js +15 -3
- package/lib/gate-audit.ts +18 -7
- package/lib/gate-runner.js +77 -30
- package/lib/init.js +10 -0
- package/lib/language-tools.js +833 -833
- package/lib/mattpocock-license-verdict.json +7 -0
- package/lib/phase-transition.js +499 -249
- package/lib/retro.js +222 -0
- package/lib/shared-utils.js +7 -0
- package/lib/sprint-audit.js +235 -235
- package/lib/sprint-init.js +207 -0
- package/lib/sprint-state-migrator.js +1 -0
- package/lib/sprint-status.js +111 -0
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/package.json +3 -2
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/bin/delphi-review-guard.sh +0 -0
- package/plugins/claude-code/bin/xp-gate-check +0 -0
- package/plugins/claude-code/skills/batch-grill-me/SKILL.md +15 -0
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/delphi-review/SKILL.md +59 -4
- package/plugins/claude-code/skills/delphi-review/references/requirements.md +390 -0
- package/plugins/claude-code/skills/delphi-review/references/round-templates.md +67 -0
- package/plugins/claude-code/skills/domain-modeling/ADR-FORMAT.md +64 -0
- package/plugins/claude-code/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/plugins/claude-code/skills/domain-modeling/SKILL.md +74 -0
- package/plugins/claude-code/skills/grill-with-docs/SKILL.md +7 -0
- package/plugins/claude-code/skills/grilling/SKILL.md +12 -0
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/SKILL.md +168 -43
- package/plugins/claude-code/skills/sprint-flow/references/components/memory.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/components/middleware.md +1 -1
- package/plugins/claude-code/skills/sprint-flow/references/components/skill-invocations.md +21 -20
- package/plugins/claude-code/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
- package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +14 -12
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +145 -133
- package/plugins/claude-code/skills/sprint-flow/references/phase-3-build.md +69 -2
- package/plugins/claude-code/skills/sprint-flow/references/phase-4-verify.md +93 -31
- package/plugins/claude-code/skills/sprint-flow/references/phase-5-ship.md +99 -14
- package/plugins/claude-code/skills/sprint-flow/references/phase-6-close.md +37 -5
- package/plugins/claude-code/skills/sprint-flow/references/phase-overview.md +58 -53
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/SKILL.md +8 -3
- package/plugins/claude-code/skills/to-issues/SKILL.md +1 -1
- package/plugins/opencode/index.ts +1 -0
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/batch-grill-me/SKILL.md +15 -0
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/delphi-review/SKILL.md +59 -4
- package/plugins/opencode/skills/delphi-review/references/requirements.md +390 -0
- package/plugins/opencode/skills/delphi-review/references/round-templates.md +67 -0
- package/plugins/opencode/skills/domain-modeling/ADR-FORMAT.md +64 -0
- package/plugins/opencode/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/plugins/opencode/skills/domain-modeling/SKILL.md +74 -0
- package/plugins/opencode/skills/grill-with-docs/SKILL.md +7 -0
- package/plugins/opencode/skills/grilling/SKILL.md +12 -0
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/SKILL.md +168 -43
- package/plugins/opencode/skills/sprint-flow/references/components/memory.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/components/middleware.md +1 -1
- package/plugins/opencode/skills/sprint-flow/references/components/skill-invocations.md +21 -20
- package/plugins/opencode/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
- package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +14 -12
- package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +145 -133
- package/plugins/opencode/skills/sprint-flow/references/phase-3-build.md +69 -2
- package/plugins/opencode/skills/sprint-flow/references/phase-4-verify.md +93 -31
- package/plugins/opencode/skills/sprint-flow/references/phase-5-ship.md +99 -14
- package/plugins/opencode/skills/sprint-flow/references/phase-6-close.md +37 -5
- package/plugins/opencode/skills/sprint-flow/references/phase-overview.md +58 -53
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/SKILL.md +8 -3
- package/plugins/opencode/skills/to-issues/SKILL.md +1 -1
- package/plugins/qoder/agents/delphi-architecture.md +48 -48
- package/plugins/qoder/agents/delphi-feasibility.md +48 -48
- package/plugins/qoder/agents/delphi-technical.md +48 -48
- package/plugins/qoder/bin/sprint-flow-guard.sh +0 -0
- package/plugins/qoder/bin/xp-gate-check +0 -0
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/scripts/__tests__/check-mattpocock-license.test.js +229 -0
- package/scripts/__tests__/sync-package-content.test.js +50 -0
- package/scripts/check-mattpocock-license.cjs +302 -0
- package/scripts/delphi-external-review.cjs +432 -432
- package/scripts/migrate-audit-anomalies.mjs +78 -0
- package/scripts/sync-package-content.js +52 -5
- package/skills/batch-grill-me/SKILL.md +15 -0
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/delphi-review/SKILL.md +59 -4
- package/skills/delphi-review/references/requirements.md +390 -0
- package/skills/delphi-review/references/round-templates.md +67 -0
- package/skills/domain-modeling/ADR-FORMAT.md +64 -0
- package/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/skills/domain-modeling/SKILL.md +74 -0
- package/skills/grill-with-docs/SKILL.md +7 -0
- package/skills/grilling/SKILL.md +12 -0
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/sprint-flow/SKILL.md +168 -43
- package/skills/sprint-flow/references/components/memory.md +1 -1
- package/skills/sprint-flow/references/components/middleware.md +1 -1
- package/skills/sprint-flow/references/components/skill-invocations.md +21 -20
- package/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
- package/skills/sprint-flow/references/orchestration-rules.md +14 -12
- package/skills/sprint-flow/references/phase-2-design.md +145 -133
- package/skills/sprint-flow/references/phase-3-build.md +69 -2
- package/skills/sprint-flow/references/phase-4-verify.md +93 -31
- package/skills/sprint-flow/references/phase-5-ship.md +99 -14
- package/skills/sprint-flow/references/phase-6-close.md +37 -5
- package/skills/sprint-flow/references/phase-overview.md +58 -53
- package/skills/test-specification-alignment/AGENTS.md +3 -3
- package/skills/test-specification-alignment/SKILL.md +8 -3
- package/skills/to-issues/SKILL.md +1 -1
- package/adapters/gate-3.sh +0 -68
- package/adapters/gate-4.sh +0 -80
- package/adapters/gate-7.sh +0 -45
- package/adapters/gate-8.sh +0 -56
- package/adapters/gate-9.sh +0 -104
package/adapter-common.sh
CHANGED
|
File without changes
|
package/adapters/cpp.sh
CHANGED
|
File without changes
|
package/adapters/dart.sh
CHANGED
|
File without changes
|
package/adapters/flutter.sh
CHANGED
|
File without changes
|
package/adapters/go.sh
CHANGED
|
File without changes
|
package/adapters/iac.sh
CHANGED
|
File without changes
|
package/adapters/java.sh
CHANGED
|
File without changes
|
package/adapters/kotlin.sh
CHANGED
|
File without changes
|
package/adapters/objectivec.sh
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/adapters/powershell.sh
CHANGED
|
File without changes
|
package/adapters/python.sh
CHANGED
|
File without changes
|
package/adapters/shell.sh
CHANGED
|
File without changes
|
package/adapters/swift.sh
CHANGED
|
File without changes
|
package/adapters/typescript.sh
CHANGED
|
File without changes
|
package/bin/xp-gate.js
CHANGED
|
@@ -147,7 +147,23 @@ const COMMANDS = {
|
|
|
147
147
|
const { handleSprintStatus } = require('../lib/sprint-status.js');
|
|
148
148
|
handleSprintStatus(subargs).then(code => process.exit(code));
|
|
149
149
|
},
|
|
150
|
-
usage: 'xp-gate sprint-status [--json] [--watch] [--dir <path>]'
|
|
150
|
+
usage: 'xp-gate sprint-status [--json] [--watch] [--dir <path>] [--rework-check] [--window-days N]'
|
|
151
|
+
},
|
|
152
|
+
'retro': {
|
|
153
|
+
description: 'Generate sprint engineering retrospective (#369)',
|
|
154
|
+
run: subargs => {
|
|
155
|
+
const { handleRetro } = require('../lib/retro.js');
|
|
156
|
+
handleRetro(subargs).then(code => process.exit(code));
|
|
157
|
+
},
|
|
158
|
+
usage: 'xp-gate retro [--days N] [--all] [--json] [--sprint <id>]'
|
|
159
|
+
},
|
|
160
|
+
'sprint-init': {
|
|
161
|
+
description: 'Initialize sprint state (single entry point for sprint creation, resolves #366)',
|
|
162
|
+
run: subargs => {
|
|
163
|
+
const { handleSprintInit } = require('../lib/sprint-init.js');
|
|
164
|
+
handleSprintInit(subargs).then(code => process.exit(code));
|
|
165
|
+
},
|
|
166
|
+
usage: 'xp-gate sprint-init "<task_description>" [--issues "<#123>"] [--force] [--dry-run] [--dir <path>]'
|
|
151
167
|
},
|
|
152
168
|
'next-sprint': {
|
|
153
169
|
description: 'Analyze remaining open issues and plan next iteration',
|
|
@@ -248,6 +264,18 @@ for (let i = 0; i <= 11; i++) {
|
|
|
248
264
|
};
|
|
249
265
|
}
|
|
250
266
|
|
|
267
|
+
// Named utility gates (analytics/report tools)
|
|
268
|
+
for (const gateId of ['python-health', 'test-layers', 'pbt']) {
|
|
269
|
+
const gateInfo = gateRunner.GATE_REGISTRY[gateId];
|
|
270
|
+
if (gateInfo) {
|
|
271
|
+
COMMANDS[gateId] = {
|
|
272
|
+
description: gateInfo.description,
|
|
273
|
+
run: subargs => gateRunner.runGate(gateId, subargs[0]).then(code => process.exit(code)),
|
|
274
|
+
usage: `xp-gate ${gateId} [<path>]`
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
251
279
|
function printHelp() {
|
|
252
280
|
console.log('xp-gate - AI development workflow tool');
|
|
253
281
|
console.log('');
|
package/gate-3.sh
CHANGED
|
File without changes
|
package/gate-4.sh
CHANGED
|
File without changes
|
package/gate-7.sh
CHANGED
|
File without changes
|
package/gate-8.sh
CHANGED
|
File without changes
|
package/gate-9.sh
CHANGED
|
File without changes
|
package/hooks/adapter-common.sh
CHANGED
|
File without changes
|
package/hooks/gate-3.sh
CHANGED
|
File without changes
|
package/hooks/gate-4.sh
CHANGED
|
File without changes
|
package/hooks/gate-7.sh
CHANGED
|
File without changes
|
package/hooks/gate-8.sh
CHANGED
|
File without changes
|
package/hooks/gate-9.sh
CHANGED
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# now_ms() — Cross-platform epoch-milliseconds timestamp (Issue #370 fix)
|
|
4
|
+
#
|
|
5
|
+
# Windows Git Bash's `date +%s%3N` outputs literal "N" (e.g. "1753001234N")
|
|
6
|
+
# and exits 0, so || fallback chains never trigger. This function:
|
|
7
|
+
# 1. Prefers `node -e 'console.log(Date.now())'` (cross-platform, always ms)
|
|
8
|
+
# 2. Validates output with ^[0-9]+$ regex
|
|
9
|
+
# 3. Falls back to `date +%s` (seconds × 1000) if node output is invalid
|
|
10
|
+
# 4. Final fallback: echo 0
|
|
11
|
+
#
|
|
12
|
+
# Source this file: source "$(dirname "${BASH_SOURCE[0]}")/lib/now-ms.sh"
|
|
13
|
+
# ============================================================================
|
|
14
|
+
|
|
15
|
+
now_ms() {
|
|
16
|
+
local ts
|
|
17
|
+
# Preferred: node (cross-platform, always produces epoch milliseconds)
|
|
18
|
+
ts=$(node -e 'console.log(Date.now())' 2>/dev/null)
|
|
19
|
+
if [[ "$ts" =~ ^[0-9]+$ ]]; then
|
|
20
|
+
echo "$ts"
|
|
21
|
+
return
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Fallback: date +%s (seconds only — multiply by 1000 for ms)
|
|
25
|
+
ts=$(date +%s 2>/dev/null)
|
|
26
|
+
if [[ "$ts" =~ ^[0-9]+$ ]]; then
|
|
27
|
+
echo $((ts * 1000))
|
|
28
|
+
return
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Final fallback
|
|
32
|
+
echo 0
|
|
33
|
+
}
|
package/hooks/pre-commit
CHANGED
|
@@ -165,6 +165,69 @@ file_exists_in_subdir() {
|
|
|
165
165
|
fi
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
# ============================================================================
|
|
169
|
+
# Audit/Timing helpers — must be defined BEFORE first gate call (line ~197)
|
|
170
|
+
# Bash executes top-to-bottom; gate_start_ms + record_gate_audit are called
|
|
171
|
+
# in every gate, so their definitions lead the first call.
|
|
172
|
+
# ============================================================================
|
|
173
|
+
AUDIT_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
174
|
+
PROJECT_ROOT_FOR_AUDIT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$AUDIT_SCRIPT_DIR/../..")"
|
|
175
|
+
|
|
176
|
+
# Issue #370: source cross-platform now_ms() helper (node-preferred, regex-validated)
|
|
177
|
+
source "${AUDIT_SCRIPT_DIR}/lib/now-ms.sh"
|
|
178
|
+
|
|
179
|
+
# Read max_duration_ms from .xp-gate-config.json (default 7200000 = 2 hours)
|
|
180
|
+
_MAX_DURATION_MS=$(node -e "try{const c=JSON.parse(require('fs').readFileSync('${PROJECT_ROOT_FOR_AUDIT}/.xp-gate-config.json','utf8'));console.log((c.audit&&c.audit.max_duration_ms)||7200000);}catch(e){console.log(7200000);}" 2>/dev/null || echo "7200000")
|
|
181
|
+
|
|
182
|
+
# Get milliseconds timestamp (used by gates for duration measurement)
|
|
183
|
+
# Issue #370: delegates to now_ms() — node-preferred, regex-validated, cross-platform
|
|
184
|
+
gate_start_ms() {
|
|
185
|
+
now_ms
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Audit helper — records gate execution to .xp-gate/audit.jsonl
|
|
189
|
+
# NEVER blocks commit on failure (try/catch around npx tsx).
|
|
190
|
+
# Usage: record_gate_audit <gate_id> <gate_name> <passed> <issues_found> <start_ms>
|
|
191
|
+
record_gate_audit() {
|
|
192
|
+
local gate_id="$1"
|
|
193
|
+
local gate_name="$2"
|
|
194
|
+
local passed="$3"
|
|
195
|
+
local issues_found="$4"
|
|
196
|
+
local start_ms="$5"
|
|
197
|
+
local end_ms
|
|
198
|
+
end_ms=$(now_ms)
|
|
199
|
+
local duration_ms=$((end_ms - start_ms))
|
|
200
|
+
if [ "$duration_ms" -lt 0 ] 2>/dev/null; then
|
|
201
|
+
duration_ms=0
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
# Issue #370: detect anomalous duration (poisoned timestamp arithmetic)
|
|
205
|
+
local duration_anomaly_flag=""
|
|
206
|
+
if [ "$duration_ms" -gt "$_MAX_DURATION_MS" ] 2>/dev/null; then
|
|
207
|
+
duration_anomaly_flag="--duration-anomaly true"
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# Determine gate result: PASS or FAIL based on status variable
|
|
211
|
+
local gate_result="true"
|
|
212
|
+
if [ "$passed" != "PASS" ] && [ "$passed" != "SKIP" ]; then
|
|
213
|
+
gate_result="false"
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
# Fire-and-forget: audit must never block the commit
|
|
217
|
+
{
|
|
218
|
+
npx tsx "${PROJECT_ROOT_FOR_AUDIT}/src/npm-package/lib/gate-audit.ts" record \
|
|
219
|
+
--gate-id "$gate_id" \
|
|
220
|
+
--gate-name "$gate_name" \
|
|
221
|
+
--passed "$gate_result" \
|
|
222
|
+
--issues-found "$issues_found" \
|
|
223
|
+
--duration-ms "$duration_ms" \
|
|
224
|
+
$duration_anomaly_flag \
|
|
225
|
+
--trigger commit \
|
|
226
|
+
2>/dev/null
|
|
227
|
+
} &
|
|
228
|
+
disown 2>/dev/null || true
|
|
229
|
+
}
|
|
230
|
+
|
|
168
231
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
169
232
|
echo " QUALITY GATES - PRE-COMMIT CHECK"
|
|
170
233
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
@@ -713,53 +776,6 @@ has_project_lang() {
|
|
|
713
776
|
done
|
|
714
777
|
return 1
|
|
715
778
|
}
|
|
716
|
-
|
|
717
|
-
# ============================================================================
|
|
718
|
-
# Audit helper — records gate execution to .xp-gate/audit.jsonl
|
|
719
|
-
# NEVER blocks commit on failure (try/catch around npx tsx).
|
|
720
|
-
# Usage: record_gate_audit <gate_id> <gate_name> <passed> <issues_found> <start_ms>
|
|
721
|
-
# ============================================================================
|
|
722
|
-
AUDIT_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
723
|
-
PROJECT_ROOT_FOR_AUDIT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$AUDIT_SCRIPT_DIR/../..")"
|
|
724
|
-
|
|
725
|
-
record_gate_audit() {
|
|
726
|
-
local gate_id="$1"
|
|
727
|
-
local gate_name="$2"
|
|
728
|
-
local passed="$3"
|
|
729
|
-
local issues_found="$4"
|
|
730
|
-
local start_ms="$5"
|
|
731
|
-
local end_ms
|
|
732
|
-
end_ms=$(date +%s%3N 2>/dev/null || node -e "console.log(Date.now())" 2>/dev/null || echo "0")
|
|
733
|
-
local duration_ms=$((end_ms - start_ms))
|
|
734
|
-
if [ "$duration_ms" -lt 0 ] 2>/dev/null; then
|
|
735
|
-
duration_ms=0
|
|
736
|
-
fi
|
|
737
|
-
|
|
738
|
-
# Determine gate result: PASS or FAIL based on status variable
|
|
739
|
-
local gate_result="true"
|
|
740
|
-
if [ "$passed" != "PASS" ] && [ "$passed" != "SKIP" ]; then
|
|
741
|
-
gate_result="false"
|
|
742
|
-
fi
|
|
743
|
-
|
|
744
|
-
# Fire-and-forget: audit must never block the commit
|
|
745
|
-
{
|
|
746
|
-
npx tsx "${PROJECT_ROOT_FOR_AUDIT}/src/npm-package/lib/gate-audit.ts" record \
|
|
747
|
-
--gate-id "$gate_id" \
|
|
748
|
-
--gate-name "$gate_name" \
|
|
749
|
-
--passed "$gate_result" \
|
|
750
|
-
--issues-found "$issues_found" \
|
|
751
|
-
--duration-ms "$duration_ms" \
|
|
752
|
-
--trigger commit \
|
|
753
|
-
2>/dev/null
|
|
754
|
-
} &
|
|
755
|
-
disown 2>/dev/null || true
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
# Get milliseconds timestamp (used by gates for duration measurement)
|
|
759
|
-
gate_start_ms() {
|
|
760
|
-
date +%s%3N 2>/dev/null || node -e "console.log(Date.now())" 2>/dev/null || echo "0"
|
|
761
|
-
}
|
|
762
|
-
|
|
763
779
|
# Switch to subdirectory if detected (for single-language subdirectory projects).
|
|
764
780
|
# Multi-language projects stay at root — each adapter handles its own path.
|
|
765
781
|
ORIGINAL_DIR=""
|
package/hooks/pre-push
CHANGED
|
File without changes
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# sprint-gate.sh — Sprint Flow Validation Gate
|
|
3
|
+
#
|
|
4
|
+
# PURPOSE: Enforce Sprint Flow phase discipline at git hook level.
|
|
5
|
+
# Validates delphi-review approval, sprint state, and specification
|
|
6
|
+
# artifacts before allowing commits and pushes on sprint branches.
|
|
7
|
+
#
|
|
8
|
+
# INTEGRATION: Called from pre-commit (Gate 10) and pre-push (Gate S),
|
|
9
|
+
# or run independently for CI/manual validation.
|
|
10
|
+
#
|
|
11
|
+
# USAGE:
|
|
12
|
+
# sprint-gate.sh --pre-commit # Validate before commit
|
|
13
|
+
# sprint-gate.sh --pre-push # Validate before push
|
|
14
|
+
#
|
|
15
|
+
# MECHANISM:
|
|
16
|
+
# --pre-commit:
|
|
17
|
+
# - If .sprint-state/ missing → SKIP (not a sprint project)
|
|
18
|
+
# - If sprint-state.json missing/invalid → SKIP (non-sprint commit)
|
|
19
|
+
# - If phase >= 1 (PLAN completed) and delphi-reviewed.json missing → DENY
|
|
20
|
+
# - If delphi-reviewed.json exists, verdict must be "APPROVED"
|
|
21
|
+
# - If jq AND node unavailable → WARN but ALLOW (graceful degradation)
|
|
22
|
+
# - If jq unavailable but node available → use Node.js JSON parser fallback
|
|
23
|
+
#
|
|
24
|
+
# --pre-push:
|
|
25
|
+
# - If .sprint-state/ missing → SKIP
|
|
26
|
+
# - If branch doesn't match sprint/* → SKIP
|
|
27
|
+
# - If delphi-reviewed.json missing or verdict != "APPROVED" → DENY
|
|
28
|
+
# - If phase < 2 (BUILD not started) → DENY
|
|
29
|
+
# - If phase >= 2 and specification.yaml missing → DENY
|
|
30
|
+
# - If jq AND node unavailable → DENY (cannot verify verdict)
|
|
31
|
+
#
|
|
32
|
+
# GRACEFUL DEGRADATION:
|
|
33
|
+
# - If .sprint-state/ directory doesn't exist → SKIP (not a sprint project)
|
|
34
|
+
# - If sprint-state.json is missing/invalid → SKIP (non-sprint commit)
|
|
35
|
+
# - JSON parser fallback chain: jq → Node.js → explicit degradation message
|
|
36
|
+
#
|
|
37
|
+
# OUTPUT: JSON to stdout — {"decision":"allow|deny|skip",...}
|
|
38
|
+
# EXIT: 0 = pass/skip, 1 = block
|
|
39
|
+
|
|
40
|
+
set -euo pipefail
|
|
41
|
+
|
|
42
|
+
# --- Argument parsing ---
|
|
43
|
+
MODE=""
|
|
44
|
+
case "${1:-}" in
|
|
45
|
+
--pre-commit) MODE="pre-commit" ;;
|
|
46
|
+
--pre-push) MODE="pre-push" ;;
|
|
47
|
+
*)
|
|
48
|
+
echo '{"decision":"deny","reason":"Usage: sprint-gate.sh --pre-commit | --pre-push"}'
|
|
49
|
+
exit 1
|
|
50
|
+
;;
|
|
51
|
+
esac
|
|
52
|
+
|
|
53
|
+
# --- Locate repo root and sprint state ---
|
|
54
|
+
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || {
|
|
55
|
+
echo '{"decision":"skip","reason":"not inside a git repository"}'
|
|
56
|
+
exit 0
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
SPRINT_STATE_DIR="$REPO_ROOT/.sprint-state"
|
|
60
|
+
SPRINT_STATE_JSON="$SPRINT_STATE_DIR/sprint-state.json"
|
|
61
|
+
APPROVED_FILE="$SPRINT_STATE_DIR/delphi-reviewed.json"
|
|
62
|
+
|
|
63
|
+
# If no .sprint-state directory, this isn't a sprint project → SKIP
|
|
64
|
+
if [ ! -d "$SPRINT_STATE_DIR" ]; then
|
|
65
|
+
echo '{"decision":"skip","reason":"not a sprint project"}'
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# --- Helper: check tool availability ---
|
|
70
|
+
HAS_JQ=false
|
|
71
|
+
if command -v jq &>/dev/null; then
|
|
72
|
+
HAS_JQ=true
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
HAS_NODE=false
|
|
76
|
+
if command -v node &>/dev/null; then
|
|
77
|
+
HAS_NODE=true
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# JSON_PARSER: "jq" | "node" | "none"
|
|
81
|
+
if $HAS_JQ; then
|
|
82
|
+
JSON_PARSER="jq"
|
|
83
|
+
elif $HAS_NODE; then
|
|
84
|
+
JSON_PARSER="node"
|
|
85
|
+
else
|
|
86
|
+
JSON_PARSER="none"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# --- Helper: validate JSON file ---
|
|
90
|
+
# Returns 0 if valid, 1 if invalid or no parser available
|
|
91
|
+
json_validate() {
|
|
92
|
+
local file="$1"
|
|
93
|
+
if [ ! -f "$file" ]; then
|
|
94
|
+
return 1
|
|
95
|
+
fi
|
|
96
|
+
case "$JSON_PARSER" in
|
|
97
|
+
jq)
|
|
98
|
+
jq empty "$file" 2>/dev/null
|
|
99
|
+
return $?
|
|
100
|
+
;;
|
|
101
|
+
node)
|
|
102
|
+
node -e "try{JSON.parse(require('fs').readFileSync('$file','utf8'));process.exit(0)}catch(e){process.exit(1)}" 2>/dev/null
|
|
103
|
+
return $?
|
|
104
|
+
;;
|
|
105
|
+
*)
|
|
106
|
+
return 1
|
|
107
|
+
;;
|
|
108
|
+
esac
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# --- Helper: extract JSON field ---
|
|
112
|
+
# Usage: json_extract <file> <jq-filter>
|
|
113
|
+
# jq-filter is a jq expression like '.phase // -1' or '.verdict // ""'
|
|
114
|
+
# Falls back to Node.js when jq is unavailable
|
|
115
|
+
json_extract() {
|
|
116
|
+
local file="$1"
|
|
117
|
+
local filter="$2"
|
|
118
|
+
case "$JSON_PARSER" in
|
|
119
|
+
jq)
|
|
120
|
+
jq -r "$filter" "$file" 2>/dev/null
|
|
121
|
+
return $?
|
|
122
|
+
;;
|
|
123
|
+
node)
|
|
124
|
+
# Translate simple jq filters to Node.js
|
|
125
|
+
# Supports: .field // default patterns
|
|
126
|
+
local field default_val
|
|
127
|
+
field=$(echo "$filter" | sed 's/^\.//;s/ *\/\/.*$//')
|
|
128
|
+
default_val=$(echo "$filter" | sed -n 's/.*\/\/ *//p')
|
|
129
|
+
node -e "
|
|
130
|
+
try {
|
|
131
|
+
var d=JSON.parse(require('fs').readFileSync('$file','utf8'));
|
|
132
|
+
var v=d['${field}'];
|
|
133
|
+
if(v===undefined||v===null){v=${default_val:-null}}
|
|
134
|
+
process.stdout.write(String(v));
|
|
135
|
+
}catch(e){process.stdout.write('${default_val:-}');process.exit(1)}
|
|
136
|
+
" 2>/dev/null
|
|
137
|
+
return $?
|
|
138
|
+
;;
|
|
139
|
+
*)
|
|
140
|
+
echo ""
|
|
141
|
+
return 1
|
|
142
|
+
;;
|
|
143
|
+
esac
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# --- Helper: read phase from sprint-state.json ---
|
|
147
|
+
# Returns phase number or -1 if unreadable
|
|
148
|
+
read_phase() {
|
|
149
|
+
if [ ! -f "$SPRINT_STATE_JSON" ]; then
|
|
150
|
+
echo "-1"
|
|
151
|
+
return
|
|
152
|
+
fi
|
|
153
|
+
if [ "$JSON_PARSER" = "none" ]; then
|
|
154
|
+
echo "-1"
|
|
155
|
+
return
|
|
156
|
+
fi
|
|
157
|
+
if ! json_validate "$SPRINT_STATE_JSON"; then
|
|
158
|
+
echo "-1"
|
|
159
|
+
return
|
|
160
|
+
fi
|
|
161
|
+
local phase
|
|
162
|
+
phase=$(json_extract "$SPRINT_STATE_JSON" '.phase // -1') || phase="-1"
|
|
163
|
+
echo "$phase"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# --- Helper: read delphi verdict ---
|
|
167
|
+
# Returns verdict string or empty if unreadable
|
|
168
|
+
read_verdict() {
|
|
169
|
+
if [ ! -f "$APPROVED_FILE" ]; then
|
|
170
|
+
echo ""
|
|
171
|
+
return
|
|
172
|
+
fi
|
|
173
|
+
if [ "$JSON_PARSER" = "none" ]; then
|
|
174
|
+
echo ""
|
|
175
|
+
return
|
|
176
|
+
fi
|
|
177
|
+
if ! json_validate "$APPROVED_FILE"; then
|
|
178
|
+
echo ""
|
|
179
|
+
return
|
|
180
|
+
fi
|
|
181
|
+
json_extract "$APPROVED_FILE" '.verdict // ""' || echo ""
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# --- Pre-commit mode ---
|
|
185
|
+
if [ "$MODE" = "pre-commit" ]; then
|
|
186
|
+
PHASE=$(read_phase)
|
|
187
|
+
|
|
188
|
+
# If sprint-state.json is missing or invalid, skip (non-sprint commit)
|
|
189
|
+
if [ "$PHASE" = "-1" ]; then
|
|
190
|
+
if [ ! -f "$SPRINT_STATE_JSON" ]; then
|
|
191
|
+
echo '{"decision":"skip","reason":"sprint-state.json not found, non-sprint commit"}'
|
|
192
|
+
else
|
|
193
|
+
echo '{"decision":"skip","warning":"sprint-state.json is not valid JSON or no JSON parser available (install jq or node), allowing commit"}'
|
|
194
|
+
fi
|
|
195
|
+
exit 0
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# Phase >= 1 means PLAN is completed — delphi-review must be APPROVED
|
|
199
|
+
if [ "$PHASE" -ge 1 ] 2>/dev/null; then
|
|
200
|
+
# Check if delphi-reviewed.json exists
|
|
201
|
+
if [ ! -f "$APPROVED_FILE" ]; then
|
|
202
|
+
echo '{"decision":"deny","reason":"delphi-review not APPROVED. Complete Phase 1 before committing code changes."}'
|
|
203
|
+
exit 1
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# If no JSON parser available, warn but allow (graceful degradation)
|
|
207
|
+
if [ "$JSON_PARSER" = "none" ]; then
|
|
208
|
+
echo '{"decision":"allow","warning":"neither jq nor node available, cannot verify delphi-review verdict. Install jq or node for full protection."}'
|
|
209
|
+
exit 0
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# Validate delphi-reviewed.json is valid JSON
|
|
213
|
+
if ! json_validate "$APPROVED_FILE"; then
|
|
214
|
+
echo '{"decision":"deny","reason":"delphi-reviewed.json is not valid JSON. Re-run: /delphi-review"}'
|
|
215
|
+
exit 1
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# Check verdict
|
|
219
|
+
VERDICT=$(read_verdict)
|
|
220
|
+
if [ "$VERDICT" != "APPROVED" ]; then
|
|
221
|
+
echo "{\"decision\":\"deny\",\"reason\":\"delphi-review verdict is '${VERDICT}', not APPROVED. Fix issues and re-run: /delphi-review\"}"
|
|
222
|
+
exit 1
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# All checks passed
|
|
226
|
+
echo "{\"decision\":\"allow\",\"message\":\"delphi-review APPROVED, sprint phase ${PHASE} validated\"}"
|
|
227
|
+
exit 0
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# Phase < 1 (pre-PLAN) — no delphi enforcement needed
|
|
231
|
+
echo "{\"decision\":\"allow\",\"reason\":\"sprint phase ${PHASE} (pre-PLAN), no delphi enforcement required\"}"
|
|
232
|
+
exit 0
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# --- Pre-push mode ---
|
|
236
|
+
if [ "$MODE" = "pre-push" ]; then
|
|
237
|
+
# Only enforce on sprint/* branches
|
|
238
|
+
BRANCH="$(git branch --show-current 2>/dev/null)" || BRANCH=""
|
|
239
|
+
case "$BRANCH" in
|
|
240
|
+
sprint/*) ;; # continue
|
|
241
|
+
*)
|
|
242
|
+
echo "{\"decision\":\"skip\",\"reason\":\"branch '${BRANCH}' does not match sprint/* pattern\"}"
|
|
243
|
+
exit 0
|
|
244
|
+
;;
|
|
245
|
+
esac
|
|
246
|
+
|
|
247
|
+
# Validate sprint-state.json exists and is readable
|
|
248
|
+
PHASE=$(read_phase)
|
|
249
|
+
if [ "$PHASE" = "-1" ] && [ ! -f "$SPRINT_STATE_JSON" ]; then
|
|
250
|
+
echo "{\"decision\":\"deny\",\"reason\":\"sprint-state.json not found on sprint branch '${BRANCH}'\"}"
|
|
251
|
+
exit 1
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
# Delphi review must be APPROVED
|
|
255
|
+
if [ ! -f "$APPROVED_FILE" ]; then
|
|
256
|
+
echo '{"decision":"deny","reason":"delphi-reviewed.json not found. Complete Phase 1 delphi-review before pushing."}'
|
|
257
|
+
exit 1
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
if [ "$JSON_PARSER" = "none" ]; then
|
|
261
|
+
echo '{"decision":"deny","reason":"neither jq nor node available, cannot verify delphi-review verdict. Install jq or node to push from sprint branch."}'
|
|
262
|
+
exit 1
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
if ! json_validate "$APPROVED_FILE"; then
|
|
266
|
+
echo '{"decision":"deny","reason":"delphi-reviewed.json is not valid JSON. Re-run: /delphi-review"}'
|
|
267
|
+
exit 1
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
VERDICT=$(read_verdict)
|
|
271
|
+
if [ "$VERDICT" != "APPROVED" ]; then
|
|
272
|
+
echo "{\"decision\":\"deny\",\"reason\":\"delphi-review verdict is '${VERDICT}', not APPROVED. Fix issues and re-run: /delphi-review\"}"
|
|
273
|
+
exit 1
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
# specification.yaml must exist (Phase 1 PLAN output)
|
|
277
|
+
SPEC_FILE="$REPO_ROOT/specification.yaml"
|
|
278
|
+
if [ ! -f "$SPEC_FILE" ]; then
|
|
279
|
+
# Also check sprint-state output directory
|
|
280
|
+
SPEC_FILE="$SPRINT_STATE_DIR/phase-outputs/specification.yaml"
|
|
281
|
+
if [ ! -f "$SPEC_FILE" ]; then
|
|
282
|
+
echo '{"decision":"deny","reason":"specification.yaml not found. Phase 1 PLAN must produce specification.yaml before pushing."}'
|
|
283
|
+
exit 1
|
|
284
|
+
fi
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
# All checks passed — comprehensive success output
|
|
288
|
+
SPEC_SIZE=$(wc -c < "$SPEC_FILE" 2>/dev/null | tr -d ' ')
|
|
289
|
+
echo "{\"decision\":\"allow\",\"message\":\"sprint push validated\",\"branch\":\"${BRANCH}\",\"phase\":${PHASE},\"verdict\":\"APPROVED\",\"specification_size_bytes\":${SPEC_SIZE:-0}}"
|
|
290
|
+
exit 0
|
|
291
|
+
fi
|