oh-my-muse/docs/superpowers/plans/2026-05-25-P1R-0-baseline-gate.md

27 KiB
Raw Blame History

P1R-0 Baseline Gate Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Build a repeatable P1R API coverage gate that audits OpenAPI operations against current muse-cloud/ implementation and produces a machine-readable completion matrix.

Architecture: A Python audit script parses OpenAPI YAML files and scans Java source conservatively for dedicated controllers, catch-all contract handlers, generic persistence handlers, and placeholder SSE handlers. It writes JSON and Markdown reports under docs/superpowers/reports/, and a Maven test verifies the report shape and ensures non-real implementations remain incomplete or blocked.

Tech Stack: Python 3 + PyYAML, Java 21, JUnit 5, Jackson, Maven, Spring Boot 3 / Yudao Cloud.


File Structure

  • Create: muse-cloud/scripts/p1r-audit-api-coverage.py
    • Parses docs/api-contracts/*/openapi.yaml.
    • Scans Java files under muse-cloud/.
    • Writes docs/superpowers/reports/p1r-api-coverage.json.
    • Writes docs/superpowers/reports/p1r-api-coverage.md.
    • Exits non-zero in --check mode if required fields are missing, or non-real implementation entries are not incomplete / blocked.
  • Create: docs/superpowers/reports/p1r-api-coverage.json
    • Generated baseline JSON report.
  • Create: docs/superpowers/reports/p1r-api-coverage.md
    • Generated human-readable report.
  • Create: muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rApiCoverageReportTest.java
    • Maven gate that parses the JSON report and checks structure and status rules.
  • No production source file should be modified in P1R-0.

Task 1: Add The API Coverage Audit Script

Files:

  • Create: muse-cloud/scripts/p1r-audit-api-coverage.py

  • Step 1: Create the script with OpenAPI parsing and Java scanning

Use apply_patch to create muse-cloud/scripts/p1r-audit-api-coverage.py with this content:

#!/usr/bin/env python3
"""Generate the P1R API coverage matrix.

The script is intentionally conservative: if it cannot prove an operation has a
dedicated business implementation, it marks the operation as incomplete or
blocked. This prevents catch-all contract handlers from being counted as done.
"""

from __future__ import annotations

import argparse
import json
import re
import sys
from dataclasses import dataclass, asdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

try:
    import yaml
except ImportError as exc:
    raise SystemExit("PyYAML is required. Install it with: python3 -m pip install PyYAML") from exc


REPO_ROOT = Path(__file__).resolve().parents[2]
CONTRACT_ROOT = REPO_ROOT / "docs" / "api-contracts"
MUSE_CLOUD_ROOT = REPO_ROOT / "muse-cloud"
REPORT_ROOT = REPO_ROOT / "docs" / "superpowers" / "reports"
JSON_REPORT = REPORT_ROOT / "p1r-api-coverage.json"
MARKDOWN_REPORT = REPORT_ROOT / "p1r-api-coverage.md"
HTTP_METHODS = {"get", "post", "put", "patch", "delete"}


@dataclass(frozen=True)
class Operation:
    domain: str
    side: str
    method: str
    path: str
    operationId: str
    isWrite: bool
    requiresCommandId: bool
    externalDependencies: list[str]
    targetStage: str
    implementationStatus: str
    completionStatus: str
    controllerFiles: list[str]
    serviceFiles: list[str]
    notes: str


def main() -> int:
    parser = argparse.ArgumentParser(description="Generate P1R API coverage matrix")
    parser.add_argument("--check", action="store_true", help="validate generated report after writing it")
    args = parser.parse_args()

    operations = collect_operations()
    REPORT_ROOT.mkdir(parents=True, exist_ok=True)
    report = build_report(operations)
    JSON_REPORT.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    MARKDOWN_REPORT.write_text(render_markdown(report), encoding="utf-8")

    if args.check:
        validate_report(report)
    print(f"Generated {JSON_REPORT.relative_to(REPO_ROOT)}")
    print(f"Generated {MARKDOWN_REPORT.relative_to(REPO_ROOT)}")
    return 0


def collect_operations() -> list[Operation]:
    source_index = build_source_index()
    operations: list[Operation] = []
    for contract_path in sorted(CONTRACT_ROOT.glob("*/openapi.yaml")):
        domain = contract_path.parent.name
        with contract_path.open("r", encoding="utf-8") as handle:
            document = yaml.safe_load(handle)
        for path, path_item in sorted((document.get("paths") or {}).items()):
            if not isinstance(path_item, dict):
                continue
            for method, operation_doc in sorted(path_item.items()):
                if method.lower() not in HTTP_METHODS or not isinstance(operation_doc, dict):
                    continue
                operation_id = operation_doc.get("operationId")
                if not operation_id:
                    raise SystemExit(f"Missing operationId in {contract_path}:{path}:{method}")
                full_path = normalize_path(path)
                side = detect_side(full_path)
                is_write = method.upper() not in {"GET", "HEAD", "OPTIONS"}
                requires_command_id = operation_requires_command_id(operation_doc)
                implementation = classify_implementation(full_path, operation_id, domain, source_index)
                operations.append(Operation(
                    domain=domain,
                    side=side,
                    method=method.upper(),
                    path=full_path,
                    operationId=operation_id,
                    isWrite=is_write,
                    requiresCommandId=requires_command_id,
                    externalDependencies=detect_external_dependencies(domain, full_path, operation_id),
                    targetStage=target_stage(domain),
                    implementationStatus=implementation["implementationStatus"],
                    completionStatus=implementation["completionStatus"],
                    controllerFiles=implementation["controllerFiles"],
                    serviceFiles=implementation["serviceFiles"],
                    notes=implementation["notes"],
                ))
    return operations


