# Sync

> Unified cross-language consistency verification and documentation alignment. Phase A: verifies feature specs and protocol spec match all language implementations (classes, functions, parameters, return types, trait/interface satisfaction, multi-constructor patterns, and optional algorithm-skeleton checkpoints) via itemized checklist comparison. Phase B: verifies all documentation (PRD, SRS, Tech Design, Test Plan, Feature Specs, PROTOCOL_SPEC, README, examples, tests) is internally consistent and free of contradictions. Includes cross-language example scenario coverage and test scenario coverage comparison. Optionally hands off behavioral equivalence to the `tester` skill. Covers both apcore core SDKs and apcore-mcp bridges.

- Skill: `aiperceivable/sync` (Agent Skill, multi-file: 8 files)
- Install (CLI): `npx skillmds@latest add aiperceivable/sync`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aiperceivable/sync/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: aiperceivable (https://skillmd.com/u/aiperceivable)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aiperceivable/sync

---


# Apcore Skills — Sync

## ⚡ Execution Entry Point (READ THIS FIRST)

**When this skill is loaded, you MUST immediately begin executing the Workflow below — do not wait, do not summarize, do not ask "what should I do now". Skills are operational manuals, not reference documents.** Read the first executable step (argument parsing / ecosystem discovery), then continue through Phase A and Phase B in order, until the workflow completes or you reach an `AskUserQuestion` checkpoint. Phase A MUST complete before Phase B begins.

If the harness shows you `Successfully loaded skill · N tools allowed`, that message means **the SKILL.md content was injected into your context** — it does NOT mean the skill has run. Skills do not "run" autonomously; you run them by executing the Detailed Steps below.

If you find yourself about to say "the skill didn't produce output", "skill 仍未输出", "falling back to manual sync", "回退到手动 sync", or anything similar, **STOP**. You have misunderstood how skills work. Go directly to the first executable step and start.

The first user-visible action of this skill should be either (a) the output of the first step / Phase A startup, or (b) an `AskUserQuestion` if scope detection needs disambiguation. Never an apology, never a fallback, never silence.

---

Unified consistency verification across all apcore ecosystem documentation and implementations.

## Iron Law

**DOCUMENTATION REPOS ARE THE SINGLE SOURCE OF TRUTH. Phase A verifies code matches specs. Phase B verifies docs are internally consistent. Both phases run in order; every checklist item is evaluated. A checklist item passing cleanly, with evidence of what was checked, IS a valid result — the goal is correct findings, not findings-per-item.**

## Anti-Rationalization Table

| Thought | Reality |
|---------|---------|
| "Python is the reference, just copy it" | The documentation repo is the authority. Python may have diverged too. |
| "This naming difference is just language convention" | Convention differences (snake_case vs camelCase) are expected. Semantic differences (get_module vs findModule) are bugs. |
| "Extra methods in one SDK are fine" | Language-specific additions are OK only if documented. Undocumented extras indicate drift. |
| "I'll just compare export counts" | Count matching is necessary but not sufficient. Signature-level comparison is required. |
| "Docs look close enough" | If the code says `get_module()` but the README says `find_module()`, that is a bug. Every symbol must match exactly. |
| "I can check docs without verifying code first" | Phase B depends on Phase A. If Phase A has not established ground truth, docs verification is comparing against potentially wrong code. |
| "Checking a few symbols is representative" | Build the complete checklist. Compare every item. Partial checks create false confidence. |
| "CHANGELOG has the wrong API name" | CHANGELOG is a release artifact, not documentation. Leave it to the release skill. |
| "Doc examples are just illustrative" | If a code example calls a non-existent method or passes the wrong number of args, a user copying it will get a compile/runtime error. Doc examples ARE the onboarding API — treat them as code. |
| "Deprecated APIs in docs are just stale" | If CHANGELOG says an API was removed in v0.18.0 but docs still reference it, users following the docs will hit errors on the current version. Cross-check CHANGELOG Removed sections against doc examples. |
| "PRD is product-level, no need to check against code" | If the PRD says a feature exists but no implementation matches, that is a gap. Every layer must agree. |
| "Internal helpers should also be 1-to-1 across languages" | NO. Function-level identity (helper names, decomposition, line count) conflicts with each language's design (Rust ownership splits, Go's no-default-args, Python list comprehensions). **BUT** intent / logic / purpose MUST be identical across languages — that is enforced by the CONTRACT tier (default ON, Step 4B), the SKELETON tier (opt-in, Step 4A — algorithm checkpoint sequence), and the BEHAVIOR tier (opt-in, Step 7.5 — runtime equivalence via `tester`). Helper-name parity is never enforced at any tier. |
| "Same public signature means same intent" | NO. Two `register(id, module)` methods can share the same signature yet diverge in logic — one validates before mutating, another writes first and rolls back on error; one raises on duplicate, another silently overwrites; one is thread-safe, another races. These are intent-level bugs. The CONTRACT tier compares inputs validation rules, errors raised, side-effect order, return shape, and behavioral properties (async/thread-safe/pure/idempotent/reentrant) against the spec's `## Contract:` block — or cross-repo when spec is silent. |
| "Trait satisfaction is a Rust thing, skip it for other languages" | Every language has an equivalent: Python `__str__` / TS `toString()` / Go `String()` / Rust `impl Display`. The protocol spec defines required interface contracts; each language must satisfy them with its idiomatic mechanism. Build a dedicated checklist row. |
| "Multiple constructors are language-specific sugar" | Rust's `Self::new()` / `Self::with_config()` / `Self::from_env()` corresponds to Python `classmethod` factories, TS static factories, Go `NewX` / `NewXFromY`. If the spec defines multiple construction paths, every language must expose all of them. Treat constructors as a list, not a single entry. |
| "Contract extraction (4B) already catches intent divergence, deep-chain is redundant" | NO. 4B's sub-agent is **one-per-repo doing shape extraction** — it lists `inputs/errors/side_effects` as declared fields. It cannot see bugs that only appear when you read the code: bare dict subscripts that throw `KeyError` on malformed input, internal methods that silently skip validation, functions that fail to update a map the peer language updates. These are visible in the AST, not in the contract shape. Step 4C reads all N languages' source for one module **side-by-side** and diffs the call graphs — that is how the `_discover_custom` / `discover_internal` / `for...of null` class of bugs get caught. |
| "A sub-agent that reports 'no issues' means the module is lazy" | Zero findings IS a valid outcome — when backed by evidence. Sub-agents must cite `file:line:snippet` for every claim, including negative claims (e.g., "checked the validation path — `registry.py:L45-L52` performs the same guard as peers, no divergence"). The orchestrator rejects reports without evidence citations, NOT reports with zero findings. Do not fabricate low-severity findings to avoid an empty report. When evidence is genuinely ambiguous, emit `inconclusive` with a reason, not a made-up finding. |
| "I found 0 issues in this dimension — I should report *something* to avoid looking lazy" | No. Quota-filling is the primary source of false positives in this skill. A dimension returning `FINDING_COUNT: 0` with a short "what I checked" note is a cleaner signal than a padded one. If unsure, use `inconclusive` — never invent. |
| "The input could *theoretically* be malformed, so this is a security bug" | If the input source is internal/trusted (project's own files, hard-coded constants, type-checked internal calls, dev-local scanner output), this is not a security finding. Trust-boundary test: is the input source genuinely external (network, untrusted user, cross-trust-boundary file upload)? If not, drop or downgrade to warning. Speculative attacker scenarios on internal data flow are noise. |
| "This could raise if someone passes a weird type" | Speculative failures on internal call sites are not bugs. Only flag when (a) a real call site exists that actually sources the weird type, or (b) it's a public API boundary where external callers exist. Justifications starting with "if X ever happens" / "could theoretically" / "in case someone..." do not qualify — those are speculation, not evidence. |
| "Two SDKs differ on a defensive check — the stricter one is right, flag the looser one as CRITICAL" | Defensive-code divergence maxes out at WARNING unless the missing check causes observable divergence in the `## Contract:` block (different error raised, different side-effect order, different return shape). Same observable behavior + different defensive style = warning, or drop. Design-preference disagreements never justify a `critical`. |

## When to Use

- After adding features to one SDK — verify all SDKs and their docs match
- Periodic consistency check across all language implementations
- Before a release to ensure all SDKs expose the same API surface and docs are accurate
- After API changes to sync usage examples and documentation across repos
- When a new SDK is nearing feature parity with existing ones
- After updating PRD/SRS/Tech Design — verify downstream docs and code still match

## Command Format

```
/apcore-skills:sync [repo1,repo2,...] [--phase a|b|all] [--fix] [--scope core|mcp|all] [--lang python,typescript,...] [--internal-check none|contract|skeleton|behavior] [--deep-chain on|off] [--strict] [--no-cache] [--save]
```

| Argument / Flag | Default | Description |
|------|---------|-------------|
| positional repos | — | Comma-separated repo names to sync. See **Positional Repo Arguments** below. |
| `--phase` | `all` | Which phase to run: `a` (spec vs implementation), `b` (documentation internal consistency), `all` (A then B) |
| `--fix` | off | Auto-fix issues (naming, stubs, doc references) |
| `--scope` | **cwd** | Which group: `core`, `mcp`, `all`. **If omitted and no positional repos, defaults to the current working directory's repo only.** Use `--scope all` to scan all repos. |
| `--lang` | all discovered | Comma-separated list of languages to compare |
| `--internal-check` | `contract` | Internal consistency tier. `none` = public API only. `contract` = **DEFAULT** — also compare behavioral contracts (inputs validation, errors raised, side-effect order, return shape, properties) via Step 4B, static. `skeleton` = contract + algorithm checkpoint sequences (Step 4A, static, requires source instrumentation). `behavior` = all static tiers + hand off to `tester` skill for runtime behavioral equivalence (Step 7.5, dynamic). Higher tiers include lower tiers. Function-level (helper) identity is intentionally NOT supported — see Anti-Rationalization Table. |
| `--deep-chain` | `on` | Cross-language deep-chain analysis (Step 4C). When on, the orchestrator spawns one sub-agent **per logical module** and feeds it all N languages' source side-by-side. The sub-agent diffs call graphs, finds missing-validation / missing-registration / defensive-gap divergences that shape-level extraction (4B) cannot see. Forced off when `--internal-check=none`. Set `--deep-chain off` for fast sync (reduces sub-agent count, loses intent-level chain coverage). |
| `--strict` | off (lean mode) | Re-enable noise-prone finding classes that are suppressed by default. By default sync suppresses language-idiom downgrades (`defensive-depth`, `error-class-name-only`, `async-no-work`, `constructor-name-idiom`, `type-wrapping`), style-only naming nits (lint-suppression-style suggestions), and findings tagged `[verify-spec-first]` (where the recommendation depends on which spec version is authoritative). Pass `--strict` before a release sync when you want the full surface. **Real bugs are never suppressed** — `critical`/`blocker`, spec violations, missing API, and chain-level structural divergences (missing-validation / missing-registration / semantic-divergence) always surface regardless. Identical semantics to `apcore-skills:audit --strict` — see `shared/strict-suppression.md`. |
| `--no-cache` | off (cache on) | Bypass the Step 2 / Step 4C extraction cache (`shared/ecosystem.md` §0.6b) and force every repo/module to re-run its sub-agent even if the cache would have hit. **This trades cost for nothing extra in coverage** — a cache hit is byte-identical to a fresh run for unchanged input, so `--no-cache` does not find anything a cached run would have missed. Use it only to recover from a suspected bad cache entry, or right after editing `references/extract-api-prompt.md` / `references/deep-chain-prompt.md` without bumping their `--extra` version tag. |
| `--save` | off | Save report to file |

### Lean vs Strict — when to use which

- **Lean (default)** — answers "what should I actually fix?". Use during iterative cleanup. Lean mode is designed to terminate at zero warnings once real bugs are fixed.
- **Strict (`--strict`)** — answers "what is every divergence anyone could possibly notice?". Use before a major release or when chasing cross-language API/contract symmetry. Strict mode does NOT necessarily reach zero — some findings stay open as accepted policy.

### Internal Consistency Tiers

Each tier is **cumulative** — higher tiers include all lower tiers.

| Tier | What is checked | How | Cost |
|------|----------------|-----|------|
| `none` | Public API surface only (Step 4) | Static signature comparison | Low |
| `contract` (**default**) | Public API + **behavioral contract** per method: inputs validation, errors raised, side-effect order, return shape, properties (async, thread_safe, pure, idempotent, reentrant) | Static — sub-agents extract contract per `shared/api-extraction-protocol.md` E.4b; main context compares each row against spec `## Contract:` block (if present) and cross-repo (always) | Low — no new runtime cost |
| `skeleton` | Contract tier + algorithm checkpoint sequence inside each public method | Static — grep `checkpoint:NAME` literal strings in source, compare ordered set against spec's `## Algorithm` section | Low — requires source instrumentation |
| `behavior` | All static tiers + runtime behavioral equivalence — same input → same observable output across all SDKs | Dynamic — invokes `/apcore-skills:tester --mode run --category protocol` (Step 7.5) and merges results | High — runs tests |

**Contract tier is the default** because it answers the question "do all SDKs agree on what the method DOES?" without requiring any source instrumentation or test execution. It captures intent (logic/purpose) divergence that pure signature comparison misses. See `shared/contract-spec.md` for the `## Contract:` block format.

**Deep-chain analysis (Step 4C, `--deep-chain on` by default) runs alongside every non-`none` tier.** It is **not** a `--internal-check` tier because it operates on a different axis: instead of comparing extracted **shape** (as contract/skeleton/behavior do), it compares actual **call graphs across languages**. A sub-agent reads all N languages' source for one module side-by-side and diffs the code directly. This catches bugs that shape extraction is structurally blind to (e.g., `for (const entry of customModules)` crashing on `null` when peer languages don't; internal methods skipping validation; maps missing an insert). See Step 4C.

