-
Notifications
You must be signed in to change notification settings - Fork 0
chore(tooling): piso de calidad — ESLint, Prettier, lefthook, commitlint y CI #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Reformateo masivo con Prettier — no aporta nada al blame. | ||
| 1a60335c1bc179b3263d2f9a62c02c28b9484cc4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| name: CI | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
| push: | ||
| branches: [main] | ||
|
|
||
| # Un push nuevo al mismo PR cancela la corrida anterior. | ||
| concurrency: | ||
| group: ci-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| quality: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
|
|
||
| - uses: pnpm/action-setup@v6 | ||
|
|
||
| - uses: actions/setup-node@v7 | ||
| with: | ||
| node-version: 22 | ||
| cache: pnpm | ||
|
|
||
| - run: pnpm install --frozen-lockfile | ||
|
|
||
| - run: pnpm typecheck | ||
|
|
||
| - run: pnpm lint | ||
|
|
||
| - run: pnpm format:check | ||
|
|
||
| - run: pnpm test | ||
|
|
||
| # Se buildea el target de Cloudflare, que es el que va a producción. | ||
| - run: pnpm build:cf | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||||||||||||||||
| name: Deploy | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # Solo después de que CI pase en main. Si la calidad falla, no se despliega. | ||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||
| workflow_run: | ||||||||||||||||||||||||
| workflows: [CI] | ||||||||||||||||||||||||
| types: [completed] | ||||||||||||||||||||||||
| branches: [main] | ||||||||||||||||||||||||
|
Comment on lines
+4
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== workflows =="
git ls-files .github/workflows || true
echo
echo "== deploy.yml =="
if [ -f .github/workflows/deploy.yml ]; then
cat -n .github/workflows/deploy.yml
else
fd -a 'deploy.yml' .github/workflows || true
fi
echo
echo "== references to workflow_run/deploy =="
rg -n "workflow_run|workflow_run\.(event|head_branch|head_sha|conclusion)|deployment|CLOUDFLARE|cloudflare|pages" .github/workflows || trueRepository: exactamente-ar/exactamente-mcp Length of output: 1880 🌐 Web query:
💡 Result: Using workflow_run to checkout code using github.event.workflow_run.head_sha creates a critical security vulnerability known as a pwn request [1][2]. Because workflow_run workflows run in the context of the base repository—with access to secrets, write permissions, and the ability to influence the environment—checking out and executing untrusted code from a fork allows an attacker to execute arbitrary commands with those elevated privileges [3][4][5]. Security Risks and Mechanics 1. Privilege Escalation: Even if the triggering workflow is unprivileged, the workflow_run event operates with the base repository's permissions [3][6]. Attackers can submit a pull request that triggers a workflow, which then triggers the workflow_run, effectively using the second workflow as a confused deputy to perform malicious actions [3][6]. 2. Code Execution: If you check out the head_sha of an untrusted pull request using actions/checkout and subsequently run build scripts (e.g., npm install, pnpm build) or use local actions (uses:./), those commands execute attacker-controlled code within your trusted environment [1][2]. 3. Artifact Poisoning: Artifacts from untrusted workflows are a major attack vector [3][6]. If a workflow_run downloads and processes these artifacts without rigorous validation, an attacker can exfiltrate secrets or execute malicious code [3][6]. Mitigation and Best Practices As of June 2026, actions/checkout v7 introduced safety defaults that refuse to fetch fork pull request code in workflow_run workflows triggered by pull requests [7][8]. 1. Use Separation of Concerns: The recommended pattern is to perform untrusted work in an unprivileged workflow (e.g., triggered by pull_request) and store results in artifacts [9][2]. A separate, privileged workflow (triggered by workflow_run) should then download, verify, and process those artifacts [9][2]. 2. Treat Artifacts as Untrusted: Never assume artifacts are safe. Always implement strict validation and sandboxing when handling them in a privileged context [3]. 3. Avoid Unsafe Checkouts: Do not use the allow-unsafe-pr-checkout: true flag unless absolutely necessary and after a thorough security review [10][7][5]. 4. Pin Actions: Use full commit SHAs for all action references to protect against supply-chain attacks where action tags are hijacked [8]. In summary, you should never check out or execute code from an untrusted PR source within a workflow_run context [4]. If you must process data from an untrusted PR, ensure the privileged workflow only interacts with verified artifacts, never raw source code [9][2]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== ci.yml =="
cat -n .github/workflows/ci.yml
echo
echo "== current workflow_run trigger configuration =="
python3 - <<'PY'
from pathlib import Path
text = Path(".github/workflows/deploy.yml").read_text()
lines = text.splitlines()
in_event = False
for i, line in enumerate(lines[1:], start=2):
if line.startswith("on:") or "workflow_run:" in line:
in_event = True
if in_event:
print(f"{i}: {line}")
if line and not line.startswith(" ") and line.rstrip(":") != "on" and line.lstrip().isdigit():
break
if in_event and i > 10:
break
PYRepository: exactamente-ar/exactamente-mcp Length of output: 1339 Do not checkout CI’s
Minimum event guard- if: github.event.workflow_run.conclusion == 'success'
+ if: >-
+ github.event.workflow_run.conclusion == 'success' &&
+ github.event.workflow_run.event == 'push' &&
+ github.event.workflow_run.head_branch == 'main'🧰 Tools🪛 zizmor (1.26.1)[error] 4-8: use of fundamentally insecure workflow trigger (dangerous-triggers): workflow_run is almost always used insecurely (dangerous-triggers) 🤖 Prompt for AI AgentsSources: MCP tools, Linters/SAST tools |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| concurrency: | ||||||||||||||||||||||||
| group: deploy-production | ||||||||||||||||||||||||
| cancel-in-progress: false | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||
| deploy: | ||||||||||||||||||||||||
| if: github.event.workflow_run.conclusion == 'success' | ||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||
| - uses: actions/checkout@v7 | ||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||
| ref: ${{ github.event.workflow_run.head_sha }} | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - uses: pnpm/action-setup@v6 | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - uses: actions/setup-node@v7 | ||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||
| node-version: 22 | ||||||||||||||||||||||||
| cache: pnpm | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - run: pnpm install --frozen-lockfile | ||||||||||||||||||||||||
|
Comment on lines
+25
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files .github/workflows/deploy.yml || true
echo "== workflow excerpt =="
if [ -f .github/workflows/deploy.yml ]; then
nl -ba .github/workflows/deploy.yml | sed -n '1,80p'
fi
echo "== workflow names/triggers =="
python3 - <<'PY'
from pathlib import Path
p=Path(".github/workflows/deploy.yml")
if p.exists():
print(p.read_text(errors="replace")[:3000])
PY
echo "== similar cache usages =="
rg -n "setup-node|cache:\s*pnpm|permissions:|contents:|deployment|deploy" .github/workflows || trueRepository: exactamente-ar/exactamente-mcp Length of output: 272 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== workflow excerpt =="
awk '{printf "%4d\t%s\n", NR, $0}' .github/workflows/deploy.yml | sed -n '1,80p'
echo "== cache/action usages and permissions =="
awk '{printf "%4d\t%s\n", NR, $0}' .github/workflows/deploy.yml | grep -nE 'uses: actions/setup-node|cache:|permissions:|contents:|deploy|npm|pnpm' || true
echo "== workflow files search for cache setup-node =="
while IFS= read -r f; do
echo "-- $f"
grep -nE 'uses: actions/setup-node|cache:\s*pnpm|permissions:|contents:|deployment|deploy' "$f" || true
done < <(find .github/workflows -maxdepth 1 -type f -print)Repository: exactamente-ar/exactamente-mcp Length of output: 1621 🌐 Web query:
💡 Result: Cache poisoning in GitHub Actions, particularly when using Citations:
Remove dependency caching from the privileged deploy job.
Safer deployment setup - uses: actions/setup-node@v7
with:
node-version: 22
- cache: pnpm📝 Committable suggestion
Suggested change
🧰 Tools🪛 zizmor (1.26.1)[error] 25-25: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): this step (cache-poisoning) 🤖 Prompt for AI AgentsSources: MCP tools, Linters/SAST tools |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - run: pnpm build:cf | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - uses: cloudflare/wrangler-action@v3 | ||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||||||||||||||||||||||||
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| dist | ||
| .xmcp | ||
| .wrangler | ||
| node_modules | ||
| pnpm-lock.yaml | ||
| worker.js | ||
| xmcp-env.d.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "singleQuote": true, | ||
| "semi": true, | ||
| "printWidth": 100 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,35 +32,35 @@ Your MCP server URL will be: `https://exactamente-mcp.<your-subdomain>.workers.d | |
|
|
||
| ### Basic Information | ||
|
|
||
| | Field | Value | | ||
| |---|---| | ||
| | **App Name** | Exactamente | | ||
| | **Description** | Search and download academic materials — exams, summaries, and finals from Argentine universities. | | ||
| | **Company Name** | _(your name or business name — must match verified org)_ | | ||
| | **Company URL** | _(your website URL)_ | | ||
| | **Privacy Policy URL** | _(required — must disclose academic data returned by tools)_ | | ||
| | Field | Value | | ||
| | ---------------------- | -------------------------------------------------------------------------------------------------- | | ||
| | **App Name** | Exactamente | | ||
| | **Description** | Search and download academic materials — exams, summaries, and finals from Argentine universities. | | ||
| | **Company Name** | _(your name or business name — must match verified org)_ | | ||
| | **Company URL** | _(your website URL)_ | | ||
| | **Privacy Policy URL** | _(required — must disclose academic data returned by tools)_ | | ||
|
|
||
| ### MCP Server Configuration | ||
|
|
||
| | Field | Value | | ||
| |---|---| | ||
| | **MCP Server URL** | `https://exactamente-mcp.<your-subdomain>.workers.dev/mcp` | | ||
| | **Authentication** | None (public API) | | ||
| | **Template MCP Server URL** | _(leave blank — universal endpoint)_ | | ||
| | Field | Value | | ||
| | --------------------------- | ---------------------------------------------------------- | | ||
| | **MCP Server URL** | `https://exactamente-mcp.<your-subdomain>.workers.dev/mcp` | | ||
| | **Authentication** | None (public API) | | ||
| | **Template MCP Server URL** | _(leave blank — universal endpoint)_ | | ||
|
|
||
| ### Tool Information | ||
|
|
||
| | Tool | Description | readOnlyHint | destructiveHint | openWorldHint | | ||
| |---|---|---|---|---| | ||
| | `health-check` | Check backend health status | `true` | `false` | `false` | | ||
| | `list-universities` | List available universities | `true` | `false` | `false` | | ||
| | `list-faculties` | List faculties, optionally by university | `true` | `false` | `false` | | ||
| | `list-careers` | List careers, optionally by faculty | `true` | `false` | `false` | | ||
| | `search-subjects` | Search and filter subjects | `true` | `false` | `false` | | ||
| | `get-subject` | Get detailed subject information | `true` | `false` | `false` | | ||
| | `list-resources` | List published study resources | `true` | `false` | `false` | | ||
| | `find-subject-materials` | Combined subject + resource search | `true` | `false` | `false` | | ||
| | `download-resource` | Get download URL for a resource file | `true` | `false` | `false` | | ||
| | Tool | Description | readOnlyHint | destructiveHint | openWorldHint | | ||
| | ------------------------ | ---------------------------------------- | ------------ | --------------- | ------------- | | ||
| | `health-check` | Check backend health status | `true` | `false` | `false` | | ||
| | `list-universities` | List available universities | `true` | `false` | `false` | | ||
| | `list-faculties` | List faculties, optionally by university | `true` | `false` | `false` | | ||
| | `list-careers` | List careers, optionally by faculty | `true` | `false` | `false` | | ||
| | `search-subjects` | Search and filter subjects | `true` | `false` | `false` | | ||
| | `get-subject` | Get detailed subject information | `true` | `false` | `false` | | ||
| | `list-resources` | List published study resources | `true` | `false` | `false` | | ||
| | `find-subject-materials` | Combined subject + resource search | `true` | `false` | `false` | | ||
| | `download-resource` | Get download URL for a resource file | `true` | `false` | `false` | | ||
|
Comment on lines
+53
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Mark The agent-surface contract describes 🤖 Prompt for AI Agents |
||
|
|
||
| ### Test Prompts & Expected Responses | ||
|
|
||
|
|
@@ -69,29 +69,35 @@ Provide at least 3 test cases. Each must pass on both ChatGPT web and mobile. | |
| #### Test 1: Explore universities | ||
|
|
||
| **Prompt:** | ||
|
|
||
| > Listá las universidades disponibles | ||
|
|
||
| **Expected behavior:** | ||
|
|
||
| - Calls `list-universities` | ||
| - Returns a list of universities with names and IDs | ||
| - Suggests next action: explore faculties of a university | ||
|
|
||
| #### Test 2: Search for exam materials | ||
|
|
||
| **Prompt:** | ||
|
|
||
| > Buscá parciales de Análisis Matemático | ||
|
|
||
| **Expected behavior:** | ||
|
|
||
| - Calls `search-subjects` with search="Análisis Matemático" | ||
| - Calls `list-resources` or `find-subject-materials` with type="parcial" | ||
| - Returns matching exam resources with titles, years, and download URLs | ||
|
|
||
| #### Test 3: Download a specific resource | ||
|
|
||
| **Prompt:** | ||
|
|
||
| > Descargá el final de Álgebra de 2024 | ||
|
|
||
| **Expected behavior:** | ||
|
|
||
| - Calls `search-subjects` with search="Álgebra" | ||
| - Calls `list-resources` with type="final" | ||
| - Calls `download-resource` with the matching resource ID | ||
|
|
@@ -100,19 +106,23 @@ Provide at least 3 test cases. Each must pass on both ChatGPT web and mobile. | |
| #### Test 4: Navigate career structure | ||
|
|
||
| **Prompt:** | ||
|
|
||
| > Mostrame las materias de primer año de Ingeniería en Sistemas | ||
|
|
||
| **Expected behavior:** | ||
|
|
||
| - Calls `list-universities` → `list-faculties` → `list-careers` to find the career | ||
| - Calls `search-subjects` with careerId and year=1 | ||
| - Returns a list of first-year subjects for that career | ||
|
|
||
| #### Test 5: Health check | ||
|
|
||
| **Prompt:** | ||
|
|
||
| > Verificá la conexión con Exactamente | ||
|
|
||
| **Expected behavior:** | ||
|
|
||
| - Calls `health-check` | ||
| - Returns status "ok" with timestamp | ||
| - Confirms the backend is reachable | ||
|
|
@@ -128,9 +138,9 @@ Capture screenshots showing the app working correctly on: | |
|
|
||
| ### Localization | ||
|
|
||
| | Field | Value | | ||
| |---|---| | ||
| | **Primary Language** | Spanish (es) | | ||
| | Field | Value | | ||
| | ----------------------- | ------------------------------------- | | ||
| | **Primary Language** | Spanish (es) | | ||
| | **Supported Countries** | Argentina (AR) — add others as needed | | ||
|
|
||
| --- | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export default { | ||
| extends: ['@commitlint/config-conventional'], | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import js from '@eslint/js'; | ||
| import prettier from 'eslint-config-prettier'; | ||
| import globals from 'globals'; | ||
| import tseslint from 'typescript-eslint'; | ||
|
|
||
| export default tseslint.config( | ||
| { | ||
| ignores: [ | ||
| 'dist/**', | ||
| '.xmcp/**', | ||
| '.wrangler/**', | ||
| 'node_modules/**', | ||
| 'worker.js', | ||
| 'xmcp-env.d.ts', | ||
| ], | ||
| }, | ||
|
|
||
| js.configs.recommended, | ||
| ...tseslint.configs.recommended, | ||
|
|
||
| { | ||
| files: ['**/*.ts'], | ||
| languageOptions: { | ||
| // Corre en Workers y en Node (transporte stdio), así que los globals | ||
| // de ambos son válidos. | ||
| globals: { ...globals.node, ...globals.worker }, | ||
| }, | ||
| rules: { | ||
| // `_` para lo que se descarta a propósito, e ignoreRestSiblings para el | ||
| // patrón `const { a, ...resto } = obj`. | ||
| '@typescript-eslint/no-unused-vars': [ | ||
| 'error', | ||
| { argsIgnorePattern: '^_', varsIgnorePattern: '^_', ignoreRestSiblings: true }, | ||
| ], | ||
| }, | ||
| }, | ||
|
|
||
| // Prettier último: apaga todo lo que sea de formato. | ||
| prettier, | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| pre-commit: | ||
| parallel: true | ||
| commands: | ||
| format: | ||
| glob: '*.{ts,js,mjs,json,jsonc,md}' | ||
| run: pnpm exec prettier --write {staged_files} | ||
| stage_fixed: true | ||
| skip: | ||
| - merge | ||
| - rebase | ||
| lint: | ||
| glob: '*.ts' | ||
| run: pnpm exec eslint {staged_files} | ||
| skip: | ||
| - merge | ||
| - rebase | ||
| test: | ||
| glob: '*.ts' | ||
| run: pnpm exec vitest run --changed | ||
|
Comment on lines
+1
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: In Lefthook, you can combine parallel execution and automatic re-staging of modified files using the parallel and stage_fixed options within your configuration [1][2][3]. Parallel Execution: To run commands or scripts concurrently within a hook (such as pre-commit), set parallel to true at the hook level [2][4]. By default, Lefthook executes tasks sequentially [1][2]. Example of parallel configuration: pre-commit: parallel: true commands: lint: run: npm run lint --fix {staged_files} stage_fixed: true test: run: npm test stage_fixed: true stage_fixed: When stage_fixed is set to true, Lefthook automatically executes git add on the files modified by the command or script after it finishes running [1][3]. This feature is specifically designed for the pre-commit hook [3]. Key details regarding stage_fixed: - If a files option is specified for the command, Lefthook uses those files for git add [1][3]. - If no files option is provided, the {staged_files} template is used automatically [1][3]. - Any glob or exclude filters applied to the command are respected during the re-staging process [1][3]. You can also group jobs to control execution flow more granularly (e.g., mixing parallel and piped execution) using the jobs key [5][6]. Grouped jobs can have their own parallel or piped settings, allowing for complex workflows within a single hook [5][6]. Sources: [1] https://github.com/evilmartians/lefthook/blob/v1.7.15/docs/configuration.md [2] https://lefthook.dev/configuration/parallel/ [5] https://lefthook.dev/configuration/jobs/ [3] https://lefthook.dev/configuration/stage_fixed/ [6] https://lefthook.dev/configuration/group/ [4] https://github.com/evilmartians/lefthook/blob/master/README.md Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== lefthook.yml =="
if [ -f lefthook.yml ]; then
cat -n lefthook.yml
else
fd -a 'lefthook\.(ya?ml|yml)' . || true
fi
echo
echo "== pnpm prettier/eslint/vitest versions/dependencies =="
if [ -f package.json ]; then
node -e 'const p=require("./package.json"); console.log(JSON.stringify({packageManager:p.packageManager, scripts:p.scripts, dependencies:p.dependencies, devDependencies:p.devDependencies}, null, 2))'
fi
echo
echo "== lockfile entries =="
for f in pnpm-lock.yaml package-lock.json yarn.lock; do
[ -f "$f" ] && { echo "--- $f ---"; rg -n '^( +(prettier|eslint|vitest|lefthook)|[^ ]+ *):|lefthook|prettier|eslint|vitest' "$f" | head -80; }
done
echo
echo "== git staged files relevant =="
git status --short --short || true
git diff --cached --name-only -- *.{ts,js,mjs,json,jsonc,md} || trueRepository: exactamente-ar/exactamente-mcp Length of output: 5419 🌐 Web query:
💡 Result: In Lefthook, the stage_fixed option is designed specifically for the pre-commit hook to automatically stage files modified by a command or script [1][2]. When set to true, Lefthook executes git add on the files affected by your command after it completes [1][3]. Configuration and Behavior When stage_fixed: true is enabled: - Lefthook automatically calls git add on the files [1][4]. - If you have specified a files option for the command, those files are used [1][3]. - If no files option is specified, the {staged_files} template is used as the default target for git add [1][3]. - Filters such as glob and exclude are respected and applied during the staging process [1][3]. Example Configuration: pre-commit: commands: lint: run: npm run lint --fix {staged_files} stage_fixed: true Parallel Execution Risks While Lefthook supports parallel: true at the hook level to increase performance, combining this with stage_fixed: true can lead to race conditions [5][6][7]. Because multiple commands may attempt to write to the git index simultaneously, users have reported intermittent hangs or errors during the commit process [7]. If you experience these issues, it is recommended to set parallel: false for the affected hook group to ensure sequential, stable execution of the staging process [7]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== package manager availability =="
command -v pnpm || true
echo "pnpm version: $(pnpm --version 2>/dev/null || true)"
echo
echo "== repository package manager files =="
cat package.json | sed -n '1,80p'
[ -f package.json ] && [ -f pnpm-lock.yaml ] && echo "repository uses pnpm via packageManager and pnpm-lock.yaml"Repository: exactamente-ar/exactamente-mcp Length of output: 1814 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Read-only probe: parse lefthook.yml and inspect file patterns used by each pre-commit command.
python3 - <<'PY'
from pathlib import Path
import re
cfg = Path("lefthook.yml").read_text()
commands = re.findall(r'^( +)([A-Za-z0-9_][A-Za-z0-9_\- ]*[A-Za-z0-9_]*)[ ]*:[ \t\n]', cfg, re.M)[-20:]
print("commands:", commands)
glob_re = re.compile(r"glob:\s*'(.*?)'|glob:\s*\"(.*?)\"")
for idx, line in enumerate(cfg.splitlines(), 1):
m = glob_re.match(line.strip())
if m:
print(f"{idx}: {m.group(1) or m.group(2)}")
PY
echo
echo "== deterministic subset overlap from lefthook.yml =="
python3 - <<'PY'
python3 - <<'PY'
import yaml
from pathlib import Path
cfg = yaml.safe_load(Path("lefthook.yml").read_text())
pre = cfg.get("pre-commit", {})
print("parallel:", pre.get("parallel"))
for name, cmd in pre.get("commands", {}).items():
print(name, "glob:", cmd.get("glob"), "stage_fixed:", cmd.get("stage_fixed"))
PY
filesets = []
for name, cmd in pre.get("commands", {}).items():
s = cmd.get("glob", "")
sets = {line: (bool("ts" in line) and line.endswith(".ts")) for line in s.split()}
filesets.append((name, sets))
for _ in filesets:
pass
PYRepository: exactamente-ar/exactamente-mcp Length of output: 768 🌐 Web query:
💡 Result: In Lefthook, stage_fixed and parallel are configuration options that serve distinct purposes within hook definitions, such as pre-commit [1][2][3]. stage_fixed This is a boolean option (default: false) that applies only to the pre-commit hook [1][2]. When set to true, Lefthook automatically executes git add on the files modified by the command or script after it finishes [1]. - If a files option is specified for the command, Lefthook uses that command to identify which files to stage [1][2]. - If no files option is used, Lefthook defaults to using the {staged_files} template to identify files [1][2]. - All defined filters (such as glob or exclude) are applied to the files being staged [1][2]. parallel This is a boolean option (default: false) that allows commands and scripts within a hook to run concurrently rather than sequentially [2][3]. Interaction between stage_fixed and parallel When both options are used together, Lefthook executes the commands concurrently [4]. If multiple commands are configured with stage_fixed: true and they modify the same files, they will run in parallel, and the file state will be determined by whichever command completes last [4]. Crucially, even when running concurrently, Lefthook will stage the files for all commands as they finish, regardless of the parallel execution [4]. Example usage: pre-commit: parallel: true commands: lint: run: npm run lint --fix {staged_files} stage_fixed: true format: run: npx prettier --write {staged_files} stage_fixed: true Citations:
Serialize formatting before lint and tests.
🤖 Prompt for AI AgentsSource: MCP tools |
||
| skip: | ||
| - merge | ||
| - rebase | ||
|
|
||
| pre-push: | ||
| commands: | ||
| typecheck: | ||
| run: pnpm typecheck | ||
| skip: | ||
| - merge | ||
| - rebase | ||
| test: | ||
| run: pnpm test | ||
| skip: | ||
| - merge | ||
| - rebase | ||
|
|
||
| commit-msg: | ||
| commands: | ||
| commitlint: | ||
| run: pnpm exec commitlint --edit {1} | ||
| skip: | ||
| - merge | ||
| - rebase | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: exactamente-ar/exactamente-mcp
Length of output: 313
🌐 Web query:
actions/checkout persist-credentials default true README💡 Result:
The persist-credentials input for the actions/checkout GitHub Action controls whether the repository's authentication token or SSH key is configured in the local git environment [1][2][3]. Key details include: - Default Value: The default is true [3][4]. - Functionality: When set to true, the action configures the token or SSH key with the local git config (or, in more recent versions, a secure file location managed by the runner) [1][5][6]. This allows subsequent steps in your workflow to execute authenticated git commands, such as git fetch or git push, without needing to manually provide credentials [1][5]. - Security: The token is automatically removed by the action during the post-job cleanup phase [1][7]. - Opt-out: You can disable this behavior by explicitly setting persist-credentials: false in your workflow file if you do not need git commands to be authenticated or prefer to manage credentials manually [1][7]. Beginning with version v6.0.0, the action was updated to store these credentials in a secure location under $RUNNER_TEMP rather than directly in the local.git/config file, improving security for the persisted credentials [5][6].
Citations:
🏁 Script executed:
Repository: exactamente-ar/exactamente-mcp
Length of output: 2733
Drop the checkout command from this comment.
The issue is valid: both workflows use
actions/checkoutwithoutpersist-credentials: falseand without read-only contents permissions, leavingGITHUB_TOKENavailable to later repository-controlled steps. However, the pinnedactions/checkout@v7reference does not need to be part of the command here.🧰 Tools
🪛 zizmor (1.26.1)
[warning] 18-18: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
📍 Affects 2 files
.github/workflows/ci.yml#L18-L18(this comment).github/workflows/deploy.yml#L19-L21🤖 Prompt for AI Agents
Sources: MCP tools, Linters/SAST tools