def build_source_index() -> dict[str, Any]:
    java_files = sorted((MUSE_CLOUD_ROOT).glob("**/src/main/java/**/*.java"))
    contract_support_files: list[str] = []
    generic_persistence_files: list[str] = []
    sse_placeholder_files: list[str] = []
    dedicated_controllers: list[str] = []
    service_files: list[str] = []
    all_text: dict[str, str] = {}

    for java_file in java_files:
        text = java_file.read_text(encoding="utf-8", errors="ignore")
        rel = str(java_file.relative_to(REPO_ROOT))
        all_text[rel] = text
        if "MuseApiContractSupport.handle" in text:
            contract_support_files.append(rel)
        if "MuseContractPersistenceService" in text or "contractPersistenceService.handle" in text:
            generic_persistence_files.append(rel)
        if "SseEmitter" in text and ("muse ai task stream ready" in text or ".name(\"done\")" in text):
            sse_placeholder_files.append(rel)
        if "@RestController" in text and "MuseApiContractSupport.handle" not in text and "contractPersistenceService.handle" not in text:
            dedicated_controllers.append(rel)
        if "/application/" in rel or "/service/" in rel:
            service_files.append(rel)

    return {
        "allText": all_text,
        "contractSupportFiles": contract_support_files,
        "genericPersistenceFiles": generic_persistence_files,
        "ssePlaceholderFiles": sse_placeholder_files,
        "dedicatedControllers": dedicated_controllers,
        "serviceFiles": service_files,
    }


def classify_implementation(path: str, operation_id: str, domain: str, source_index: dict[str, Any]) -> dict[str, Any]:
    matching_dedicated = files_containing_route_or_operation(source_index["dedicatedControllers"], source_index["allText"], path, operation_id)
    matching_services = files_matching_domain_services(source_index["serviceFiles"], domain)
    matching_catch_all = files_containing_route_or_operation(source_index["contractSupportFiles"], source_index["allText"], path, operation_id)
    matching_generic = files_containing_route_or_operation(source_index["genericPersistenceFiles"], source_index["allText"], path, operation_id)
    matching_sse = files_containing_route_or_operation(source_index["ssePlaceholderFiles"], source_index["allText"], path, operation_id)

    if matching_sse:
        return status("sse_placeholder", "incomplete", matching_sse, matching_services, "SSE endpoint returns placeholder events and needs P1R real task stream implementation.")
    if matching_dedicated:
        return status("dedicated", "needs_verification", matching_dedicated, matching_services, "Dedicated entry exists; later P1R stage must verify DTO, state machine, persistence, and real external closure.")
    if matching_generic:
        return status("generic_persistence", "incomplete", matching_generic, matching_services, "Handled by MuseContractPersistenceService generic persistence, which is not P1R completion.")
    if matching_catch_all:
        return status("catch_all", "incomplete", matching_catch_all, matching_services, "Handled by MuseApiContractSupport catch-all, which is not P1R completion.")
    return status("missing", "incomplete", [], matching_services, "No backend route was identified by the conservative scanner.")