**Function-level identity (helper names / count / decomposition) is explicitly NOT a tier.** It conflicts with each language's design philosophy (Rust ownership splits, Go's no-default-args, Python list comprehensions) and produces noise rather than signal.

### Positional Repo Arguments

Positional repo names control **exactly** which repos are included. They take priority over `--scope` and CWD-based defaults.

**Multiple repos (explicit set):**
```
/apcore-skills:sync apcore,apcore-typescript,apcore-python
```
Syncs exactly these 3 repos — no expansion, no auto-discovery. The doc repo and impl repos are determined from the provided list:
- If a `protocol` repo (e.g., `apcore`) is in the list → it becomes `doc_repo` for core scope
- If a `docs-site` repo (e.g., `apcore-mcp`) is in the list → it becomes `doc_repo` for mcp scope
- If no doc repo is in the list → auto-include the relevant doc repo based on the impl repos' scope group (a `core-sdk` repo implies `apcore/` as doc repo; a `mcp-bridge` repo implies `apcore-mcp/` as doc repo)
- All remaining repos become `impl_repos`

**Single repo name — smart expansion:**
```
/apcore-skills:sync apcore
```
A single repo name triggers **smart expansion** based on the repo's type:
- `protocol` repo (`apcore`) → expand to `apcore` + **all discovered `core-sdk` repos** (`apcore-{lang}`). Does NOT include `mcp-bridge` repos.
- `docs-site` repo (`apcore-mcp`) → expand to `apcore-mcp` + **all discovered `mcp-bridge` repos** (`apcore-mcp-{lang}`)
- `core-sdk` repo (`apcore-python`) → expand to this repo + its `doc_repo` (`apcore`). Only this single impl repo, not all core SDKs.
- `mcp-bridge` repo (`apcore-mcp-python`) → expand to this repo + its `doc_repo` (`apcore-mcp`). Only this single impl repo.
- `integration` repo → Phase A N/A, Phase B only on this repo
- `shared-lib` or `tooling` repo → Phase B only on this repo

