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

@@ -1,5 +1,5 @@
# Task Board
_Updated: 2026-04-29_
_Updated: 2026-04-30_
<!--
Canonical layout. One block per task. Per-task deep context lives in
@@ -85,6 +85,15 @@ _Updated: 2026-04-29_
**Next action:** when triggered (skill count crosses threshold), evaluate variant B (grouped by family) vs variant C (flat + manifest) from the original brainstorm in `.wiki/concepts/repo-layout.md`
**Branch:** (not started)
## 🟢 [using-projects-meta-freshness-gate] — добавить Step 0 freshness check в using-projects-meta
**Status:** done
**Where I stopped:** Step 0 добавлен в `using-projects-meta/SKILL.md` (cache_age>10мин или errors>0 → `dist/sync.js` для read; shared-wiki write — безусловный `git pull --ff-only`; 401/403 → auth.toml rotation); зеркалирован в README; bump 1.0.0→1.1.0; rebuild+install OK; description trimmed до 924 chars после регрессии (полное Step 0 в description ~1450 chars сломало парсинг — harness зафолбэкнулся на H1, скилл потерял все триггеры); Common mistakes + Red flags расширены; concept page + log обновлены; new feedback memory `skill_description_length_limit`
**Next action:** (none — kept until merged)
**Branch:** master
---
## 🟢 [projects-meta-skills] — setup-projects-meta + using-projects-meta — пара скиллов для projects-meta-mcp
**Status:** done

View File

@@ -0,0 +1,34 @@
# using-projects-meta-freshness-gate
## Goal
Add an explicit Step 0 "Freshness gate" to `using-projects-meta` so the agent does not hit a stale MCP cache or a sha-based optimistic-lock conflict in cross-machine workflows. Pre-flight: always `meta_status`; if `cache_age` > 10 min or `errors_count` > 0, run `node ~/.local/projects-meta-mcp/dist/sync.js` for reads; for shared-wiki writes (`knowledge_ingest`, `knowledge_promote`) additionally `git -C ~/projects/projects-wiki pull --ff-only`. 401/403 from sync → token expired → send user to `~/.config/projects-mcp/auth.toml`. Tasks-mutations need only the sync (no local clone of target tasks repo). Bump skill 1.0.0 → 1.1.0 (additive, non-breaking).
## Key files
- `skills/using-projects-meta/SKILL.md` — add **Step 0 — Freshness gate** section before "Read (no confirmation needed)" and before "Mutate (always two-step)"; tweak Workflow ordering
- `skills/using-projects-meta/README.md` — mirror the rule in a new "Freshness gate" subsection of "Two operation classes"
- `.wiki/concepts/projects-meta-skills.md` — append a "Freshness gate (v1.1.0)" subsection
- `.wiki/log.md` — one entry
## Decisions log
- 2026-04-30: Threshold = 10 min cache age. Reason: balance between catching cross-machine pushes and not hitting Gitea on every casual `tasks_aggregate`. User-supplied number, codified.
- 2026-04-30: For shared-wiki writes, pull is **unconditional** (not gated by cache age). Reason: sha-based optimistic lock in `knowledge_ingest` will reject the commit otherwise with a 422, and the failure mode is opaque ("why did it fail?"). The pull is cheap (fast-forward is a no-op when already current).
- 2026-04-30: For tasks-mutations, sync via `dist/sync.js` is enough. Reason: there's no local clone of the target tasks repo — mutations go straight through Gitea API. The sync only refreshes the local view of all repos' STATUS.md so the agent reasons from current state when writing.
- 2026-04-30: 401/403 from sync → send user to rotate `gitea_token` in `~/.config/projects-mcp/auth.toml`. Reason: silently failing or pretending sync succeeded leaves the agent reasoning on stale data. Loud failure is the whole point of the check.
- 2026-04-30: Version bump 1.0.0 → 1.1.0 (additive behavior change — adds a pre-flight step, doesn't break existing usage). `using-context7` precedent: `version` in frontmatter, semver discipline.
## Open questions
- [ ] Should Step 0 apply to `meta_status` itself? Decision: no — that would be circular. `meta_status` is the freshness probe, not a read that depends on freshness.
## Completed steps
- [x] Open task in STATUS.md as 🔴 active
- [x] Add Step 0 — Freshness gate section to `using-projects-meta/SKILL.md` (between Workflow header and Read sub-section); add Step 0 reference to Read and Mutate sub-flows; extend Common mistakes (3 new rows: stale aggregate, skipped pull, sync 401/403); extend Red flags (2 new bullets)
- [x] Mirror rule in `using-projects-meta/README.md` as new "Step 0 — Freshness gate (v1.1.0, mandatory pre-flight)" section
- [x] Bump `version: 1.0.0``1.1.0` in SKILL.md frontmatter
- [x] First rebuild + reinstall — caused regression: full Step 0 inlined into description took it to ~1450 chars, exceeded harness limit, description silently dropped → listing showed `using-projects-meta: Using the projects-meta MCP server` (H1 fallback); fixed by trimming description to 924 chars with one-sentence pointer "v1.1.0 mandates a Step 0 freshness gate ... see SKILL body"
- [x] Saved feedback memory `skill_description_length_limit.md` so future me checks length after frontmatter edits
- [x] Wiki: appended "Freshness gate (v1.1.0)" subsection to `concepts/projects-meta-skills.md`; one entry in `log.md`
- [x] Closed in STATUS.md (🟢 done); next: commit + push + MCP round-trip close
## Notes
- User-supplied draft text for the skill (verbatim): _"Step 0 — Freshness gate. Always call mcp__projects-meta__meta_status first. If cache_age_minutes > 10 or errors.length > 0, run node ~/.local/projects-meta-mcp/dist/sync.js before any read. For shared-wiki writes (knowledge_ingest, knowledge_promote), additionally run git -C ~/projects/projects-wiki pull --ff-only — sha-based optimistic lock will reject your commit otherwise."_
- Trigger reason from user: "projects-meta — это шина между машинами. Юзер на ноуте может не знать, что на десктопе уже был коммит в shared wiki 5 минут назад. Без этого шага write-флоу ломается случайным образом, и баг трудно воспроизвести."

View File

@@ -67,6 +67,20 @@ Both `setup-projects-meta` and `using-projects-meta` now write the canon path. `
This is a meta-lesson: **always pull shared/multi-machine resources before relying on cached anchors**. Saved as a feedback memory.
## Freshness gate (v1.1.0)
`using-projects-meta` v1.1.0 adds a mandatory pre-flight **Step 0** before any `tasks_*` / `knowledge_*` call:
1. Probe `mcp__projects-meta__meta_status` (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`) → **unconditional** `git -C ~/projects/projects-wiki pull --ff-only` (sha-based optimistic lock will reject otherwise with 422).
4. For tasks-mutations (`tasks_create/update/close`) → sync via `dist/sync.js` is enough; no local clone of target tasks repo.
5. 401/403 from sync → token dead → send user to `~/.config/projects-mcp/auth.toml`. Loud failure mandatory.
Why: `projects-meta` is a bus between machines. Another host can push minutes ago without the agent knowing. Without Step 0, reads return stale data and writes hit opaque 422s — exactly the failure mode we hit during this very session (skipped initial pull → wiki anchors stale → had to rewrite three places after retroactive `git pull`). Codified the lesson into the skill so future sessions don't repeat it.
10-minute threshold is the chosen balance: catches multi-machine drift, doesn't burn Gitea round-trips for casual back-to-back questions.
## Round-trip note
The task `[projects-meta-skills]` was created on a different machine (`OpeItcLoc03@DESKTOP-NSEF0UK`) via `mcp__projects-meta__tasks_create target_project=claude-skills`. Closing it on this machine via `mcp__projects-meta__tasks_close target_project=claude-skills slug=projects-meta-skills` round-trips back to the same Gitea board — both sides see the 🟢 done state. First end-to-end multi-machine task lifecycle in the fleet.

