Skip to content

chore(tooling): piso de calidad — ESLint, Prettier, lefthook, commitlint y CI - #2

Merged
JuanPE44 merged 2 commits into
mainfrom
juanpe44/tooling-calidad
Jul 27, 2026
Merged

chore(tooling): piso de calidad — ESLint, Prettier, lefthook, commitlint y CI#2
JuanPE44 merged 2 commits into
mainfrom
juanpe44/tooling-calidad

Conversation

@JuanPE44

@JuanPE44 JuanPE44 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Por qué

Este repo no tenía ningún gate: sin linter, sin formateo, sin hooks, sin CI. Se replica el piso que ya usa exactamente-frontend, adaptado al stack de xmcp/Workers.

Qué entra

ESLint flat config + typescript-eslint, con eslint-config-prettier para que no se peleen por formato
Prettier mismo estilo que el frontend (comillas simples, 100 cols)
lefthook pre-commit: formatea y lintea lo staged + tests afectados · pre-push: typecheck + suite completa · commit-msg: Conventional Commits
CI en PRs a main: typecheck, lint, format:check, test, build:cf
Deploy wrangler deploy encadenado a que CI pase en main

Unificación en pnpm

package-lock.json y pnpm-lock.yaml convivían — instalación no determinística. Se elimina el de npm y se declara packageManager: pnpm@10.28.0.

El lockfile de pnpm estaba desactualizado: wrangler y @cloudflare/workers-types se habían agregado con npm, así que pnpm install --frozen-lockfile fallaba. Regenerado.

Dos decisiones que vale explicar

  • typescript queda en ^6, no ^7. typescript-eslint 8.x no soporta TS 7 todavía — con TS 7 el linter directamente no arranca.
  • eslint.config.mjs, no .js. El paquete no declara "type": "module"; agregarlo cambiaría cómo se resuelve el bin publicado (dist/stdio.js). La extensión .mjs resuelve lo mismo sin tocar el paquete.

Commits separados

  1. style: — reformateo con Prettier, sin cambios de comportamiento. Anotado en .git-blame-ignore-revs.
  2. chore(tooling): — el setup en sí.

⚠️ El commit de formato arrastra tu cambio sin commitear en wrangler.jsonc (el custom domain mcp.exactamente.com.ar). No había forma de separarlo: Prettier tenía que reformatear ese archivo igual para que format:check pase en CI.

Antes de mergear

El workflow de deploy necesita dos secrets en el repo:

  • CLOUDFLARE_API_TOKEN
  • CLOUDFLARE_ACCOUNT_ID

Sin ellos el CI pasa igual, pero el deploy falla.

Verificación

typecheck     limpio
lint          limpio
format:check  All matched files use Prettier code style
test          4 archivos · 15 tests · todos pasan
build:cf      Compiled in 1768ms

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added access through the custom domain mcp.exactamente.com.ar.
    • Improved subject details with clearer Spanish descriptions and more reliable guidance for finding related resources.
    • Enhanced tool responses with clearer pagination and next-step recommendations.
  • Documentation

    • Clarified workflow examples, tool metadata, expected responses, and localization details.
  • Improvements

    • Standardized formatting and validation across the project for more consistent behavior and maintainability.

JuanPE44 and others added 2 commits July 27, 2026 17:53
Commit solo de formato, sin cambios de comportamiento. Se aísla acá para
que el diff del tooling se pueda leer, y queda en .git-blame-ignore-revs
para que no ensucie los blame.

Arrastra el cambio de dominio custom en wrangler.jsonc que estaba sin
commitear en el working tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El repo no tenía ningún gate de calidad. Se replica el piso que ya usa
exactamente-frontend, adaptado a este stack.

- ESLint (flat config) + Prettier, con eslint-config-prettier para que no
  se peleen por formato
- lefthook: pre-commit formatea y lintea lo staged y corre los tests
  afectados; pre-push corre typecheck y la suite completa; commit-msg
  valida Conventional Commits
- CI en PRs a main: typecheck, lint, format:check, test y build:cf
- Deploy a Cloudflare encadenado a que CI pase en main

Se unifica en pnpm: se elimina package-lock.json (convivía con
pnpm-lock.yaml) y se declara packageManager. El lockfile de pnpm estaba
desactualizado porque wrangler se había instalado con npm.

typescript queda en ^6 y no ^7: typescript-eslint 8.x todavía no soporta
TS 7 y el lint no arranca. eslint.config va como .mjs en vez de .js porque
el paquete no declara "type": "module" y agregarlo cambiaría cómo se
resuelve el bin publicado.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds CI and deployment workflows, repository formatting and hook configuration, agent-surface test coverage, documentation updates, and broad Prettier-style normalization across source files.