Display: `"Scope: {repo-names} (from positional args)"`

**No positional repos:** Falls through to `--scope` or CWD-based default (see Step 1.1).

## Documentation and Implementation Repo Mapping

Each `--scope` group has one **documentation repo** (single source of truth) and one or more **implementation repos** (language-specific code):

| Scope | Documentation Repo | Contents | Implementation Repos |
|-------|-------------------|----------|---------------------|
| `core` | `apcore/` | PROTOCOL_SPEC.md, PRD, SRS, Tech Design, Test Plan, Feature Specs | `apcore-python/`, `apcore-typescript/`, ... |
| `mcp` | `apcore-mcp/` | PRD, SRS, Tech Design, Test Plan, Feature Specs | `apcore-mcp-python/`, `apcore-mcp-typescript/`, ... |

Implementation repos contain only code and a README. They do NOT contain PRD/SRS/Tech Design/Test Plan/Feature Specs — those live exclusively in the documentation repo.

## Context Management

**All per-repo AND per-module operations use parallel sub-agents.** The main context ONLY handles:
1. Orchestration — determining scope, phase, enumerating modules, tracking per-module progress, spawning sub-agents
2. Spec reference — reading the documentation repo (lightweight, structured docs)
3. Comparison logic — building and evaluating the checklist from structured summaries
4. Phase sequencing — Phase A must complete before Phase B begins
5. Reporting — formatting combined results