View File

@@ -30,5 +30,6 @@ Parseable: `grep "^## \[" .wiki/log.md | tail -20`.
## [2026-04-28] decision | install-portability — `install.sh`/`build.sh` patched to drop `mapfile`+`find -printf`; stock macOS (bash 3.2 + BSD find) now works; verified on git-bash (16 skills discovered, sorted, installed; build.sh produces archive)
## [2026-04-29] decision | projects-meta-skills — built `setup-projects-meta` (8-phase install of projects-meta-mcp + auth.toml + MCP registration) and `using-projects-meta` (runtime policy with local-first rule and two-step mutation); skill pair pattern applied for the 4th time (context7 / wiki / tasks / projects-meta); both built + installed; visible to the harness
## [2026-04-30] refactor | projects-meta-skills — wiki path canon corrected: `~/projects/.wiki` → `~/projects/projects-wiki/` (clone root), content at `~/projects/projects-wiki/.wiki/`. Old path caused write/read mismatch bug (fixed upstream in commit `621a69f` of `projects-meta-mcp`). Setup-projects-meta Phase 1 now detects legacy clone, Phase 4 re-clones to canon. Lesson: pull shared resources before relying on cached anchors
## [2026-04-30] decision | using-projects-meta v1.1.0 — added mandatory Step 0 freshness gate: probe `meta_status`; if cache_age > 10min or errors > 0, `node dist/sync.js`; for shared-wiki writes unconditional `git -C ~/projects/projects-wiki pull --ff-only`; 401/403 → loud failure to user. Codifies the same-session lesson — `projects-meta` is a multi-machine bus, stale cache breaks read accuracy and write atomicity
## [2026-04-28] decision | project-bootstrap@1.1.0 — added Step 5.6: detects `superpowers@claude-plugins-official` via `~/.claude/plugins/installed_plugins.json` and prints install command + upstream link if missing; chat-only, never auto-installs (slash commands aren't callable from a skill, and silent plugin install is overreach)
## [2026-04-28] doc | README.md + README.ru.md — new "Using skills in projects" / "Использование в проектах" section after install quick-start; describes project-bootstrap workflow (git, .gitignore, README, .wiki/, .tasks/, CLAUDE.md, manifest, superpowers-plugin check) and the init/upgrade modes

Binary file not shown.

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.