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
protocolrepo (e.g.,apcore) is in the list → it becomesdoc_repofor core scope - If a
docs-siterepo (e.g.,apcore-mcp) is in the list → it becomesdoc_repofor 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-sdkrepo impliesapcore/as doc repo; amcp-bridgerepo impliesapcore-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:
protocolrepo (apcore) → expand toapcore+ all discoveredcore-sdkrepos (apcore-{lang}). Does NOT includemcp-bridgerepos.docs-siterepo (apcore-mcp) → expand toapcore-mcp+ all discoveredmcp-bridgerepos (apcore-mcp-{lang})core-sdkrepo (apcore-python) → expand to this repo + itsdoc_repo(apcore). Only this single impl repo, not all core SDKs.mcp-bridgerepo (apcore-mcp-python) → expand to this repo + itsdoc_repo(apcore-mcp). Only this single impl repo.integrationrepo → Phase A N/A, Phase B only on this reposhared-libortoolingrepo → 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:
- Orchestration — determining scope, phase, enumerating modules, tracking per-module progress, spawning sub-agents
- Spec reference — reading the documentation repo (lightweight, structured docs)
- Comparison logic — building and evaluating the checklist from structured summaries
- Phase sequencing — Phase A must complete before Phase B begins
- 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 totrueif--strictappears in$ARGUMENTS, elsefalse. Pass to the Step 9.0.3 suppression pass and surface in the combined-report header.NO_CACHE— set totrueif--no-cacheappears in$ARGUMENTS, elsefalse. Whentrue, Step 2.0 and Step 4C.2.0 skip the cachecheckcall entirely and treat every repo/module as a miss (still writing fresh results back viaput, 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):
- Split by comma to get the repo name list
- 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}" - 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
- Skip
--scopeand CWD-based default logic entirely - 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:
- Detect the current working directory's repo name (basename of CWD, e.g.,
apcore-python) - Look up this repo in the discovered ecosystem:
- If it's a
core-sdkrepo → set scope tocore, filterimpl_reposto only this repo - If it's a
mcp-bridgerepo → set scope tomcp, filterimpl_reposto only this repo - If it's the
protocolrepo (apcore/) → set scope tocore, include all core impl repos (user is editing the spec, so check all implementations against it) - If it's a
docs-siterepo (apcore-mcp/) → set scope tomcp, include all mcp impl repos - If it's an
integrationrepo → 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-libortoolingrepo → run Phase B only on this repo (no spec to compare against) - If CWD is not inside any discovered repo → use
AskUserQuestionto ask: "CWD is not an apcore repo. Which repo do you want to sync?" with options fromrepos[]names + "All repos (full ecosystem scan)"
- If it's a
- 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>"}— treatdataexactly as if a fresh sub-agent had just returned it. Store intoapi_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. Keephashfor 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):
- Read
{doc_repo_path}/PROTOCOL_SPEC.md— extract the API contract sections - Scan
{doc_repo_path}/docs/features/*.md— extract per-feature API definitions (classes, functions, parameters, return types, trait/interface contracts, multi-constructor patterns) - If
{doc_repo_path}/docs/tech-design.md(ordocs/tech-design/*.md) exists — extract any internal interface contracts marked as normative. Tag them withinternal_contract: trueso Step 4A knows they apply to internal symbols, not just public API. - From each feature spec, parse any
## Algorithmsection — extract the ordered checkpoint list for each public method. Store asspec_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 pershared/contract-spec.md. For each spec Contract block, capture{inputs[], preconditions[], side_effects[], postconditions[], errors[], returns, properties{}}. Store asspec_contracts[scope][symbol] = {...}. This is the input for Step 4B. - If
{doc_repo_path}/docs/spec/type-mapping.mdexists — load cross-language type mappings
For apcore-mcp/ (mcp scope):
- Scan
{doc_repo_path}/docs/features/*.md— extract per-feature API definitions,## Algorithmcheckpoint sections, and## Contract:behavioral contract sections - If
{doc_repo_path}/docs/tech-design.mdexists — extract internal interface contracts - 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 sequencesspec_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:
Read
{doc_repo_path}/CHANGELOG.md— extract all symbols listed under### Removedor### Deprecatedsections, grouped by version. Store asdeprecated_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:
## 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-checkisskeletonorbehavior) - 4B — contract parity (runs by default —
--internal-checkiscontract,skeleton, orbehavior) - 4C — cross-language deep-chain analysis (runs when
--deep-chain=onAND--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:
- Class exists — present in spec? present in each implementation?
- Constructors (list, not single entry) — the spec may declare multiple construction paths (Rust
Self::new/Self::with_config/Self::from_env; Pythonclassmethodfactories; GoNewX/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) - 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?
- 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 severitycritical
For each FUNCTION:
- Function exists — present in spec? present in each implementation?
- Name follows language convention?
- Each parameter: name ✓, type ✓, required/optional ✓, default ✓
- Return type matches?
- Async flag matches?
For each ENUM:
- Enum exists in each implementation?
- Each member: name matches ✓? value matches ✓?
For each TYPE/INTERFACE:
- Type exists in each implementation?
- Each field: name matches ✓? type matches ✓? required/optional ✓?
For each ERROR CLASS:
- Error class exists in each implementation?
- Error code value matches?
- Parent class matches?
For each CONSTANT:
- Constant exists in each implementation?
- Type matches ✓? Value matches ✓?
4.3 Protocol Compliance Check
For each implementation repo, compare against the spec API:
- Missing from spec — implementation has symbols not defined in spec (language-specific additions)
- Missing from implementation — spec defines symbols not in implementation
- 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 substepspec_skeletons[scope]is empty (no feature spec in this scope declares any## Algorithmsection) → 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
## Algorithmsection 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.mdE.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)