Changes

Repository automation and agent-surface updates

Layer / File(s) Summary
Quality and developer tooling
.git-blame-ignore-revs, .prettier*, commitlint.config.ts, eslint.config.mjs, lefthook.yml, package.json
Adds formatting, linting, commit validation, hook, testing, and package-manager configuration.
CI and Cloudflare deployment workflows
.github/workflows/*, wrangler.jsonc
Adds quality checks for pull requests and main-branch pushes, followed by deployment after successful CI.
Agent surface and tool response validation
CHATGPT_SUBMISSION.md, README.md, src/__tests__/*, src/tools/*, src/lib/toolShared.ts
Updates agent-facing documentation, structured next actions, subject details, and related assertions.
Source formatting normalization
src/client/*, src/config*, src/prompts/*, src/resources/*, src/middleware.ts, src/worker.ts, src/types/*, xmcp.config.ts
Applies single-quote, semicolon, wrapping, and import-formatting conventions without changing the described APIs or control flow.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: d4rm5

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main tooling, quality checks, and CI changes in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch juanpe44/tooling-calidad

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
package.json (1)

11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not silently ignore hook installation failures.

|| true makes pnpm install succeed even when Lefthook is misconfigured or cannot install hooks, silently disabling the local quality gate. Let installation fail, or explicitly skip it only in environments where hooks are intentionally unavailable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 11, Update the package.json prepare script to remove
the unconditional “|| true” fallback from lefthook installation, allowing
prepare to fail when hook installation fails; only add an explicit
environment-based skip if the project already defines a supported mechanism for
environments where hooks are unavailable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Line 18: Update the actions/checkout steps in .github/workflows/ci.yml lines
18-18 and .github/workflows/deploy.yml lines 19-21 to disable persisted
credentials and ensure repository contents permissions are read-only for
subsequent steps. Apply the same security configuration to both workflow
checkout steps; do not include the pinned checkout reference as part of the
comment text.

In @.github/workflows/deploy.yml:
- Around line 25-30: Remove the cache configuration from the
actions/setup-node@v7 step in the deploy job, specifically delete the cache:
pnpm setting while preserving node-version: 22 and the existing frozen-lockfile
installation.
- Around line 4-8: Restrict the privileged deployment workflow’s workflow_run
trigger to successful CI runs originating from a push to main, and ensure its
job condition validates the run event before proceeding. Do not deploy
PR-triggered runs or check out and rebuild github.event.workflow_run.head_sha;
instead consume the artifact produced by the trusted CI run.

In `@CHATGPT_SUBMISSION.md`:
- Around line 53-63: Update the download-resource row in the tool metadata table
so its readOnlyHint is false, while preserving its existing destructiveHint and
openWorldHint values.

In `@lefthook.yml`:
- Around line 1-19: Update the pre-commit configuration to run the format
command before lint and test rather than in parallel. Preserve the existing
format, lint, test, glob, skip, and stage_fixed settings, but use Lefthook’s
sequential/piped command grouping so lint and tests consume Prettier’s formatted
and restaged output.

---

Nitpick comments:
In `@package.json`:
- Line 11: Update the package.json prepare script to remove the unconditional
“|| true” fallback from lefthook installation, allowing prepare to fail when
hook installation fails; only add an explicit environment-based skip if the
project already defines a supported mechanism for environments where hooks are
unavailable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d7219b9-2d83-425a-8cba-7f60172ebdbd

📥 Commits

Reviewing files that changed from the base of the PR and between 2e9fa92 and 0d0f4ad.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (38)
  • .git-blame-ignore-revs
  • .github/workflows/ci.yml
  • .github/workflows/deploy.yml
  • .prettierignore
  • .prettierrc
  • CHATGPT_SUBMISSION.md
  • README.md
  • commitlint.config.ts
  • eslint.config.mjs
  • lefthook.yml
  • package.json
  • src/__tests__/agentSurface.spec.ts
  • src/__tests__/downloadResource.spec.ts
  • src/client/exactamenteApi.spec.ts
  • src/client/exactamenteApi.ts
  • src/config.spec.ts
  • src/config.ts
  • src/lib/toolShared.ts
  • src/middleware.ts
  • src/prompts/buscar-materiales-de-materia.ts
  • src/prompts/descargar-parcial.ts
  • src/prompts/diagnosticar-conexion.ts
  • src/prompts/explorar-carrera.ts
  • src/resources/(exactamente)/meta.ts
  • src/resources/(subjects)/[subjectId]/details.ts
  • src/tools/download-resource.ts
  • src/tools/find-subject-materials.ts
  • src/tools/get-subject.ts
  • src/tools/health-check.ts
  • src/tools/list-careers.ts
  • src/tools/list-faculties.ts
  • src/tools/list-resources.ts
  • src/tools/list-universities.ts
  • src/tools/search-subjects.ts
  • src/types/worker.d.ts
  • src/worker.ts
  • wrangler.jsonc
  • xmcp.config.ts

Comment thread .github/workflows/ci.yml
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

Copy link
Copy Markdown

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:

#!/bin/bash
set -euo pipefail

echo "== workflow files =="
git ls-files '.github/workflows/*' || true

echo
echo "== ci.yml relevant lines =="
if [ -f .github/workflows/ci.yml ]; then
  nl -ba .github/workflows/ci.yml | sed -n '1,120p'
fi

echo
echo "== deploy.yml relevant lines =="
if [ -f .github/workflows/deploy.yml ]; then
  nl -ba .github/workflows/deploy.yml | sed -n '1,140p'
fi

echo
echo "== occurrences of action/checkout and persist-credentials / permissions =="
rg -n "actions/checkout|persist-credentials|permissions:|GITHUB_TOKEN|GitHub Token|github-token" .github/workflows || true

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:

#!/bin/bash
set -euo pipefail

echo "== workflow files =="
git ls-files '.github/workflows/*'

echo
echo "== ci.yml relevant lines =="
if [ -f .github/workflows/ci.yml ]; then
  awk '{printf "%6d: %s\n", NR, $0}' .github/workflows/ci.yml | sed -n '1,160p'
fi

echo
echo "== deploy.yml relevant lines =="
if [ -f .github/workflows/deploy.yml ]; then
  awk '{printf "%6d: %s\n", NR, $0}' .github/workflows/deploy.yml | sed -n '1,160p'
fi

echo
echo "== occurrences of action/checkout and permissions / persist-credentials =="
if command -v rg >/dev/null 2>&1; then
  rg -n "actions/checkout@|persist-credentials|permissions:|GITHUB_TOKEN|github-token|ref:" .github/workflows || true
else
  grep -RIn "actions/checkout@\\|persist-credentials\\|permissions:\\|GITHUB_TOKEN\\|github-token\\|ref:" .github/workflows || true
fi

Repository: exactamente-ar/exactamente-mcp

Length of output: 2733


Drop the checkout command from this comment.

The issue is valid: both workflows use actions/checkout without persist-credentials: false and without read-only contents permissions, leaving GITHUB_TOKEN available to later repository-controlled steps. However, the pinned actions/checkout@v7 reference 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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 18, Update the actions/checkout steps in
.github/workflows/ci.yml lines 18-18 and .github/workflows/deploy.yml lines
19-21 to disable persisted credentials and ensure repository contents
permissions are read-only for subsequent steps. Apply the same security
configuration to both workflow checkout steps; do not include the pinned
checkout reference as part of the comment text.

Sources: MCP tools, Linters/SAST tools

Comment on lines +4 to +8
on:
workflow_run:
workflows: [CI]
types: [completed]
branches: [main]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 || true

Repository: exactamente-ar/exactamente-mcp

Length of output: 1880


🌐 Web query:

GitHub Actions workflow_run security checkout github.event.workflow_run.head_sha head_branch push trusted artifact

💡 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
PY

Repository: exactamente-ar/exactamente-mcp

Length of output: 1339


Do not checkout CI’s head_sha in a privileged deploy workflow.

CI runs on both pull_request and push, but this workflow checks out github.event.workflow_run.head_sha, rebuilds it, and then applies Cloudflare deployment secrets. A PR that triggers a successful CI run can therefore cause execution under these privileged secrets; gate deployment on a successful push CI run (head_branch == main has been checked through this repo’s workflow configuration). Prefer deploying an artifact produced by CI rather than rebuilding checked-out source.

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 Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy.yml around lines 4 - 8, Restrict the privileged
deployment workflow’s workflow_run trigger to successful CI runs originating
from a push to main, and ensure its job condition validates the run event before
proceeding. Do not deploy PR-triggered runs or check out and rebuild
github.event.workflow_run.head_sha; instead consume the artifact produced by the
trusted CI run.

Sources: MCP tools, Linters/SAST tools

Comment on lines +25 to +30
- uses: actions/setup-node@v7
with:
node-version: 22
cache: pnpm

- run: pnpm install --frozen-lockfile

Copy link
Copy Markdown

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:

#!/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 || true

Repository: 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:

GitHub Actions dependency caching cache poisoning setup-node cache unsigned unauthorized

💡 Result:

Cache poisoning in GitHub Actions, particularly when using actions/setup-node, is a security risk where an attacker injects malicious content into the dependency cache [1][2][3]. Because GitHub Actions caches are shared based on the branch or tag—not the job's identity or privilege level—any workflow run that can read a cache may extract its contents [4][5]. If a subsequent, more privileged workflow (such as a release or publishing job) restores this poisoned cache, it may execute malicious code or expose sensitive secrets [1][2][3][5]. Key security considerations and mitigations include: 1. Disable Automatic Caching in Privileged Workflows: actions/setup-node enables npm caching by default if packageManager is detected in package.json [6][7]. For workflows with elevated privileges or access to secrets (e.g., publishing workflows), you should explicitly disable this by setting package-manager-cache: false [8][6][9]. 2. Use Read-Only Cache Modes: To allow untrusted triggers (like pull requests) to benefit from cached dependencies without risk of them overwriting or poisoning the cache, use the cache-write input [10]. Setting cache-write: ${{ github.event_name != 'pull_request' }} ensures that pull request workflows only read from the cache and cannot modify it [10]. 3. Understand Cache Scope and Restrictions: GitHub enforces security boundaries by limiting which triggers can write to the cache [4][5]. Low-trust triggers, such as pull_request from a fork, are typically granted read-only access to caches in the default branch's scope [5]. However, these protections do not eliminate all risks, especially if a workflow is misconfigured [4]. 4. Never Store Secrets in Caches: Caches are not signed or verified [4]. Never store secrets, tokens, or credentials in a cached path [4]. If an attacker can open a pull request, they may be able to read the contents of the cache [4]. 5. General Best Practices: Always use npm ci rather than npm install to ensure your build strictly follows the committed package-lock.json, preventing the use of arbitrary dependencies that might have been injected into a local cache [2]. Ensure you are not pairing dependency caching with privileged pull_request_target workflows or untrusted forks [2]. Technical note: A 403 error during caching often relates to branch-specific or event-based access restrictions enforced by the GitHub Actions cache service, rather than a failure of the action itself [11]. Always check official documentation for the latest guidance on cache scopes and security configurations [4][5].

Citations:


Remove dependency caching from the privileged deploy job.

setup-node with cache: pnpm restores dependencies from the shared Actions cache, and GitHub caches are not signed/verified. Since this job uses workflow_run: CI, also checks out head_sha and then passes Cloudflare secrets to an external action, disabling caching here removes an avoidable cache-poison path.

Safer deployment setup
       - uses: actions/setup-node@v7
         with:
           node-version: 22
-          cache: pnpm
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/setup-node@v7
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: actions/setup-node@v7
with:
node-version: 22
- run: pnpm install --frozen-lockfile
🧰 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 Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy.yml around lines 25 - 30, Remove the cache
configuration from the actions/setup-node@v7 step in the deploy job,
specifically delete the cache: pnpm setting while preserving node-version: 22
and the existing frozen-lockfile installation.

Sources: MCP tools, Linters/SAST tools

Comment thread CHATGPT_SUBMISSION.md
Comment on lines +53 to +63
| 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` |

Copy link
Copy Markdown

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

Mark download-resource as effectful.

The agent-surface contract describes download-resource as writing, but this table sets readOnlyHint to true. Set it to false so clients do not auto-run a write as a read-only action.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHATGPT_SUBMISSION.md` around lines 53 - 63, Update the download-resource row
in the tool metadata table so its readOnlyHint is false, while preserving its
existing destructiveHint and openWorldHint values.

Comment thread lefthook.yml
Comment on lines +1 to +19
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

lefthook configuration pre-commit parallel commands stage_fixed stage_fixed true documentation

💡 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} || true

Repository: exactamente-ar/exactamente-mcp

Length of output: 5419


🌐 Web query:

lefthook git add {staged_files} implementation stage_fixed pre-commit parallel

💡 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
PY

Repository: exactamente-ar/exactamente-mcp

Length of output: 768


🌐 Web query:

lefthook source run_hook parallel stage_fixed gitAdd

💡 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.

pre-commit.parallel = true runs format, lint, and test concurrently while format.stage_fixed rewrites and restages files. Use a piped/sequential group so Prettier formats the staged files first, then lint/test run on the output; otherwise formatting can change after lint/test have already read the unformatted source.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lefthook.yml` around lines 1 - 19, Update the pre-commit configuration to run
the format command before lint and test rather than in parallel. Preserve the
existing format, lint, test, glob, skip, and stage_fixed settings, but use
Lefthook’s sequential/piped command grouping so lint and tests consume
Prettier’s formatted and restaged output.

Source: MCP tools

@JuanPE44
JuanPE44 merged commit c1f348b into main Jul 27, 2026
2 checks passed
@JuanPE44
JuanPE44 deleted the juanpe44/tooling-calidad branch July 27, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant