27 KiB
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
--checkmode if required fields are missing, or non-real implementation entries are notincomplete/blocked.
- Parses
- 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");
}
}
}
/**
* 验证报告不会把错误响应字段写成 message,P1R 必须坚持 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.