Parallelism fan-out:
- **Step 2** — one sub-agent per **cache-miss** implementation repo (per-repo, simultaneous) for public API extraction. A repo whose relevant source is unchanged since the last run is served from the extraction cache instead — see 2.0.
- **Step 4C** — one sub-agent per **cache-miss logical module** (cross-language — each sub-agent reads all N languages' source for its module), dispatched in batches by the orchestrator with bounded concurrency. Progress table is maintained in main context. A module whose source + spec Contract are unchanged since the last run is served from the extraction cache instead — see 4C.2.0.
- **Step 6** — one sub-agent per documentation repo + one per implementation repo (simultaneous) for documentation auditing
- **Step 10** — one sub-agent per repo with fixable findings (simultaneous)

**Extraction cache (Step 2, Step 4C).** On a repeat sync where most repos/modules haven't changed since the last run, this is the dominant lever for reducing sub-agent count — a re-run of an ecosystem-scale sync (many repos × many modules) can skip the large majority of Step 2/4C sub-agent calls entirely while producing byte-identical results for the unchanged portions. It is lossless (content-hash keyed, `shared/ecosystem.md` §0.6b) and ON by default; pass `--no-cache` only to force a full re-run. This does not reduce what Step 4/4A/4B/6 evaluate — every symbol and module still gets a checklist row and a finding decision, whether the underlying extraction came from a fresh sub-agent or a cache hit.

**Orchestrator progress tracking (Step 4C).** The main context keeps a table `module_progress[module] = {status: pending|in_progress|complete|failed|inconclusive, findings_count, inconclusive_count, assigned_sub_agent_id}`. As each sub-agent returns, the orchestrator updates the row and prints one line: `[4C] {module}: {N} findings ({critical}/{warning}/{info}/{inconclusive})`. If any module comes back `failed` or entirely `inconclusive`, the orchestrator emits a visible warning — a module that cannot be analyzed is itself a risk indicator, not a quiet success.

## Workflow

```
Step 0 (ecosystem) → Step 1 (parse args) → PHASE A [Steps 2-5, including 4A/4B/4C] → PHASE B [Steps 6-8] → Step 9 (combined report + review-compatible output) → [Step 10 (fix)]
```

---

## Detailed Steps

### Step 0: Ecosystem Discovery

@../shared/ecosystem.md

Filter repos based on `--scope` and `--lang` flags. Identify documentation repos and implementation repos per scope group.

---

### Step 1: Parse Arguments and Determine Scope

Parse `$ARGUMENTS` for all flags and positional repo names. Determine:
- Active phases (a, b, or both)
- Scope groups and language filter
- Fix mode
- Target repos (from positional args, `--scope`, or CWD)
- **`STRICT_MODE`** — set to `true` if `--strict` appears in `$ARGUMENTS`, else `false`. Pass to the Step 9.0.3 suppression pass and surface in the combined-report header.
- **`NO_CACHE`** — set to `true` if `--no-cache` appears in `$ARGUMENTS`, else `false`. When `true`, Step 2.0 and Step 4C.2.0 skip the cache `check` call entirely and treat every repo/module as a miss (still writing fresh results back via `put`, so the cache is warm again for the next run).

**Resolution priority:** Positional repo args > `--scope` flag > CWD-based default.

#### 1.0 Positional Repo Arguments

**If positional repo names are provided** (comma-separated, non-flag tokens in `$ARGUMENTS`):

1. Split by comma to get the repo name list
2. Validate each name against `repos[]` from ecosystem discovery. If a name is not found, report error: `"Repo '{name}' not found in ecosystem. Available: {repo names}"`
3. Apply the expansion rules from **Positional Repo Arguments** section above:
   - **Multiple repos** → use exactly as provided, auto-include doc repos if missing
   - **Single repo** → apply smart expansion based on repo type
4. Skip `--scope` and CWD-based default logic entirely
5. Display: `"Scope: {repo-names} (from positional args). {N} doc repos, {N} impl repos."`

#### 1.1 CWD-based Default Scope

**Only applies when NO positional repos are provided.**

**If `--scope` is NOT specified:**
1. Detect the current working directory's repo name (basename of CWD, e.g., `apcore-python`)
2. Look up this repo in the discovered ecosystem:
   - If it's a `core-sdk` repo → set scope to `core`, filter `impl_repos` to **only this repo**
   - If it's a `mcp-bridge` repo → set scope to `mcp`, filter `impl_repos` to **only this repo**
   - If it's the `protocol` repo (`apcore/`) → set scope to `core`, include **all** core impl repos (user is editing the spec, so check all implementations against it)
   - If it's a `docs-site` repo (`apcore-mcp/`) → set scope to `mcp`, include **all** mcp impl repos
   - If it's an `integration` repo → Phase A is N/A (integrations don't have a protocol spec to compare against), run Phase B only on this repo
   - If it's a `shared-lib` or `tooling` repo → run Phase B only on this repo (no spec to compare against)
   - If CWD is not inside any discovered repo → use `AskUserQuestion` to ask: "CWD is not an apcore repo. Which repo do you want to sync?" with options from `repos[]` names + "All repos (full ecosystem scan)"
