feat(using-projects-meta): v1.1.0 — mandatory Step 0 freshness gate

Cross-machine workflows broke silently when the agent reasoned on a stale
MCP cache, or hit sha-based optimistic-lock 422s on shared-wiki writes
because another host had pushed minutes earlier. Codify the missing
pre-flight as Step 0:

  1. Always probe mcp__projects-meta__meta_status first.
  2. If cache_age_minutes > 10 OR errors_count > 0 → run
     `node ~/.local/projects-meta-mcp/dist/sync.js` before any read.
  3. For shared-wiki WRITES (knowledge_ingest, knowledge_promote) →
     unconditional `git -C ~/projects/projects-wiki pull --ff-only`
     (sha-lock will reject the commit otherwise; failure is opaque).
  4. Tasks-mutations: sync via dist/sync.js is enough (no local clone
     of target tasks repo).
  5. Sync 401/403 → STOP, send user to ~/.config/projects-mcp/auth.toml
     to rotate gitea_token. No silent retry.

10-minute cache window chosen as the read threshold — balances
multi-machine drift detection against unnecessary Gitea round-trips.

Common mistakes + Red flags expanded to call out the new failure modes.
README mirrors the rule for human readers. Wiki concept page
projects-meta-skills.md gets a "Freshness gate (v1.1.0)" subsection.

Closes [using-projects-meta-freshness-gate].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-30 00:33:05 +03:00
parent e744dad46b
commit 6c83a35d6d
7 changed files with 133 additions and 5 deletions

View File

@@ -49,6 +49,32 @@ wiki content. See the table below.
| "Has the **shared** wiki got X?" | `mcp__projects-meta__knowledge_search` |
| "Sync state across machines?" | `mcp__projects-meta__meta_status` |
## Step 0 — Freshness gate (v1.1.0, mandatory pre-flight)
`projects-meta` is a bus between machines — another host may have pushed
minutes ago. Without this gate, reads return stale data and writes hit
sha-based optimistic-lock 422s with no explanation.
Before **any** `tasks_*` or `knowledge_*` call:
1. `mcp__projects-meta__meta_status` — probe cache age + errors.
2. If `cache_age_minutes` > 10 OR `errors_count` > 0 →
`node ~/.local/projects-meta-mcp/dist/sync.js`.
3. For shared-wiki **writes** (`knowledge_ingest`, `knowledge_promote`) →
**also** `git -C ~/projects/projects-wiki pull --ff-only`. Unconditional.
The MCP server uses sha-based optimistic locking on the wiki repo;
without an up-to-date local SHA the commit is rejected with a 422.
4. For tasks-mutations (`tasks_create`/`update`/`close`) → sync via
`dist/sync.js` is enough; there's no local clone of the target tasks repo.
5. If sync returns **401 / 403** → STOP. Token is dead. Send the user to
`~/.config/projects-mcp/auth.toml` to rotate `gitea_token`. Don't
pretend success, don't retry silently.
**Don't sync unconditionally** on every call — overhead + 401-risk for
casual reads. The 10-minute window is the chosen threshold.
**Don't apply Step 0 to `meta_status` itself** — it's the probe.
## Two operation classes
### Read (no confirmation)

View File