def status(implementation: str, completion: str, controllers: list[str], services: list[str], notes: str) -> dict[str, Any]:
    return {
        "implementationStatus": implementation,
        "completionStatus": completion,
        "controllerFiles": controllers,
        "serviceFiles": services,
        "notes": notes,
    }


def files_containing_route_or_operation(files: list[str], text_by_file: dict[str, str], path: str, operation_id: str) -> list[str]:
    route_fragments = route_fragments_for(path)
    matches: list[str] = []
    for file_path in files:
        text = text_by_file[file_path]
        if operation_id in text or any(fragment in text for fragment in route_fragments):
            matches.append(file_path)
    return matches


def route_fragments_for(path: str) -> list[str]:
    path_without_prefix = path
    path_without_prefix = path_without_prefix.replace("/app-api", "")
    path_without_prefix = path_without_prefix.replace("/admin-api", "")
    fragments = [path_without_prefix]
    parts = [part for part in path_without_prefix.split("/") if part]
    if len(parts) >= 2:
        fragments.append("/" + "/".join(parts[:2]))
    if len(parts) >= 3:
        fragments.append("/" + "/".join(parts[:3]))
    return sorted(set(fragments), key=len, reverse=True)


def files_matching_domain_services(files: list[str], domain: str) -> list[str]:
    domain_path = "/module/" + ("member" if domain == "account" else domain) + "/"
    return [file_path for file_path in files if domain_path in file_path]


def normalize_path(path: str) -> str:
    if path.startswith("/app-api/") or path.startswith("/admin-api/"):
        return path
    return path


def detect_side(path: str) -> str:
    if path.startswith("/admin-api/"):
        return "admin"
    if path.startswith("/app-api/"):
        return "app"
    return "unknown"


def operation_requires_command_id(operation_doc: dict[str, Any]) -> bool:
    text = json.dumps(operation_doc, ensure_ascii=False)
    return "commandId" in text or "X-Command-Id" in text


def detect_external_dependencies(domain: str, path: str, operation_id: str) -> list[str]:
    haystack = f"{domain} {path} {operation_id}".lower()
    dependencies: set[str] = set()
    if any(word in haystack for word in ["ai", "task", "suggestion", "agent", "job", "source-event"]):
        dependencies.update(["New-API", "SSE"])
    if any(word in haystack for word in ["knowledge", "kb", "document", "entity", "relation", "graph", "reindex"]):
        dependencies.add("RAGFlow")
    if any(word in haystack for word in ["import", "export", "download", "file", "document"]):
        dependencies.add("FileService")
    if any(word in haystack for word in ["marketplace", "purchase", "install", "handoff", "license"]):
        dependencies.update(["Account", "MarketAuthorization"])
    if any(word in haystack for word in ["account", "usage", "quota", "balance", "correlation", "new-api-binding"]):
        dependencies.update(["New-API", "AccountLedger"])
    return sorted(dependencies)


def target_stage(domain: str) -> str:
    return {
        "content": "P1R-1 Content Real API",
        "meta": "P1R-2 Meta Real API",
        "account": "P1R-3 Account Real API",
        "ai": "P1R-4 AI Real API",
        "knowledge": "P1R-5 Knowledge Real API",
        "market": "P1R-6 Market Real API",
        "events": "P1R-7 End-to-End Acceptance",
    }.get(domain, "P1R-0 Baseline Gate")