3. Display: "Scope: {repo-name} (from CWD). Use --scope all for full ecosystem scan."

**If `--scope` IS specified:** use the explicit scope as before.

#### 1.2 Resolve Repos

For each scope group, resolve:
- `doc_repo` — the documentation repo path (e.g., `apcore/` for core, `apcore-mcp/` for mcp)
- `impl_repos[]` — the implementation repo paths (may be a single repo if CWD-scoped)

**Single-SDK handling:** If a scope group contains fewer than 2 implementation repos:
- Skip cross-implementation comparison for that group
- Display: "Only 1 {scope} implementation found ({repo-name}). Cross-language comparison requires at least 2 implementations."
- Still run spec compliance check (Phase A) and documentation consistency check (Phase B) as single-repo validation

Display:
```
Sync scope: {scope} {("(from CWD)" if defaulted)}
Languages: {lang1}, {lang2}, ...
Doc repos: {doc_repo1}, {doc_repo2}
Impl repos: {impl_repo1}, {impl_repo2}, ...
Phases: {A only | B only | A then B}
Mode: {report only | auto-fix}
```

---

## PHASE A: Spec ↔ Implementation Consistency

Verify that the documentation repo's feature specs and protocol spec match what each language implementation actually exports. Build an explicit checklist, compare every item.

### Step 2: Extract Public APIs (Parallel Sub-agents — One per Implementation Repo)

**2.0 Extraction cache (lossless — skip unless `--no-cache`).** Per `shared/ecosystem.md` §0.6b, before spawning any sub-agent, check the extraction cache for each implementation repo in a single fast pass:

```
python3 <scripts_dir>/extract_cache.py check --cache-dir {ecosystem_root}/.apcore-skills-cache/sync \
    --kind api --key {repo_name} --repo-dir {repo_path} --lang {language} \
    --extra "sync-extract-v1"
```

**Before the first `check`/`put` call of the run:** if `{ecosystem_root}` is (or is inside) a git repo and its `.gitignore` does not already contain `.apcore-skills-cache/` (or a pattern that covers it, e.g. `.apcore-skills-cache`), append that line. Do this once per run, not once per repo — it is the actual point the cache directory gets created, so this is where the ecosystem.md §0.6b guidance must be executed, not just documented.

**If `python3` is unavailable, or `extract_cache.py` errors:** treat every repo as a cache miss and proceed with normal 2.1 sub-agent dispatch — never block sync on the cache being unavailable (same fallback discipline as `discover.py`, `shared/ecosystem.md` §0.1).

For each repo, this returns in well under a second (it hashes local files, no LLM call):
- `{"status": "hit", "hash": ..., "data": "<cached extraction summary>"}` — treat `data` exactly as if a fresh sub-agent had just returned it. Store into `api_summaries[repo_name]` and print `[Step2] {repo}: cache hit (unchanged since {cached_at}) — extraction skipped`. Do NOT spawn a sub-agent for this repo.
- `{"status": "miss", "hash": ...}` — no cached entry, or source changed since the last run. Keep `hash` for 2.2; this repo goes into the sub-agent batch below.

`--no-cache` (flag on the `/apcore-skills:sync` command) forces every repo to `miss` — use it to force a full re-extraction (e.g. after suspecting a bad cache entry, or after editing `references/extract-api-prompt.md` without bumping the `--extra` tag).

**2.1** Spawn one `Agent(subagent_type="general-purpose")` **per cache-miss implementation repo, all simultaneously in a single round of parallel Agent calls**. Each sub-agent extracts the public API from one repo independently. Do NOT process repos sequentially. If every repo was a cache hit, skip straight to 2.2's bookkeeping — zero sub-agents needed this run.

**Sub-agent prompt:** Use the template from `@references/extract-api-prompt.md`, filling in `{repo_path}` and `{package}` for each repo.

**2.2** After each sub-agent returns AND the Extraction coverage gate below has evaluated `extraction_coverage[repo_name]` for it, write the output back to the cache **only if the repo cleared the gate with no coverage WARNING** (module/re-export coverage == 100% AND source-file coverage ≥ 80% AND the `EXTRACTION_VERIFICATION` block is present):

```
python3 <scripts_dir>/extract_cache.py put --cache-dir {ecosystem_root}/.apcore-skills-cache/sync \
    --kind api --key {repo_name} --hash {hash_from_2.0} --data-file <path-to-file-holding-the-sub-agent's-raw-output>
```

If a repo's extraction triggered any coverage WARNING, do NOT `put` it — leave the cache entry as-is (miss again next run, giving the repo another independent attempt to reach full coverage rather than freezing a partial result). This mirrors 4C.2.2's "never cache a `failed` module" rule.

**Main context retains:** Each repo's structured API summary (from cache or from a fresh sub-agent — identical downstream handling either way). Store as `api_summaries[repo_name]`.

**Extraction coverage gate (MANDATORY — before Step 3).** Each repo's summary — whether from a fresh sub-agent or a 2.0 cache hit (the cached `data` is the prior sub-agent's verbatim output, coverage block included) — carries an `EXTRACTION_VERIFICATION` block (Step E.5). Read it; do not skip straight to comparison. Every later phase compares against whatever surface Step 2 produced, so a partial extraction makes the entire Phase A result wrong in a way no downstream check can detect — the symbols simply are not there to be missing.

For each repo, store `extraction_coverage[repo_name]` and act on it:

| Signal | Action |
|---|---|
| Module tree or re-export coverage < 100% | Emit WARNING `[A-EXT-{seq}] extraction incomplete for {repo} — {N} of {M} modules scanned; Phase A findings for this repo may be missing symbols`. Continue. |
| Source-file coverage < 80% | Same WARNING form, citing the file percentage. Continue. |
| `EXTRACTION_VERIFICATION` block absent entirely | Emit WARNING `[A-EXT-{seq}] sub-agent for {repo} returned no extraction verification — coverage unknown, treat this repo's Phase A results as unverified`. Do NOT silently accept. |

These warnings carry into the Phase A report (Step 5) and the combined report (Step 9) under the `A-` namespace. A repo whose extraction was incomplete must never be reported as "0 findings" without the accompanying coverage warning — a clean result on a partial surface is the failure mode this gate exists to catch. Any repo triggering a row in this table is, per 2.2, **not** written to the cache — it gets a fresh, independent extraction attempt on the next sync run instead of being frozen below the coverage bar indefinitely.

---

### Step 3: Load Documentation Repo Reference