@@ -1,7 +1,7 @@
---
name: using-projects-meta
version: 1.0.0
description: Use when working across multiple projects on one or many machines — cross-project task aggregation (`mcp__projects-meta__tasks_*`), shared Gitea-backed wiki query / ingest (`mcp__projects-meta__knowledge_*`), or sync diagnostics (`mcp__projects-meta__meta_status`). Triggers on phrases like "across all projects", "what's on the boards", "check shared wiki", "search projects-wiki", "ingest into shared wiki", "что у меня на досках", "по всем проектам", "общая вики", "cross-project status", or any time the user wants to see / mutate state in another repo than the current cwd. Mutation tools (`tasks_create`, `tasks_update`, `tasks_close`, `knowledge_ingest`, `knowledge_promote`) require two-step preview → confirm. Skip for the **current** project's tasks/wiki — those live on disk in `.tasks/` / `.wiki/` and are read directly.
version: 1.1.0
description: Use when working across multiple projects on one or many machines — cross-project task aggregation (`mcp__projects-meta__tasks_*`), shared Gitea-backed wiki query / ingest (`mcp__projects-meta__knowledge_*`), or sync diagnostics (`mcp__projects-meta__meta_status`). Triggers on phrases like "across all projects", "what's on the boards", "check shared wiki", "search projects-wiki", "ingest into shared wiki", "что у меня на досках", "по всем проектам", "общая вики", "cross-project status", or any time the user wants to see / mutate state in another repo than the current cwd. v1.1.0 mandates a Step 0 freshness gate (probe `meta_status`, sync if stale, pull `projects-wiki` before shared-wiki writes) — see SKILL body. Mutation tools require two-step preview → confirm. Skip for the **current** project's tasks/wiki — those live on disk in `.tasks/` / `.wiki/`.
---
# Using the projects-meta MCP server
@@ -56,11 +56,49 @@ Use MCP only for **other** projects, **other** machines, or **shared** wiki cont
- Library / framework documentation — that's `using-context7`.
- Code search — that's `Glob` / `Grep`.
## Step 0 — Freshness gate (run before any tool)
`projects-meta` is a bus between machines. Another host may have pushed minutes ago. Without this gate, reads return stale data and writes hit sha-based optimistic-lock 422s with no explanation. Mandatory pre-flight, every session, every workflow:
```
1. Call mcp__projects-meta__meta_status.
2. Branch on cache freshness:
• If cache_age_minutes > 10 OR errors_count > 0:
run `node ~/.local/projects-meta-mcp/dist/sync.js`
(or `npm run sync` from ~/.local/projects-meta-mcp).
• Else: cache is fresh enough — skip sync, no need to hit the network.
3. For shared-wiki WRITES (knowledge_ingest, knowledge_promote):
ALWAYS additionally run `git -C ~/projects/projects-wiki pull --ff-only`
regardless of cache age. The MCP server uses sha-based optimistic locking;
without an up-to-date local file SHA, the commit will be rejected (422)
and the failure mode is opaque to the user.
4. For tasks-mutations (tasks_create, tasks_update, tasks_close):
sync via dist/sync.js is enough — there's no local clone of the target
tasks repo, mutations go straight through Gitea API. Sync only refreshes
the local view so you reason from current state.
5. If sync returns 401 or 403:
STOP. The Gitea token in ~/.config/projects-mcp/auth.toml is dead or
wrong-scoped. Tell the user explicitly:
"Gitea sync failed with <401|403>. Rotate gitea_token in
~/.config/projects-mcp/auth.toml (Gitea: settings/applications)
and rerun."
Do not pretend sync succeeded. Do not retry silently.
```
**Don't sync unconditionally on every call.** Network overhead + risk of 401 even on a casual "what's on my boards". The 10-minute cache window is the right balance — catches multi-machine drift without burning Gitea round-trips for back-to-back questions.
**Don't apply Step 0 to `meta_status` itself** — it's the freshness probe, not a downstream read.
## Workflow
### Read (no confirmation needed)
```
0. Run Step 0 — Freshness gate (above) first.
1. Identify what you need: cross-project tasks? shared wiki page? sync state?
2. Pick the right read tool (table below).
3. Cite the result with the source slug / project name.
@@ -69,6 +107,9 @@ Use MCP only for **other** projects, **other** machines, or **shared** wiki cont
### Mutate (always two-step)
```
0. Run Step 0 — Freshness gate (above) first.
For shared-wiki writes (knowledge_ingest, knowledge_promote): unconditional
`git -C ~/projects/projects-wiki pull --ff-only` is part of Step 0.
1. Identify the mutation: tasks_create / tasks_update / tasks_close / knowledge_ingest / knowledge_promote.
2. Call the tool WITHOUT `confirm: true` → returns a dry-run preview (the proposed file diff and the Gitea commit message).
3. Show the preview to the user. Wait for explicit "ok" / "go" / "поехали".
@@ -180,7 +221,9 @@ User: "close `[projects-meta-skills]` in claude-skills"
| Reading current project's tasks via `tasks_get` instead of disk | Read `.tasks/STATUS.md` directly. MCP is for *other* projects. |
| Inlining `confirm: true` on the first mutation call | Always preview first; show user; only then `confirm: true`. |
| Using `knowledge_search` for the project's own wiki | The shared wiki is a separate Gitea repo. Local `.wiki/` is in cwd. |
| Acting on a stale `tasks_aggregate` without checking `meta_status` | If `age_seconds` > 3600 the cache may be behind reality. Either run `node dist/sync.js` or warn the user. |
| Acting on a stale `tasks_aggregate` without checking `meta_status` | Step 0 — Freshness gate is mandatory. If `cache_age_minutes` > 10 (or errors > 0), run `node ~/.local/projects-meta-mcp/dist/sync.js` first. |
| Skipping `git -C ~/projects/projects-wiki pull` before `knowledge_ingest` / `knowledge_promote` | sha-based optimistic lock will reject the commit (422) and the failure is opaque. Pull is unconditional for shared-wiki writes — fast-forward is a no-op when current. |
| Treating sync 401/403 as "MCP is fine, the page just doesn't exist yet" | 401/403 means the Gitea token is dead. Stop, tell the user to rotate `gitea_token` in `~/.config/projects-mcp/auth.toml`. Never guess on stale data. |
| Calling `knowledge_ingest` with the wrong `type` | `type` must be one of `entities` / `concepts` / `packages` / `sources` / `raw`. Mis-typed pages land in the wrong section and break `index.md`. |
| Vague `knowledge_search` queries ("auth", "config") | Specific multi-word queries return targeted snippets; vague ones return noise. |
| Forgetting `domain="all"` when searching across families | Default `domain` is auto-detected from cwd; use `"all"` if the wiki page lives in a different family. |
@@ -188,5 +231,6 @@ User: "close `[projects-meta-skills]` in claude-skills"
## Red flags
- "I'll just commit it directly" → no. Mutation tools have a preview step for a reason — silent writes to another repo are a recipe for drift.
- "The cache is fresh enough" → check `meta_status.age_seconds`. If it's >3600s and the user is about to act on the data, sync first.
- "The cache is fresh enough" → run Step 0. The 10-minute window is the threshold; below it skip sync, above it sync. Don't eyeball this — the bus moves fast in cross-machine sessions.
- "I'll skip the pull, my last write was 30 seconds ago" → another machine pushed in between. Always pull before shared-wiki writes; the sha-lock check is your only safety net.
- "I'll skip the wiki page" → if you're answering a cross-cutting question and there's no wiki page, that's a `knowledge_ingest` candidate. Surface it to the user.