def build_report(operations: list[Operation]) -> dict[str, Any]:
    summary = {
        "totalOperations": len(operations),
        "completedOperations": sum(1 for operation in operations if operation.completionStatus == "completed"),
        "incompleteOperations": sum(1 for operation in operations if operation.completionStatus == "incomplete"),
        "needsVerificationOperations": sum(1 for operation in operations if operation.completionStatus == "needs_verification"),
        "catchAllOperations": sum(1 for operation in operations if operation.implementationStatus == "catch_all"),
        "genericPersistenceOperations": sum(1 for operation in operations if operation.implementationStatus == "generic_persistence"),
        "ssePlaceholderOperations": sum(1 for operation in operations if operation.implementationStatus == "sse_placeholder"),
        "missingOperations": sum(1 for operation in operations if operation.implementationStatus == "missing"),
        "blockedOperations": sum(1 for operation in operations if operation.completionStatus == "blocked"),
    }
    return {
        "generatedAt": datetime.now(timezone.utc).replace(microsecond=0).isoformat(),
        "sourceContracts": [str(path.relative_to(REPO_ROOT)) for path in sorted(CONTRACT_ROOT.glob("*/openapi.yaml"))],
        "summary": summary,
        "operations": [asdict(operation) for operation in operations],
    }


def render_markdown(report: dict[str, Any]) -> str:
    lines = [
        "# P1R API Coverage Report",
        "",
        f"- Generated at: `{report['generatedAt']}`",
        f"- Total operations: `{report['summary']['totalOperations']}`",
        f"- Needs verification: `{report['summary']['needsVerificationOperations']}`",
        f"- Incomplete: `{report['summary']['incompleteOperations']}`",
        f"- Catch-all: `{report['summary']['catchAllOperations']}`",
        f"- Generic persistence: `{report['summary']['genericPersistenceOperations']}`",
        f"- SSE placeholder: `{report['summary']['ssePlaceholderOperations']}`",
        f"- Missing: `{report['summary']['missingOperations']}`",
        "",
        "## Operations",
        "",
        "| Domain | Side | Method | Path | Operation | Implementation | Completion | Target Stage |",
        "|--------|------|--------|------|-----------|----------------|------------|--------------|",
    ]
    for operation in report["operations"]:
        lines.append(
            "| {domain} | {side} | {method} | `{path}` | `{operationId}` | {implementationStatus} | {completionStatus} | {targetStage} |".format(**operation)
        )
    lines.extend(["", "## Incomplete Or Blocked", ""])
    for operation in report["operations"]:
        if operation["completionStatus"] in {"incomplete", "blocked"}:
            lines.append(f"- `{operation['operationId']}` `{operation['method']} {operation['path']}`: {operation['implementationStatus']} - {operation['notes']}")
    lines.append("")
    return "\n".join(lines)


def validate_report(report: dict[str, Any]) -> None:
    required_fields = {
        "domain", "side", "method", "path", "operationId", "isWrite",
        "requiresCommandId", "externalDependencies", "targetStage",
        "implementationStatus", "completionStatus", "controllerFiles",
        "serviceFiles", "notes",
    }
    if report["summary"]["totalOperations"] <= 0:
        raise SystemExit("No operations found")
    for operation in report["operations"]:
        missing = required_fields - operation.keys()
        if missing:
            raise SystemExit(f"Operation {operation.get('operationId')} missing fields: {sorted(missing)}")
        if operation["implementationStatus"] in {"catch_all", "generic_persistence", "sse_placeholder", "missing"}:
            if operation["completionStatus"] not in {"incomplete", "blocked"}:
                raise SystemExit(f"{operation['operationId']} non-real implementation must be incomplete or blocked")
        if "message" in json.dumps(operation, ensure_ascii=False):
            raise SystemExit(f"{operation['operationId']} contains forbidden response evidence field: message")


if __name__ == "__main__":
    raise SystemExit(main())
  • Step 2: Make the script executable

Run:

chmod +x muse-cloud/scripts/p1r-audit-api-coverage.py

Expected: command exits 0.

  • Step 3: Run the script in check mode

Run:

python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check

Expected output includes:

Generated docs/superpowers/reports/p1r-api-coverage.json
Generated docs/superpowers/reports/p1r-api-coverage.md

If it exits with PyYAML is required, install PyYAML only after confirming the current Python environment:

python3 -m pip show PyYAML || python3 -m pip install PyYAML
  • Step 4: Inspect the generated summary

Run:

python3 - <<'PY'
import json
from pathlib import Path
report = json.loads(Path("docs/superpowers/reports/p1r-api-coverage.json").read_text())
print(json.dumps(report["summary"], indent=2, ensure_ascii=False))
PY