For each documentation repo in scope, read the authoritative specs:

**For `apcore/` (core scope):**
1. Read `{doc_repo_path}/PROTOCOL_SPEC.md` — extract the API contract sections
2. Scan `{doc_repo_path}/docs/features/*.md` — extract per-feature API definitions (classes, functions, parameters, return types, **trait/interface contracts**, **multi-constructor patterns**)
3. If `{doc_repo_path}/docs/tech-design.md` (or `docs/tech-design/*.md`) exists — extract any internal interface contracts marked as normative. Tag them with `internal_contract: true` so Step 4A knows they apply to internal symbols, not just public API.
4. From each feature spec, parse any `## Algorithm` section — extract the ordered checkpoint list for each public method. Store as `spec_skeletons[scope][symbol] = [checkpoint_1, checkpoint_2, ...]`. This is the input for Step 4A.
4b. **From each feature spec, parse any `## Contract:` section** — extract the behavioral contract per `shared/contract-spec.md`. For each spec Contract block, capture `{inputs[], preconditions[], side_effects[], postconditions[], errors[], returns, properties{}}`. Store as `spec_contracts[scope][symbol] = {...}`. This is the input for Step 4B.
5. If `{doc_repo_path}/docs/spec/type-mapping.md` exists — load cross-language type mappings

**For `apcore-mcp/` (mcp scope):**
1. Scan `{doc_repo_path}/docs/features/*.md` — extract per-feature API definitions, `## Algorithm` checkpoint sections, **and `## Contract:` behavioral contract sections**
2. If `{doc_repo_path}/docs/tech-design.md` exists — extract internal interface contracts
3. If a protocol or spec file exists — extract the API contract

Store as:
- `spec_api[scope]` — canonical API surface (signatures)
- `spec_skeletons[scope][symbol]` — algorithm checkpoint sequences
- `spec_contracts[scope][symbol]` — behavioral contracts (inputs validation, errors, side effects, properties)

All three must be matched by implementations. If a public method has no `## Contract:` block in any feature spec, Step 4B still runs — it compares across implementations (cross-repo mode) and emits a `warning` finding `"no spec Contract declared for {method} — compared across repos only"` pointing to the doc repo.

**For all documentation repos:**

6. Read `{doc_repo_path}/CHANGELOG.md` — extract all symbols listed under `### Removed` or `### Deprecated` sections, grouped by version. Store as `deprecated_api[scope]` = list of `{symbol, version, section}` entries. These are used in Phase B Step 6 to flag doc examples that reference removed/deprecated APIs.

   CHANGELOG is NOT checked for its own correctness (that remains a release artifact). It is used ONLY as a **signal source** to detect stale references in documentation examples.

**Algorithm section format (convention).** Feature specs SHOULD declare algorithm skeletons in this form so that Step 4A can parse them:

```markdown
## Algorithm: Registry.register

1. validate_id_format
2. check_duplicate
3. resolve_dependencies
4. acquire_write_lock
5. insert_into_index
6. emit_registered_event
7. release_write_lock
```

If a feature spec has no `## Algorithm` section for a given method, Step 4A skips skeleton checking for that method (with INFO finding "no spec skeleton declared").

---

### Step 4: Checklist Comparison (The Core of Phase A)

@../shared/api-extraction.md

Build an explicit per-symbol checklist and evaluate every single item. No shortcuts.

Step 4 has four substeps that run in order:
- **4.1–4.3** — signature / type / naming checklist (always runs)
- **4A** — skeleton checkpoint comparison (runs only when `--internal-check` is `skeleton` or `behavior`)
- **4B** — contract parity (runs by default — `--internal-check` is `contract`, `skeleton`, or `behavior`)
- **4C** — cross-language deep-chain analysis (runs when `--deep-chain=on` AND `--internal-check != none`; default is on)

#### 4.1 Build the Master Checklist

From `spec_api` and all `api_summaries`, construct a union of all symbols. Apply canonical name normalization for matching, **differentiated by symbol kind**:

**Classes / Types / Enums / Interfaces** → normalize to `PascalCase`:
- Python, TypeScript, Go, Rust, Java, C#, Kotlin, Swift, PHP: `PascalCase` (pass through)

**Functions / Methods / Variables / Constants** → normalize to `snake_case`:
- Python: `snake_case` (pass through)
- TypeScript: `camelCase` → `snake_case`
- Go: `PascalCase` → `snake_case` (exported functions)
- Rust: `snake_case` (pass through)
- Java: `camelCase` → `snake_case`
- C#: `PascalCase` → `snake_case`
- Kotlin: `camelCase` → `snake_case`
- Swift: `camelCase` → `snake_case`
- PHP: `camelCase` → `snake_case`

For each symbol, create a checklist row covering every checkable property.

#### 4.2 Checklist Evaluation Rules

**For each CLASS:**

