javi-forge 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -1
- package/ci-local/README.md +56 -11
- package/ci-local/ci-local.ps1 +445 -0
- package/ci-local/ci-local.sh +52 -9
- package/ci-local/docker/python.Dockerfile +7 -0
- package/ci-local/hooks/commit-msg +120 -44
- package/ci-local/hooks/commit-msg.test.sh +286 -0
- package/ci-local/hooks/pre-commit +9 -1
- package/ci-local/hooks/pre-push +13 -2
- package/ci-local/install.ps1 +367 -0
- package/ci-local/install.sh +151 -5
- package/ci-local/uninstall.ps1 +85 -0
- package/ci-local/uninstall.sh +95 -0
- package/dist/cli/dispatch/ci.d.ts +12 -0
- package/dist/cli/dispatch/ci.js +38 -0
- package/dist/cli/dispatch/security.d.ts +11 -0
- package/dist/cli/dispatch/security.js +68 -0
- package/dist/cli/dispatch/simple-renderers.d.ts +16 -0
- package/dist/cli/dispatch/simple-renderers.js +65 -0
- package/dist/cli/dispatch/skill-publish.d.ts +15 -0
- package/dist/cli/dispatch/skill-publish.js +44 -0
- package/dist/cli/dispatch/skills-cmd.d.ts +13 -0
- package/dist/cli/dispatch/skills-cmd.js +93 -0
- package/dist/cli/dispatch/tdd.d.ts +11 -0
- package/dist/cli/dispatch/tdd.js +45 -0
- package/dist/cli/dispatch/types.d.ts +29 -0
- package/dist/cli/dispatch/types.js +2 -0
- package/dist/cli/dispatch/workflow.d.ts +11 -0
- package/dist/cli/dispatch/workflow.js +67 -0
- package/dist/cli/help.d.ts +127 -0
- package/dist/cli/help.js +138 -0
- package/dist/cli/runtime.d.ts +16 -0
- package/dist/cli/runtime.js +38 -0
- package/dist/cli/validators.d.ts +17 -0
- package/dist/cli/validators.js +35 -0
- package/dist/commands/analyze.js +1 -3
- package/dist/commands/ci.d.ts +47 -0
- package/dist/commands/ci.js +380 -63
- package/dist/commands/doctor.js +2 -4
- package/dist/commands/init/report.d.ts +13 -0
- package/dist/commands/init/report.js +13 -0
- package/dist/commands/init/steps/agent-skills.d.ts +13 -0
- package/dist/commands/init/steps/agent-skills.js +45 -0
- package/dist/commands/init/steps/ai-sync.d.ts +15 -0
- package/dist/commands/init/steps/ai-sync.js +62 -0
- package/dist/commands/init/steps/ci.d.ts +24 -0
- package/dist/commands/init/steps/ci.js +74 -0
- package/dist/commands/init/steps/claude-md.d.ts +14 -0
- package/dist/commands/init/steps/claude-md.js +54 -0
- package/dist/commands/init/steps/code-graph.d.ts +16 -0
- package/dist/commands/init/steps/code-graph.js +68 -0
- package/dist/commands/init/steps/context-dir.d.ts +13 -0
- package/dist/commands/init/steps/context-dir.js +47 -0
- package/dist/commands/init/steps/docker-deploy.d.ts +16 -0
- package/dist/commands/init/steps/docker-deploy.js +60 -0
- package/dist/commands/init/steps/ghagga.d.ts +15 -0
- package/dist/commands/init/steps/ghagga.js +58 -0
- package/dist/commands/init/steps/git.d.ts +25 -0
- package/dist/commands/init/steps/git.js +81 -0
- package/dist/commands/init/steps/gitignore.d.ts +13 -0
- package/dist/commands/init/steps/gitignore.js +39 -0
- package/dist/commands/init/steps/local-ai.d.ts +14 -0
- package/dist/commands/init/steps/local-ai.js +53 -0
- package/dist/commands/init/steps/manifest.d.ts +13 -0
- package/dist/commands/init/steps/manifest.js +58 -0
- package/dist/commands/init/steps/memory.d.ts +15 -0
- package/dist/commands/init/steps/memory.js +60 -0
- package/dist/commands/init/steps/mock.d.ts +13 -0
- package/dist/commands/init/steps/mock.js +64 -0
- package/dist/commands/init/steps/sdd.d.ts +12 -0
- package/dist/commands/init/steps/sdd.js +40 -0
- package/dist/commands/init/steps/security.d.ts +28 -0
- package/dist/commands/init/steps/security.js +103 -0
- package/dist/commands/init/types.d.ts +30 -0
- package/dist/commands/init/types.js +2 -0
- package/dist/commands/init.d.ts +5 -4
- package/dist/commands/init.js +42 -630
- package/dist/commands/llmstxt.js +1 -1
- package/dist/commands/plugin.js +1 -1
- package/dist/commands/security.js +23 -4
- package/dist/commands/skills/analysis.d.ts +19 -0
- package/dist/commands/skills/analysis.js +211 -0
- package/dist/commands/skills/benchmark.d.ts +6 -0
- package/dist/commands/skills/benchmark.js +82 -0
- package/dist/commands/skills/constants.d.ts +10 -0
- package/dist/commands/skills/constants.js +27 -0
- package/dist/commands/skills/directives.d.ts +21 -0
- package/dist/commands/skills/directives.js +101 -0
- package/dist/commands/skills/parsing.d.ts +16 -0
- package/dist/commands/skills/parsing.js +108 -0
- package/dist/commands/skills/rules.d.ts +5 -0
- package/dist/commands/skills/rules.js +32 -0
- package/dist/commands/skills/scoring.d.ts +47 -0
- package/dist/commands/skills/scoring.js +306 -0
- package/dist/commands/skills.d.ts +7 -125
- package/dist/commands/skills.js +8 -874
- package/dist/commands/tdd-pipeline.js +1 -1
- package/dist/commands/tdd.js +1 -1
- package/dist/commands/workflow.js +2 -0
- package/dist/constants.js +2 -2
- package/dist/index.js +26 -473
- package/dist/lib/agent-skills.js +1 -1
- package/dist/{commands → lib}/atlassian-mcp.js +2 -2
- package/dist/lib/auto-skill-install.js +2 -3
- package/dist/lib/auto-wire.js +7 -7
- package/dist/lib/ci-config.d.ts +65 -0
- package/dist/lib/ci-config.js +297 -0
- package/dist/lib/claudemd.js +1 -1
- package/dist/lib/codex-export.js +4 -8
- package/dist/lib/common.js +2 -2
- package/dist/lib/context.js +2 -2
- package/dist/{commands → lib}/crash-recovery.js +4 -4
- package/dist/lib/docker.d.ts +22 -1
- package/dist/lib/docker.js +57 -49
- package/dist/lib/exec.d.ts +10 -0
- package/dist/lib/exec.js +11 -0
- package/dist/lib/frontmatter.js +2 -2
- package/dist/{commands → lib}/harness-audit.js +1 -1
- package/dist/{commands → lib}/parallel-batch.js +1 -3
- package/dist/lib/plugin.js +2 -4
- package/dist/{commands → lib}/secret-scanner.js +3 -1
- package/dist/{commands → lib}/security-analysis.js +4 -2
- package/dist/lib/skill-publish.js +23 -5
- package/dist/{commands → lib}/skill-scanner.d.ts +1 -1
- package/dist/{commands → lib}/skill-scanner.js +3 -3
- package/dist/lib/stack-detector.js +3 -3
- package/dist/{commands → lib}/supply-chain.js +1 -1
- package/dist/lib/template.js +2 -2
- package/dist/lib/workflow/discovery.d.ts +8 -0
- package/dist/lib/workflow/discovery.js +18 -1
- package/dist/lib/workflow/parser.js +15 -15
- package/dist/lib/workflow/validator.js +4 -4
- package/dist/tasks/index.d.ts +3 -3
- package/dist/tasks/index.js +3 -3
- package/dist/tasks/scaffold-tasks.d.ts +1 -1
- package/dist/tasks/scaffold-tasks.js +10 -10
- package/dist/tasks/task-id.d.ts +1 -1
- package/dist/tasks/task-id.js +9 -5
- package/dist/tasks/task-tracker.d.ts +1 -1
- package/dist/tasks/task-tracker.js +7 -7
- package/dist/ui/App.js +1 -1
- package/dist/ui/CIContext.js +1 -2
- package/dist/ui/Header.js +4 -4
- package/dist/ui/NameInput.js +1 -1
- package/dist/ui/Progress.d.ts +1 -1
- package/dist/ui/Progress.js +1 -1
- package/dist/ui/StackSelector.js +1 -1
- package/lib/common.psm1 +167 -0
- package/lib/common.sh +30 -0
- package/package.json +38 -19
- /package/dist/{commands → lib}/atlassian-mcp.d.ts +0 -0
- /package/dist/{commands → lib}/crash-recovery.d.ts +0 -0
- /package/dist/{commands → lib}/harness-audit.d.ts +0 -0
- /package/dist/{commands → lib}/parallel-batch.d.ts +0 -0
- /package/dist/{commands → lib}/secret-scanner.d.ts +0 -0
- /package/dist/{commands → lib}/security-analysis.d.ts +0 -0
- /package/dist/{commands → lib}/supply-chain.d.ts +0 -0
- /package/dist/{commands → lib}/team-presets.d.ts +0 -0
- /package/dist/{commands → lib}/team-presets.js +0 -0
package/ci-local/ci-local.sh
CHANGED
|
@@ -16,17 +16,45 @@ set -e
|
|
|
16
16
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
17
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
18
18
|
|
|
19
|
-
#
|
|
19
|
+
# Require realpath: load-bearing for the lib symlink check below.
|
|
20
|
+
if ! command -v realpath >/dev/null 2>&1; then
|
|
21
|
+
printf 'ERROR: realpath is required (install coreutils).\n' >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Source shared library for colors and detect_stack.
|
|
20
26
|
# Look inside ci-local/lib first (self-contained), fallback to project-level lib/
|
|
27
|
+
LIB_CANDIDATE=""
|
|
21
28
|
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
|
|
22
|
-
|
|
29
|
+
LIB_CANDIDATE="$SCRIPT_DIR/lib/common.sh"
|
|
23
30
|
elif [ -f "$SCRIPT_DIR/../lib/common.sh" ]; then
|
|
24
|
-
|
|
31
|
+
LIB_CANDIDATE="$SCRIPT_DIR/../lib/common.sh"
|
|
25
32
|
else
|
|
26
33
|
echo "ERROR: lib/common.sh not found"
|
|
27
34
|
exit 1
|
|
28
35
|
fi
|
|
29
36
|
|
|
37
|
+
# SECURITY: validate lib/common.sh before sourcing. A hostile symlink runs
|
|
38
|
+
# attacker code at user privilege as soon as `source` evaluates the file.
|
|
39
|
+
# Audit round 4 (2026-05-17) PoC verified the same hijack on install.sh.
|
|
40
|
+
_LIB_REAL=$(realpath "$LIB_CANDIDATE")
|
|
41
|
+
_PROJECT_REAL=$(realpath "$PROJECT_DIR")
|
|
42
|
+
case "$_LIB_REAL" in
|
|
43
|
+
"$_PROJECT_REAL"|"$_PROJECT_REAL"/*) ;;
|
|
44
|
+
*)
|
|
45
|
+
printf 'ERROR: lib/common.sh resolves outside the project root:\n' >&2
|
|
46
|
+
printf ' LIB resolved: %s\n' "$_LIB_REAL" >&2
|
|
47
|
+
printf ' PROJECT resolved: %s\n' "$_PROJECT_REAL" >&2
|
|
48
|
+
exit 1
|
|
49
|
+
;;
|
|
50
|
+
esac
|
|
51
|
+
|
|
52
|
+
# Source the RESOLVED path to close the TOCTOU window between validation
|
|
53
|
+
# and source (round 5 finding). Preserve the variables under temp names
|
|
54
|
+
# in case any downstream caller relies on them.
|
|
55
|
+
source "$_LIB_REAL"
|
|
56
|
+
unset _LIB_REAL _PROJECT_REAL
|
|
57
|
+
|
|
30
58
|
# =============================================================================
|
|
31
59
|
# CI COMMAND SETUP (extends shared detect_stack with CI-specific commands)
|
|
32
60
|
# =============================================================================
|
|
@@ -72,9 +100,13 @@ setup_ci_commands() {
|
|
|
72
100
|
else
|
|
73
101
|
COMPILE_CMD=""
|
|
74
102
|
fi
|
|
75
|
-
# Only set test if test script exists
|
|
103
|
+
# Only set test if test script exists. If a "test:hooks" script
|
|
104
|
+
# is present, chain it so the commit-msg hook stays under coverage.
|
|
76
105
|
if grep -q '"test"' "$PROJECT_DIR/package.json" 2>/dev/null; then
|
|
77
106
|
TEST_CMD="$BUILD_TOOL test"
|
|
107
|
+
if grep -q '"test:hooks"' "$PROJECT_DIR/package.json" 2>/dev/null; then
|
|
108
|
+
TEST_CMD="$TEST_CMD && $BUILD_TOOL run test:hooks"
|
|
109
|
+
fi
|
|
78
110
|
else
|
|
79
111
|
TEST_CMD=""
|
|
80
112
|
fi
|
|
@@ -122,6 +154,17 @@ create_dockerfile() {
|
|
|
122
154
|
local docker_dir="$SCRIPT_DIR/docker"
|
|
123
155
|
mkdir -p "$docker_dir"
|
|
124
156
|
|
|
157
|
+
# Base image digests are pinned for deterministic CI. Refresh with
|
|
158
|
+
# `docker pull <img> && docker inspect <img>` and update both
|
|
159
|
+
# ci-local.sh and ci-local.ps1 in lockstep (the bytes must match for
|
|
160
|
+
# the dockerfile-hash cache to align across platforms).
|
|
161
|
+
#
|
|
162
|
+
# Java is left tag-only because JAVA_VERSION is a build-arg that lets
|
|
163
|
+
# the user pick a JDK version at runtime; pinning the digest would
|
|
164
|
+
# break any version other than 21. Default JAVA_VERSION=21 digest
|
|
165
|
+
# for reference (2026-05-18):
|
|
166
|
+
# eclipse-temurin:21-jdk-noble
|
|
167
|
+
# @sha256:1f5835564a8c5cee3e9e849a63e9b04ce5c7083070e4cbcaabbcdc36d9147450
|
|
125
168
|
case "$STACK_TYPE" in
|
|
126
169
|
java-gradle|java-maven)
|
|
127
170
|
cat > "$docker_dir/$DOCKERFILE" << 'DOCKERFILE'
|
|
@@ -137,7 +180,7 @@ DOCKERFILE
|
|
|
137
180
|
;;
|
|
138
181
|
node)
|
|
139
182
|
cat > "$docker_dir/$DOCKERFILE" << 'DOCKERFILE'
|
|
140
|
-
FROM node:22-slim
|
|
183
|
+
FROM node:22-slim@sha256:689c11043dad91472750cd824c97dd5e2318e9dd6f954e492fe7af0135d33ceb
|
|
141
184
|
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
|
142
185
|
RUN npm install -g pnpm
|
|
143
186
|
RUN useradd -m -s /bin/bash runner
|
|
@@ -148,7 +191,7 @@ DOCKERFILE
|
|
|
148
191
|
;;
|
|
149
192
|
python)
|
|
150
193
|
cat > "$docker_dir/$DOCKERFILE" << 'DOCKERFILE'
|
|
151
|
-
FROM python:3.12-slim
|
|
194
|
+
FROM python:3.12-slim@sha256:401f6e1a67dad31a1bd78e9ad22d0ee0a3b52154e6bd30e90be696bb6a3d7461
|
|
152
195
|
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
|
153
196
|
RUN pip install --no-cache-dir pytest ruff pylint poetry
|
|
154
197
|
RUN useradd -m -s /bin/bash runner
|
|
@@ -159,7 +202,7 @@ DOCKERFILE
|
|
|
159
202
|
;;
|
|
160
203
|
go)
|
|
161
204
|
cat > "$docker_dir/$DOCKERFILE" << 'DOCKERFILE'
|
|
162
|
-
FROM golang:1.23-bookworm
|
|
205
|
+
FROM golang:1.23-bookworm@sha256:167053a2bb901972bf2c1611f8f52c44d5fe7e762e5cab213708d82c421614db
|
|
163
206
|
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
|
164
207
|
RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0 && \
|
|
165
208
|
mv /root/go/bin/golangci-lint /usr/local/bin/
|
|
@@ -171,7 +214,7 @@ DOCKERFILE
|
|
|
171
214
|
;;
|
|
172
215
|
rust)
|
|
173
216
|
cat > "$docker_dir/$DOCKERFILE" << 'DOCKERFILE'
|
|
174
|
-
FROM rust:1.83-slim
|
|
217
|
+
FROM rust:1.83-slim@sha256:540c902e99c384163b688bbd8b5b8520e94e7731b27f7bd0eaa56ae1960627ab
|
|
175
218
|
RUN apt-get update && apt-get install -y git pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*
|
|
176
219
|
RUN rustup component add clippy rustfmt
|
|
177
220
|
RUN useradd -m -s /bin/bash runner
|
|
@@ -182,7 +225,7 @@ DOCKERFILE
|
|
|
182
225
|
;;
|
|
183
226
|
*)
|
|
184
227
|
cat > "$docker_dir/$DOCKERFILE" << 'DOCKERFILE'
|
|
185
|
-
FROM ubuntu:24.04
|
|
228
|
+
FROM ubuntu:24.04@sha256:c4a8d5503dfb2a3eb8ab5f807da5bc69a85730fb49b5cfca2330194ebcc41c7b
|
|
186
229
|
RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*
|
|
187
230
|
RUN useradd -m -s /bin/bash runner
|
|
188
231
|
USER runner
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
FROM python:3.12-slim
|
|
2
|
+
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
|
3
|
+
RUN pip install --no-cache-dir pytest ruff pylint poetry
|
|
4
|
+
RUN useradd -m -s /bin/bash runner
|
|
5
|
+
USER runner
|
|
6
|
+
WORKDIR /home/runner/work
|
|
7
|
+
ENTRYPOINT ["/bin/bash", "-c"]
|
|
@@ -1,68 +1,145 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -e
|
|
3
3
|
# =============================================================================
|
|
4
|
-
# COMMIT-MSG:
|
|
4
|
+
# COMMIT-MSG: Block accidental AI-attribution in commit messages
|
|
5
5
|
# =============================================================================
|
|
6
|
-
#
|
|
6
|
+
# Best-effort, NOT an adversarial-grade security control.
|
|
7
|
+
#
|
|
8
|
+
# Diseño:
|
|
9
|
+
# 1. Normalizar el mensaje (Unicode invisibles, combining marks, markdown,
|
|
10
|
+
# puntuación, emoji, full-width latin via NFKC)
|
|
11
|
+
# 2. Match patrones case-insensitive contra la versión normalizada
|
|
12
|
+
# Y contra el mensaje crudo (por si normalize sobre-strip-ea)
|
|
13
|
+
#
|
|
14
|
+
# Alcance:
|
|
15
|
+
# ✅ Defiende contra: copy-paste accidental, whitespace tricks (doble
|
|
16
|
+
# espacio, NBSP, ZWSP), markdown formatting, full-width latin,
|
|
17
|
+
# combining diacritics, dashes/underscores en model names, separadores
|
|
18
|
+
# de puntuación / emoji.
|
|
19
|
+
# ❌ NO defiende contra: homoglifos cross-script (Cyrillic Сlaude, Greek
|
|
20
|
+
# Ϲlaude, Cherokee Ꮯlaude), tag characters, variation selectors,
|
|
21
|
+
# provider names standalone sin verbo o modelo, bypasses creativos
|
|
22
|
+
# de adversario motivado con tabla Unicode en mano.
|
|
23
|
+
#
|
|
24
|
+
# El test suite (commit-msg.test.sh) documenta qué bypasses están cubiertos
|
|
25
|
+
# y cuáles aceptamos como limitación. Si necesitás defensa adversarial,
|
|
26
|
+
# usá signed commits, no regex.
|
|
7
27
|
# =============================================================================
|
|
8
28
|
|
|
9
29
|
COMMIT_MSG_FILE="$1"
|
|
10
|
-
|
|
30
|
+
RAW_MSG=$(cat "$COMMIT_MSG_FILE")
|
|
11
31
|
|
|
12
32
|
RED='\033[0;31m'
|
|
13
33
|
GREEN='\033[0;32m'
|
|
14
34
|
YELLOW='\033[1;33m'
|
|
15
35
|
NC='\033[0m'
|
|
16
36
|
|
|
17
|
-
#
|
|
37
|
+
# ─── Normalización ──────────────────────────────────────────────────
|
|
38
|
+
# Si perl falla (no instalado, módulo Unicode::Normalize ausente), el
|
|
39
|
+
# normalize retorna el mensaje crudo. El loop de patrones igual matchea
|
|
40
|
+
# contra RAW_MSG, así que perl roto degrada pero no abre todo.
|
|
41
|
+
normalize_msg() {
|
|
42
|
+
if ! command -v perl >/dev/null 2>&1; then
|
|
43
|
+
printf "%s" "$1"
|
|
44
|
+
return
|
|
45
|
+
fi
|
|
46
|
+
# 2>/dev/null suprime warnings de perl. Si el script falla por completo,
|
|
47
|
+
# || printf "%s" "$1" deja pasar el raw — el match contra RAW_MSG abajo
|
|
48
|
+
# sigue cubriendo los patrones literales.
|
|
49
|
+
printf "%s" "$1" | perl -CSAD -pe '
|
|
50
|
+
# 1. Reemplazar Unicode formatting/invisibles por ESPACIO (no borrarlos).
|
|
51
|
+
# Borrarlos junta "by" + "Claude" → "byClaude" y la regex pierde el match.
|
|
52
|
+
# ZWSP/ZWNJ/ZWJ/LRM/RLM, NBSP, bidi controls, BOM, word-joiner.
|
|
53
|
+
s/[\x{00A0}\x{200B}-\x{200F}\x{2028}-\x{202F}\x{2060}-\x{206F}\x{FEFF}]/ /g;
|
|
54
|
+
# 2. Strip combining diacritics. Esto folds "Ćlaude" (C + U+0301) a "Claude".
|
|
55
|
+
s/[\x{0300}-\x{036F}]//g;
|
|
56
|
+
# 3. NFKC normaliza compatibility forms (full-width latin → ascii)
|
|
57
|
+
use Unicode::Normalize qw(NFKC);
|
|
58
|
+
$_ = NFKC($_);
|
|
59
|
+
# 4. Markdown / puntuación / emoji → espacio
|
|
60
|
+
# Markdown: * _ ` ~
|
|
61
|
+
# Puntuación: . , : ; ! ? ( ) [ ] { } < > / \ | " '\''
|
|
62
|
+
# Emoji: Misc Symbols (2600-27BF), Pictographs (1F300-1FAFF)
|
|
63
|
+
s/[*_`~.,:;!?()\[\]{}<>\\\/|"'\''\x{2600}-\x{27BF}\x{1F300}-\x{1FAFF}]/ /g;
|
|
64
|
+
# 5. Colapsar whitespace (incluye \n, \t, espacios múltiples)
|
|
65
|
+
s/\s+/ /g;
|
|
66
|
+
' 2>/dev/null || printf "%s" "$1"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
NORMALIZED_MSG=$(normalize_msg "$RAW_MSG")
|
|
70
|
+
|
|
71
|
+
# ─── Patrones (case insensitive, grep -iE) ──────────────────────────
|
|
72
|
+
#
|
|
73
|
+
# Notas:
|
|
74
|
+
# \b = word boundary (GNU grep ext.)
|
|
75
|
+
# [[:space:]]+ = uno o más espacios (POSIX, portable)
|
|
76
|
+
# [-_ ]? = separador opcional entre proveedor y modelo
|
|
77
|
+
#
|
|
78
|
+
# Limitación conocida y aceptada: homoglifos Unicode (Cyrillic С en lugar
|
|
79
|
+
# de Latin C) requieren transliteración (Text::Unidecode) que es módulo
|
|
80
|
+
# no-core de perl. Documentado, no se intenta defender.
|
|
81
|
+
PROVIDER_PAT='(claude|gpt|chatgpt|openai|anthropic|gemini|copilot|llm|cursor|windsurf|codeium|cody|aider)'
|
|
82
|
+
|
|
18
83
|
AI_PATTERNS=(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"co-authored-by
|
|
22
|
-
|
|
23
|
-
"co-authored-by
|
|
24
|
-
|
|
25
|
-
"co-authored-by:.*\
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"made by
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"generated
|
|
32
|
-
"generated
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
"assisted
|
|
41
|
-
"assisted
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"claude
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
84
|
+
# ── Co-authored-by trailer ────────────────────────────────────────
|
|
85
|
+
# Captura cualquier proveedor en el mismo trailer.
|
|
86
|
+
"co-authored-by:.*\b${PROVIDER_PAT}\b"
|
|
87
|
+
# AI en trailer: AI alone OR AIAssistant / AIBot / AIAgent compounds
|
|
88
|
+
"co-authored-by:.*\bai(assistant|bot|agent|helper)?\b"
|
|
89
|
+
# Standalone Copilot/Codeium/Cursor en trailer (sin prefix vendor)
|
|
90
|
+
"co-authored-by:.*\b(copilot|cursor|codeium|cody|aider|chatgpt)\b"
|
|
91
|
+
|
|
92
|
+
# ── Verbos de atribución + by/with + proveedor ───────────────────
|
|
93
|
+
# Ej: "made by Claude", "generated with GPT", "built with OpenAI"
|
|
94
|
+
# Verbos expandidos en audit-2 para cubrir "I used X", "X helped me",
|
|
95
|
+
# "wrote by AI", "prompted to write", "asked Claude to".
|
|
96
|
+
"(made|generated|written|created|assisted|built|developed|authored|produced|coded|crafted|helped|used|wrote|prompted|asked|fixed|refactored)[[:space:]]+(by|with|using|via)[[:space:]]+${PROVIDER_PAT}"
|
|
97
|
+
"(made|generated|written|created|assisted|built|developed|authored|produced|coded|crafted|helped|used|wrote|prompted|asked|fixed|refactored)[[:space:]]+(by|with|using|via)[[:space:]]+\bai(assistant|bot|agent|helper)?\b"
|
|
98
|
+
|
|
99
|
+
# ── "with help from / thanks to / powered by X" ──────────────────
|
|
100
|
+
"(with[[:space:]]+help[[:space:]]+from|thanks[[:space:]]+to|powered[[:space:]]+by|courtesy[[:space:]]+of)[[:space:]]+${PROVIDER_PAT}"
|
|
101
|
+
"(with[[:space:]]+help[[:space:]]+from|thanks[[:space:]]+to|powered[[:space:]]+by|courtesy[[:space:]]+of)[[:space:]]+\bai(assistant|bot|agent|helper)?\b"
|
|
102
|
+
|
|
103
|
+
# ── "X-assisted" / "X assisted" ──────────────────────────────────
|
|
104
|
+
# Cubre: anthropic-assisted, openai assisted, ai_assisted, etc.
|
|
105
|
+
"${PROVIDER_PAT}[-_ ]?assisted"
|
|
106
|
+
"\bai[-_ ]?assisted"
|
|
107
|
+
|
|
108
|
+
# ── Standalone branding / tooling ────────────────────────────────
|
|
109
|
+
"claude[-_ ]?code"
|
|
110
|
+
"(github|microsoft)[-_ ]?copilot"
|
|
111
|
+
"\bchatgpt\b"
|
|
112
|
+
"\bcursor[-_ ]?(ai|ide)?\b"
|
|
113
|
+
"\bwindsurf\b"
|
|
114
|
+
"\bcodeium\b"
|
|
115
|
+
"\bcody\b"
|
|
116
|
+
"\baider\b"
|
|
117
|
+
|
|
118
|
+
# ── Claude + variante de modelo ──────────────────────────────────
|
|
119
|
+
# Cubre: "claude opus", "claude-sonnet", "Claude3", "claude4.5", "Claude 4.7"
|
|
120
|
+
"claude[-_ ]?(opus|sonnet|haiku|[0-9])"
|
|
121
|
+
|
|
122
|
+
# ── GPT + versión ────────────────────────────────────────────────
|
|
123
|
+
# Cubre: gpt-4, gpt 3, gpt_5, gpt4.5
|
|
124
|
+
"gpt[-_ ]?[0-9]"
|
|
125
|
+
|
|
126
|
+
# ── Email domains de los proveedores ─────────────────────────────
|
|
127
|
+
"@(anthropic|openai|cursor|codeium)\\.(com|sh|ai)"
|
|
128
|
+
"noreply@(anthropic|openai)"
|
|
54
129
|
)
|
|
55
130
|
|
|
131
|
+
# Run pattern matching against the NORMALIZED message.
|
|
132
|
+
# Use both raw and normalized — raw catches anything normalization might over-strip.
|
|
56
133
|
for pattern in "${AI_PATTERNS[@]}"; do
|
|
57
|
-
if
|
|
134
|
+
if printf "%s" "$NORMALIZED_MSG" | grep -iqE "$pattern" \
|
|
135
|
+
|| printf "%s" "$RAW_MSG" | grep -iqE "$pattern"; then
|
|
58
136
|
echo -e ""
|
|
59
137
|
echo -e "${RED}COMMIT BLOCKED: AI Attribution Detected${NC}"
|
|
60
138
|
echo -e ""
|
|
61
|
-
echo -e "${RED}
|
|
62
|
-
echo -e "${YELLOW} Pattern: $pattern${NC}"
|
|
139
|
+
echo -e "${RED}Pattern matched:${NC} ${YELLOW}$pattern${NC}"
|
|
63
140
|
echo -e ""
|
|
64
141
|
echo -e "${RED}Your commit message:${NC}"
|
|
65
|
-
echo -e "${YELLOW}$
|
|
142
|
+
echo -e "${YELLOW}$RAW_MSG${NC}"
|
|
66
143
|
echo -e ""
|
|
67
144
|
echo -e "${GREEN}Remove AI attribution. You are the sole author.${NC}"
|
|
68
145
|
echo -e ""
|
|
@@ -70,5 +147,4 @@ for pattern in "${AI_PATTERNS[@]}"; do
|
|
|
70
147
|
fi
|
|
71
148
|
done
|
|
72
149
|
|
|
73
|
-
# Mensaje OK
|
|
74
150
|
exit 0
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# COMMIT-MSG TEST SUITE
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Valida que el hook commit-msg bloquea AI attribution en TODAS las variantes
|
|
6
|
+
# conocidas, incluyendo bypasses identificados en auditoría 2026-05-17.
|
|
7
|
+
#
|
|
8
|
+
# Uso:
|
|
9
|
+
# ./commit-msg.test.sh
|
|
10
|
+
#
|
|
11
|
+
# Exit code 0 = todos los tests pasan
|
|
12
|
+
# Exit code 1 = al menos un test falla
|
|
13
|
+
# =============================================================================
|
|
14
|
+
|
|
15
|
+
set -u
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
18
|
+
HOOK="$SCRIPT_DIR/commit-msg"
|
|
19
|
+
|
|
20
|
+
if [ ! -x "$HOOK" ]; then
|
|
21
|
+
echo "ERROR: commit-msg hook not found or not executable: $HOOK"
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
PASS=0
|
|
26
|
+
FAIL=0
|
|
27
|
+
FAILED_TESTS=()
|
|
28
|
+
|
|
29
|
+
# Garantiza limpieza de tmpfiles aún si el hook revienta con set -u, etc.
|
|
30
|
+
TMP_FILES=()
|
|
31
|
+
cleanup() {
|
|
32
|
+
if [ ${#TMP_FILES[@]} -gt 0 ]; then
|
|
33
|
+
rm -f "${TMP_FILES[@]}"
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
trap cleanup EXIT INT TERM
|
|
37
|
+
|
|
38
|
+
make_tmp() {
|
|
39
|
+
local f
|
|
40
|
+
f=$(mktemp)
|
|
41
|
+
TMP_FILES+=("$f")
|
|
42
|
+
printf "%s" "$1" > "$f"
|
|
43
|
+
printf "%s" "$f"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# expect_block <description> <message>
|
|
47
|
+
# El hook debe BLOQUEAR este mensaje (exit != 0)
|
|
48
|
+
expect_block() {
|
|
49
|
+
local desc="$1"
|
|
50
|
+
local msg="$2"
|
|
51
|
+
local tmpfile
|
|
52
|
+
tmpfile=$(make_tmp "$msg")
|
|
53
|
+
if "$HOOK" "$tmpfile" >/dev/null 2>&1; then
|
|
54
|
+
FAIL=$((FAIL + 1))
|
|
55
|
+
FAILED_TESTS+=("[NOT BLOCKED] $desc | msg: '$msg'")
|
|
56
|
+
echo " FAIL: $desc (should block but passed): '$msg'"
|
|
57
|
+
else
|
|
58
|
+
PASS=$((PASS + 1))
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# expect_pass <description> <message>
|
|
63
|
+
# El hook debe PERMITIR este mensaje (exit 0)
|
|
64
|
+
expect_pass() {
|
|
65
|
+
local desc="$1"
|
|
66
|
+
local msg="$2"
|
|
67
|
+
local tmpfile
|
|
68
|
+
tmpfile=$(make_tmp "$msg")
|
|
69
|
+
if "$HOOK" "$tmpfile" >/dev/null 2>&1; then
|
|
70
|
+
PASS=$((PASS + 1))
|
|
71
|
+
else
|
|
72
|
+
FAIL=$((FAIL + 1))
|
|
73
|
+
FAILED_TESTS+=("[FALSE POSITIVE] $desc | msg: '$msg'")
|
|
74
|
+
echo " FAIL: $desc (should pass but blocked): '$msg'"
|
|
75
|
+
fi
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
echo "=== Running commit-msg hook test suite ==="
|
|
79
|
+
echo ""
|
|
80
|
+
|
|
81
|
+
# ─── Cases que DEBEN bloquear ────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
# Co-authored-by con cada proveedor
|
|
84
|
+
expect_block "Co-authored-by Claude" "feat: add foo
|
|
85
|
+
|
|
86
|
+
Co-authored-by: Claude <claude@anthropic.com>"
|
|
87
|
+
|
|
88
|
+
expect_block "Co-authored-by claude lowercase" "feat: bar
|
|
89
|
+
|
|
90
|
+
co-authored-by: claude <c@a.com>"
|
|
91
|
+
|
|
92
|
+
expect_block "Co-authored-by double space" "feat: baz
|
|
93
|
+
|
|
94
|
+
Co-authored-by: Claude <c@a.com>"
|
|
95
|
+
|
|
96
|
+
expect_block "Co-authored-by GPT" "feat: x
|
|
97
|
+
|
|
98
|
+
Co-authored-by: GPT-4 <g@o.com>"
|
|
99
|
+
|
|
100
|
+
expect_block "Co-authored-by Copilot" "feat: y
|
|
101
|
+
|
|
102
|
+
Co-authored-by: GitHub Copilot <c@gh.com>"
|
|
103
|
+
|
|
104
|
+
expect_block "Co-authored-by ai standalone" "feat: z
|
|
105
|
+
|
|
106
|
+
Co-authored-by: AI Assistant <a@b.com>"
|
|
107
|
+
|
|
108
|
+
# Made/Generated/Written/Created by/with X — single space
|
|
109
|
+
expect_block "Made by Claude" "feat: add Made by Claude"
|
|
110
|
+
expect_block "Made with Claude" "feat: Made with Claude"
|
|
111
|
+
expect_block "Generated by GPT" "feat: Generated by GPT"
|
|
112
|
+
expect_block "Written with OpenAI" "fix: Written with OpenAI"
|
|
113
|
+
expect_block "Created by Anthropic" "feat: Created by Anthropic"
|
|
114
|
+
expect_block "Assisted by AI" "feat: Assisted by AI"
|
|
115
|
+
expect_block "Built with Gemini" "feat: Built with Gemini"
|
|
116
|
+
expect_block "Developed with Copilot" "feat: Developed with Copilot"
|
|
117
|
+
expect_block "Authored by LLM" "feat: Authored by LLM"
|
|
118
|
+
|
|
119
|
+
# Made/Generated/etc — DOUBLE space (bypass identificado en audit)
|
|
120
|
+
expect_block "Made by Claude (double space)" "feat: Made by Claude"
|
|
121
|
+
expect_block "Generated with GPT (double space)" "fix: Generated with GPT"
|
|
122
|
+
expect_block "Made by\tClaude (tab)" "$(printf 'feat: Made by\tClaude')"
|
|
123
|
+
|
|
124
|
+
# With help from X
|
|
125
|
+
expect_block "With help from Claude" "feat: x
|
|
126
|
+
|
|
127
|
+
With help from Claude"
|
|
128
|
+
expect_block "with help from GPT-4" "feat: with help from GPT-4"
|
|
129
|
+
|
|
130
|
+
# X-assisted variants (bypass identificado)
|
|
131
|
+
expect_block "anthropic-assisted" "feat: anthropic-assisted refactor"
|
|
132
|
+
expect_block "openai-assisted" "feat: openai-assisted change"
|
|
133
|
+
expect_block "ai-assisted" "feat: ai-assisted commit"
|
|
134
|
+
expect_block "Claude assisted (space)" "feat: Claude assisted work"
|
|
135
|
+
expect_block "LLM_assisted (underscore)" "feat: llm_assisted feature"
|
|
136
|
+
|
|
137
|
+
# Tool branding
|
|
138
|
+
expect_block "claude code" "feat: built with claude code"
|
|
139
|
+
expect_block "claude-code" "feat: via claude-code"
|
|
140
|
+
expect_block "claude_code" "feat: claude_code generated"
|
|
141
|
+
expect_block "GitHub Copilot" "feat: GitHub Copilot helped"
|
|
142
|
+
expect_block "Microsoft Copilot" "feat: Microsoft Copilot did this"
|
|
143
|
+
|
|
144
|
+
# Claude model variants (bypass identificado)
|
|
145
|
+
expect_block "claude opus (space)" "feat: claude opus tested"
|
|
146
|
+
expect_block "claude-sonnet (dash)" "feat: claude-sonnet used"
|
|
147
|
+
expect_block "claude_haiku (underscore)" "feat: claude_haiku approach"
|
|
148
|
+
expect_block "Claude3.5" "feat: Claude3.5 wrote this"
|
|
149
|
+
expect_block "claude4.7" "feat: claude4.7 contributed"
|
|
150
|
+
expect_block "Claude 4" "feat: Claude 4 logic"
|
|
151
|
+
|
|
152
|
+
# GPT model variants
|
|
153
|
+
expect_block "gpt-4" "feat: gpt-4 reviewed"
|
|
154
|
+
expect_block "gpt 5" "feat: gpt 5 wrote"
|
|
155
|
+
expect_block "gpt_3" "feat: gpt_3 helped"
|
|
156
|
+
expect_block "GPT4" "feat: GPT4 was used"
|
|
157
|
+
|
|
158
|
+
# ChatGPT
|
|
159
|
+
expect_block "ChatGPT" "feat: ChatGPT generated"
|
|
160
|
+
expect_block "chatgpt" "feat: powered by chatgpt"
|
|
161
|
+
|
|
162
|
+
# Emails
|
|
163
|
+
expect_block "@anthropic.com" "feat: x
|
|
164
|
+
|
|
165
|
+
Author: foo@anthropic.com"
|
|
166
|
+
expect_block "@openai.com" "feat: y
|
|
167
|
+
|
|
168
|
+
Co-authored-by: bot@openai.com"
|
|
169
|
+
expect_block "noreply@anthropic" "feat: noreply@anthropic.example"
|
|
170
|
+
|
|
171
|
+
# ─── Bypasses identificados en review 2026-05-17 (post-hardening) ─────
|
|
172
|
+
|
|
173
|
+
# Markdown formatting alrededor del proveedor
|
|
174
|
+
expect_block "Made by **Claude** (markdown bold)" "feat: Made by **Claude**"
|
|
175
|
+
expect_block "Made by __GPT__ (markdown italic)" "feat: Made by __GPT__"
|
|
176
|
+
expect_block "Made by \`OpenAI\` (markdown code)" "feat: Made by \`OpenAI\`"
|
|
177
|
+
expect_block "Made by ~~Gemini~~ (strikethrough)" "feat: Made by ~~Gemini~~"
|
|
178
|
+
|
|
179
|
+
# Puntuación entre verbo y proveedor (no whitespace)
|
|
180
|
+
expect_block "Made by.Claude" "feat: Made by.Claude"
|
|
181
|
+
expect_block "Made by,Claude" "feat: Made by,Claude"
|
|
182
|
+
expect_block "Made by:Claude" "feat: Made by:Claude"
|
|
183
|
+
expect_block "Made by;Claude" "feat: Made by;Claude"
|
|
184
|
+
|
|
185
|
+
# Newline entre verbo y proveedor
|
|
186
|
+
expect_block "Made by newline Claude" "$(printf 'feat: Made by\nClaude')"
|
|
187
|
+
|
|
188
|
+
# Emoji separator
|
|
189
|
+
expect_block "Made by emoji Claude" "feat: Made by 🤖 Claude"
|
|
190
|
+
expect_block "Generated by emoji GPT" "feat: Generated by ✨ GPT"
|
|
191
|
+
|
|
192
|
+
# Unicode invisible chars (ZWSP, RLM, NBSP)
|
|
193
|
+
expect_block "Made by ZWSP Claude" "$(printf 'feat: Made byClaude')"
|
|
194
|
+
expect_block "Made by NBSP Claude" "$(printf 'feat: Made by Claude')"
|
|
195
|
+
expect_block "Made by RLM Claude" "$(printf 'feat: Made byClaude')"
|
|
196
|
+
|
|
197
|
+
# "thanks/powered by/courtesy of" alternativas
|
|
198
|
+
expect_block "thanks to Claude" "feat: thanks to Claude for help"
|
|
199
|
+
expect_block "powered by GPT" "feat: powered by GPT"
|
|
200
|
+
expect_block "courtesy of OpenAI" "feat: courtesy of OpenAI"
|
|
201
|
+
|
|
202
|
+
# AI IDEs / herramientas
|
|
203
|
+
expect_block "Cursor IDE" "feat: built with Cursor"
|
|
204
|
+
expect_block "Windsurf" "feat: written with Windsurf"
|
|
205
|
+
expect_block "Codeium" "feat: codeium suggested this"
|
|
206
|
+
expect_block "Cody" "feat: with Cody help"
|
|
207
|
+
expect_block "Aider" "feat: aider generated"
|
|
208
|
+
|
|
209
|
+
# Co-authored-by sin espacio antes de AI (bypass del review Sonnet)
|
|
210
|
+
expect_block "Co-authored-by AIAssistant" "feat: x
|
|
211
|
+
|
|
212
|
+
Co-authored-by: AIAssistant <a@b.com>"
|
|
213
|
+
|
|
214
|
+
# Co-authored-by Copilot standalone (sin GitHub/Microsoft prefix)
|
|
215
|
+
expect_block "Co-authored-by bare Copilot" "feat: x
|
|
216
|
+
|
|
217
|
+
Co-authored-by: Copilot <c@gh.com>"
|
|
218
|
+
|
|
219
|
+
# Verbos alternativos
|
|
220
|
+
expect_block "produced by Claude" "feat: produced by Claude"
|
|
221
|
+
expect_block "coded by GPT" "feat: coded by GPT"
|
|
222
|
+
expect_block "crafted with AI" "feat: crafted with AI"
|
|
223
|
+
|
|
224
|
+
# Using/via como alternativas a by/with
|
|
225
|
+
expect_block "made using Claude" "feat: made using Claude"
|
|
226
|
+
expect_block "generated via GPT" "feat: generated via GPT"
|
|
227
|
+
|
|
228
|
+
# Full-width characters (NFKC normalization)
|
|
229
|
+
expect_block "Made by Claude (fullwidth)" "feat: Made by Claude"
|
|
230
|
+
|
|
231
|
+
# ─── Bypasses adicionales identificados en audit-2 (2026-05-17 review B) ──
|
|
232
|
+
|
|
233
|
+
# Combining diacritics (U+0300-U+036F) — Ćlaude = C + U+0301
|
|
234
|
+
expect_block "Ćlaude (combining acute)" "$(printf 'feat: Made by Ćlaude')"
|
|
235
|
+
expect_block "Cláude (combining grave)" "$(printf 'feat: Made by Clàude')"
|
|
236
|
+
|
|
237
|
+
# Verbos expandidos — sólo aplican si hay (by|with|using|via) después del verbo.
|
|
238
|
+
# "asked Claude to ..." NO bloquea por diseño: el riesgo de false positive en
|
|
239
|
+
# nombres personales o conversaciones legítimas es alto. Si necesitás defensa
|
|
240
|
+
# adversarial usá signed commits.
|
|
241
|
+
expect_block "helped by Claude" "feat: helped by Claude"
|
|
242
|
+
expect_block "wrote with GPT" "feat: wrote with GPT-4"
|
|
243
|
+
expect_block "prompted via AI" "feat: prompted via AI"
|
|
244
|
+
expect_block "fixed by Copilot" "feat: fixed by Copilot"
|
|
245
|
+
expect_block "refactored with Cursor" "feat: refactored with Cursor"
|
|
246
|
+
|
|
247
|
+
# Cases que DOCUMENTADAMENTE NO bloqueamos (bare provider sin verb-prepositional):
|
|
248
|
+
# - "asked Claude to refactor" (FP risk: pidió a una persona llamada Claude)
|
|
249
|
+
# - "I used Claude to write" (FP risk: usé al usuario Claude para escribir)
|
|
250
|
+
# - "claude helped me debug" (FP risk: Claude la persona ayudó a debuggear)
|
|
251
|
+
# Estos son límites aceptados; ver header del hook.
|
|
252
|
+
expect_pass "asked Claude (person, not attribution)" "feat: asked Claude to review the PR"
|
|
253
|
+
expect_pass "user Claude in seed" "feat: configure user Claude in fixtures"
|
|
254
|
+
|
|
255
|
+
# ─── Cases que DEBEN PASAR (false positive checks) ────────────────────
|
|
256
|
+
|
|
257
|
+
expect_pass "Simple feat" "feat: add user profile endpoint"
|
|
258
|
+
expect_pass "Simple fix" "fix: handle empty array in parser"
|
|
259
|
+
expect_pass "Refactor" "refactor: extract auth into middleware"
|
|
260
|
+
expect_pass "Docs" "docs: clarify install instructions"
|
|
261
|
+
expect_pass "Multi-line valid commit" "feat: add search
|
|
262
|
+
|
|
263
|
+
Implements full-text search using PostgreSQL FTS.
|
|
264
|
+
Includes integration tests."
|
|
265
|
+
expect_pass "Author named Claude (not attribution)" "feat: add user named Claude in seed data"
|
|
266
|
+
expect_pass "AI as acronym in product name" "feat: integrate with PaiAi API gateway"
|
|
267
|
+
expect_pass "Word 'made' without AI context" "feat: changes I made last week to the parser"
|
|
268
|
+
|
|
269
|
+
# ─── Summary ──────────────────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
echo ""
|
|
272
|
+
echo "=== Results ==="
|
|
273
|
+
echo " Passed: $PASS"
|
|
274
|
+
echo " Failed: $FAIL"
|
|
275
|
+
echo ""
|
|
276
|
+
|
|
277
|
+
if [ $FAIL -gt 0 ]; then
|
|
278
|
+
echo "Failed tests:"
|
|
279
|
+
for t in "${FAILED_TESTS[@]}"; do
|
|
280
|
+
echo " - $t"
|
|
281
|
+
done
|
|
282
|
+
exit 1
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
echo "All tests passed."
|
|
286
|
+
exit 0
|
|
@@ -4,14 +4,22 @@
|
|
|
4
4
|
# =============================================================================
|
|
5
5
|
# Requires: npm install -g javi-forge
|
|
6
6
|
# To skip: git commit --no-verify
|
|
7
|
+
# Target: ~30s. The hook prints elapsed time so drift can be tracked.
|
|
7
8
|
# =============================================================================
|
|
8
9
|
|
|
9
10
|
set -e
|
|
10
11
|
|
|
12
|
+
_start=$SECONDS
|
|
11
13
|
echo "PRE-COMMIT: Running quick check..."
|
|
12
14
|
javi-forge ci --quick --no-docker --no-security --no-ci-ghagga || {
|
|
15
|
+
_elapsed=$((SECONDS - _start))
|
|
13
16
|
echo ""
|
|
14
|
-
echo "Quick check FAILED — Fix the issues above before committing."
|
|
17
|
+
echo "Quick check FAILED after ${_elapsed}s — Fix the issues above before committing."
|
|
15
18
|
echo "To skip: git commit --no-verify"
|
|
16
19
|
exit 1
|
|
17
20
|
}
|
|
21
|
+
_elapsed=$((SECONDS - _start))
|
|
22
|
+
echo "PRE-COMMIT: ${_elapsed}s (target: 30s)"
|
|
23
|
+
if [ "$_elapsed" -gt 60 ]; then
|
|
24
|
+
echo " WARNING: pre-commit took ${_elapsed}s, well over the 30s target."
|
|
25
|
+
fi
|