Expected: totalOperations is greater than 0, and completedOperations is 0 for P1R-0.

  • Step 5: Commit script and generated reports

Run:

git status --short
git add \
  muse-cloud/scripts/p1r-audit-api-coverage.py \
  docs/superpowers/reports/p1r-api-coverage.json \
  docs/superpowers/reports/p1r-api-coverage.md
git commit -m "chore(p1r): 生成 API 覆盖基线矩阵" -- \
  muse-cloud/scripts/p1r-audit-api-coverage.py \
  docs/superpowers/reports/p1r-api-coverage.json \
  docs/superpowers/reports/p1r-api-coverage.md

Expected commit includes exactly the script and two report files.

Task 2: Add Maven Gate For The Coverage Report

Files:

  • Create: muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rApiCoverageReportTest.java

  • Read: docs/superpowers/reports/p1r-api-coverage.json

  • Step 1: Add the report test

Use apply_patch to create muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rApiCoverageReportTest.java with this content:

package cn.iocoder.muse.server.framework.api;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * P1R API 覆盖矩阵门禁测试。
 *
 * <p>该测试不证明业务 API 已完成,只防止覆盖矩阵缺字段,或把 catch-all / 通用持久化入口误标为完成。</p>
 */
class P1rApiCoverageReportTest {

    /** 不能被标记为完成的实现状态。 */
    private static final Set<String> INCOMPLETE_IMPLEMENTATION_STATUSES = Set.of(
            "catch_all", "generic_persistence", "sse_placeholder", "missing");

    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 验证报告存在且至少包含一个合同 operation。
     */
    @Test
    void should_load_p1r_api_coverage_report() throws Exception {
        Path reportPath = findReportPath();
        assertThat(Files.exists(reportPath)).as("P1R API coverage report must exist").isTrue();

        JsonNode report = objectMapper.readTree(reportPath.toFile());

        assertThat(report.path("summary").path("totalOperations").asInt()).isGreaterThan(0);
        assertThat(report.path("operations").isArray()).isTrue();
        assertThat(report.path("operations").size()).isEqualTo(report.path("summary").path("totalOperations").asInt());
    }

    /**
     * 验证每个 operation 都包含后续阶段执行所需的稳定字段。
     */
    @Test
    void should_require_stable_fields_for_every_operation() throws Exception {
        JsonNode operations = objectMapper.readTree(findReportPath().toFile()).path("operations");

        for (JsonNode operation : operations) {
            assertThat(operation.path("domain").asText()).isNotBlank();
            assertThat(operation.path("side").asText()).isNotBlank();
            assertThat(operation.path("method").asText()).isNotBlank();
            assertThat(operation.path("path").asText()).startsWith("/");
            assertThat(operation.path("operationId").asText()).isNotBlank();
            assertThat(operation.has("isWrite")).isTrue();
            assertThat(operation.has("requiresCommandId")).isTrue();
            assertThat(operation.path("externalDependencies").isArray()).isTrue();
            assertThat(operation.path("targetStage").asText()).startsWith("P1R-");
            assertThat(operation.path("implementationStatus").asText()).isNotBlank();
            assertThat(operation.path("completionStatus").asText()).isNotBlank();
            assertThat(operation.path("controllerFiles").isArray()).isTrue();
            assertThat(operation.path("serviceFiles").isArray()).isTrue();
            assertThat(operation.path("notes").asText()).isNotBlank();
        }
    }

    /**
     * 验证 catch-all、通用持久化、SSE 占位和缺入口只能保持 incomplete 或 blocked。
     */
    @Test
    void should_keep_non_real_implementations_incomplete_or_blocked() throws Exception {
        JsonNode operations = objectMapper.readTree(findReportPath().toFile()).path("operations");

        for (JsonNode operation : operations) {
            String implementationStatus = operation.path("implementationStatus").asText();
            if (INCOMPLETE_IMPLEMENTATION_STATUSES.contains(implementationStatus)) {
                assertThat(operation.path("completionStatus").asText())
                        .as(operation.path("operationId").asText())
                        .isIn("incomplete", "blocked");
            }
        }
    }