See **§1 Master checklist table shape** in `@references/checklist-tables.md`.

Checklist items per CLASS:
1. Class exists — present in spec? present in each implementation?
2. **Constructors (list, not single entry)** — the spec may declare multiple construction paths (Rust `Self::new` / `Self::with_config` / `Self::from_env`; Python `classmethod` factories; Go `NewX` / `NewXFromY`; TS static factories). For each spec-declared constructor:
   a. Constructor exists in each implementation under the language-idiomatic mechanism?
   b. Each param: name convention ✓, type ✓, required/optional ✓, default value ✓ (use the default-value mapping table in api-extraction.md E.4)
3. Methods — for each method:
   a. Method exists in each implementation?
   b. Name follows language convention for the canonical name?
   c. Each parameter: name ✓, type ✓, required/optional ✓, default ✓
   d. Return type matches (using type mapping table)?
   e. Async flag matches?
4. **Trait / Interface satisfaction** — for each trait/interface contract the spec declares this class must satisfy (e.g., `Display`, `Serializable`, `Clone`, `Iterator`):
   a. Each implementation must expose the equivalent contract using the language's idiomatic mechanism. Equivalence table:
      See **§2 Trait / interface equivalence table** in `@references/checklist-tables.md`.
   b. If the spec contract has no row in this table, fall back to: "implementation exposes a method whose canonical-snake-case name matches the contract's spec name"
   c. Missing equivalent → FAIL with severity `critical`

**For each FUNCTION:**
1. Function exists — present in spec? present in each implementation?
2. Name follows language convention?
3. Each parameter: name ✓, type ✓, required/optional ✓, default ✓
4. Return type matches?
5. Async flag matches?

**For each ENUM:**
1. Enum exists in each implementation?
2. Each member: name matches ✓? value matches ✓?

**For each TYPE/INTERFACE:**
1. Type exists in each implementation?
2. Each field: name matches ✓? type matches ✓? required/optional ✓?

**For each ERROR CLASS:**
1. Error class exists in each implementation?
2. Error code value matches?
3. Parent class matches?

**For each CONSTANT:**
1. Constant exists in each implementation?
2. Type matches ✓? Value matches ✓?

#### 4.3 Protocol Compliance Check

For each implementation repo, compare against the spec API:
1. **Missing from spec** — implementation has symbols not defined in spec (language-specific additions)
2. **Missing from implementation** — spec defines symbols not in implementation
3. **Divergence** — implementation doesn't match spec definition

#### 4A: Internal Skeleton Consistency (when --internal-check >= skeleton)

**Purpose:** verify that each public method's *internal algorithm* follows the same checkpoint sequence across languages, without requiring helper-function identity. This is the only static check sync performs on internal implementation.

**Skip conditions:**
- `--internal-check=none` → skip this entire substep
- `spec_skeletons[scope]` is empty (no feature spec in this scope declares any `## Algorithm` section) → skip the entire substep with a single INFO finding `"no spec skeletons defined for scope {scope} — skeleton tier is a no-op until feature specs add ## Algorithm sections"`. Do NOT emit per-method findings in this case.
- A given method has no `## Algorithm` section in its feature spec (but other methods in the same scope do) → skip just this method with INFO finding `"no spec skeleton declared for {method}"`

**Checkpoint extraction.** Each implementation must mark its algorithm steps with structured trace/log calls so they can be statically grepped. Convention:

| Language | Marker form | Example |
|---|---|---|
| Python | `logger.debug("checkpoint:NAME")` or `tracer.start_as_current_span("checkpoint:NAME")` (OpenTelemetry) | `logger.debug("checkpoint:validate_id_format")` |
| TypeScript | `logger.debug("checkpoint:NAME")` or `tracer.startSpan("checkpoint:NAME")` (OpenTelemetry) | `logger.debug("checkpoint:validate_id_format")` |
| Go | `slog.Debug("checkpoint:NAME")` or `span.AddEvent("checkpoint:NAME")` | `slog.Debug("checkpoint:validate_id_format")` |
| Rust | `tracing::debug!("checkpoint:NAME")` or `tracing::trace_span!("checkpoint:NAME")` | `tracing::debug!("checkpoint:validate_id_format")` |
| Java | `logger.debug("checkpoint:NAME")` or `Span.current().addEvent("checkpoint:NAME")` | `logger.debug("checkpoint:validate_id_format")` |

> **Note:** The example call sites above are *illustrative*. The normative extraction rule is the regex in `shared/api-extraction-protocol.md` E.4a, which matches any string literal of the form `"checkpoint:NAME"` regardless of which logger/tracer API wraps it. Any new logging or tracing library that accepts string arguments will automatically work without updating this table.

The literal prefix is `checkpoint:` followed by a snake_case identifier. Sub-agents in Step 2 grep for `checkpoint:[a-z_][a-z0-9_]*` inside each public method's source body and return them in their natural sour

…(truncated)
