Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Reformateo masivo con Prettier — no aporta nada al blame.
1a60335c1bc179b3263d2f9a62c02c28b9484cc4
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
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

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


- 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
37 changes: 37 additions & 0 deletions .github/workflows/deploy.yml
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

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


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

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


- run: pnpm build:cf

- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
7 changes: 7 additions & 0 deletions .prettierignore
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
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"singleQuote": true,
"semi": true,
"printWidth": 100
}
62 changes: 36 additions & 26 deletions CHATGPT_SUBMISSION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

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.


### Test Prompts & Expected Responses

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 |

---
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ Tool responses include `structuredContent.agentHints.nextActions` where useful,
Compact material search:

```json
{ "tool": "find-subject-materials", "args": { "search": "algoritmos", "type": "parcial", "limit": 5 } }
{
"tool": "find-subject-materials",
"args": { "search": "algoritmos", "type": "parcial", "limit": 5 }
}
```

Manual resource lookup:
Expand Down Expand Up @@ -114,6 +117,7 @@ npm run deploy:secret
```

The worker wrapper (`src/worker.ts`) adds:

- `/.well-known/openai-apps-challenge` route for domain verification
- Content Security Policy headers (`default-src 'none'; connect-src https://api.exactamente.com.ar`)
- Security headers (`X-Content-Type-Options`, `X-Frame-Options`)
Expand Down Expand Up @@ -149,6 +153,7 @@ npx wrangler dev
## ChatGPT Apps submission

See [CHATGPT_SUBMISSION.md](./CHATGPT_SUBMISSION.md) for:

- Deployment steps
- Submission form field values
- Test prompts and expected responses
Expand Down
3 changes: 3 additions & 0 deletions commitlint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
extends: ['@commitlint/config-conventional'],
};
40 changes: 40 additions & 0 deletions eslint.config.mjs
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,
);
43 changes: 43 additions & 0 deletions lefthook.yml
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

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

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
Loading