    /**
     * 验证报告不会把错误响应字段写成 messageP1R 必须坚持 code/data/msg。
     */
    @Test
    void should_not_use_message_as_completion_evidence() throws Exception {
        String report = Files.readString(findReportPath());

        assertThat(report).doesNotContain("\"message\"");
    }

    /**
     * 从当前 Maven 执行目录向上查找仓库根目录,避免 surefire 在不同模块目录执行时路径失效。
     */
    private static Path findReportPath() {
        Path current = Path.of("").toAbsolutePath();
        for (Path candidate = current; candidate != null; candidate = candidate.getParent()) {
            Path reportPath = candidate.resolve("docs/superpowers/reports/p1r-api-coverage.json");
            if (Files.exists(reportPath)) {
                return reportPath;
            }
        }
        return current.resolve("docs/superpowers/reports/p1r-api-coverage.json");
    }

}
  • Step 2: Run the focused Maven test

Run:

cd muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" \
  mvn -o test -pl muse-server -am -Dtest=P1rApiCoverageReportTest -Dsurefire.failIfNoSpecifiedTests=false

Expected: BUILD SUCCESS.

-am 会让 Maven reactor 进入上游依赖模块的 test phase这些模块没有 P1rApiCoverageReportTest 时不应导致本门禁失败,因此该聚焦命令需要显式设置 -Dsurefire.failIfNoSpecifiedTests=false

If Maven cannot resolve dependencies in offline mode, rerun without -o:

cd muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" \
  mvn test -pl muse-server -am -Dtest=P1rApiCoverageReportTest -Dsurefire.failIfNoSpecifiedTests=false
  • Step 3: Run the script again after the test

Run:

python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check

Expected: script still succeeds and report files remain valid.

  • Step 4: Commit the Maven gate

Run:

git status --short
git add muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rApiCoverageReportTest.java
git commit -m "test(p1r): 增加 API 覆盖矩阵门禁" -- \
  muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rApiCoverageReportTest.java

Expected commit includes only the test file.

Task 3: Final P1R-0 Verification And Handoff

Files:

  • Read: docs/superpowers/reports/p1r-api-coverage.json

  • Read: docs/superpowers/reports/p1r-api-coverage.md

  • Read: docs/superpowers/specs/2026-05-25-P1R-0-baseline-gate-design.md

  • Read: docs/superpowers/plans/2026-05-25-P1R-0-baseline-gate.md

  • Step 1: Verify generated reports are internally consistent

Run:

python3 - <<'PY'
import json
from pathlib import Path
report = json.loads(Path("docs/superpowers/reports/p1r-api-coverage.json").read_text())
summary = report["summary"]
count = len(report["operations"])
assert summary["totalOperations"] == count, (summary["totalOperations"], count)
assert summary["completedOperations"] == 0, summary["completedOperations"]
for op in report["operations"]:
    assert op["completionStatus"] != "completed", op["operationId"]
print("P1R-0 report consistency OK:", count, "operations")
PY

Expected output:

P1R-0 report consistency OK: <N> operations
  • Step 2: Run all P1R-0 checks

Run:

python3 muse-cloud/scripts/p1r-audit-api-coverage.py --check
cd muse-cloud
JAVA_HOME=$(/usr/libexec/java_home -v 21) PATH="$JAVA_HOME/bin:$PATH" \
  mvn -o test -pl muse-server -am -Dtest=P1rApiCoverageReportTest -Dsurefire.failIfNoSpecifiedTests=false

Expected: script succeeds and Maven prints BUILD SUCCESS.

  • Step 3: Inspect git status before final summary

Run:

git status --short

Expected: no staged P1R-0 files. Unrelated files from parallel P2/P3/admin sessions may still appear; do not modify or commit them.

  • Step 4: Write handoff summary in the final response

Include:

P1R-0 completed:
- Generated API coverage matrix JSON and Markdown.
- Added Maven gate for matrix shape and non-real implementation status rules.
- Verified script and focused Maven test.

Important result:
- completedOperations remains 0 by design.
- catch-all/generic persistence/SSE placeholder/missing operations remain incomplete or blocked.
- Next stage is P1R-1 Content Real API spec + plan.
  • Step 5: Do not commit unrelated files

Before any final commit, verify:

git diff --cached --name-status

Expected: empty, unless you intentionally staged only P1R-0 files.