generated from coulomb/repo-seed
feat(classification-spine): implement STATE-WP-0065 repo-anchored model
Replace the ad-hoc coordination-domain spine with the Repo Classification Standard: 14 market domains, classification columns on managed_repos, and workplans anchored by repo_id (topic_id optional). - Add Alembic migration d8e9f0a1b2c3 with data backfill and workstream→workplan rename - Add api/classification.py validation and register-from-classification tooling - Expose workplan-first REST/MCP surface with legacy workstream aliases - Add C-24 consistency rule and legacy domain frontmatter mapping - Update dashboard repos page with category/capability/stake filters - Update orientation docs; mark STATE-WP-0065 finished
This commit is contained in:
@@ -26,6 +26,7 @@ Checks:
|
||||
C-20 workstream-dependency-missing WARN Yes Workplan dependency frontmatter missing from DB graph
|
||||
C-22 task-description-drift WARN Yes Task description/content differs between file and DB
|
||||
C-23 workstream-active-task-planning-status WARN Yes Workstream/workplan is planning while a task is progress or wait
|
||||
C-24 repo-classification-missing WARN No Registered repo lacks a valid .repo-classification.yaml on disk
|
||||
|
||||
Usage:
|
||||
python scripts/consistency_check.py --repo SLUG [--fix] [--no-writeback] [--json] [--api-base URL]
|
||||
@@ -42,7 +43,7 @@ Exit codes (--remote --all scheduled sweep):
|
||||
1 — automation error: API unreachable, repo list fetch failed, C-00 on
|
||||
any repo, or other infrastructure fault that prevented a full run
|
||||
|
||||
Assessment failures (C-01..C-23 except C-00) are repo hygiene gaps recorded
|
||||
Assessment failures (C-01..C-24 except C-00) are repo hygiene gaps recorded
|
||||
in the sweep report for later improvement. They do not fail the scheduler.
|
||||
|
||||
Agent/operator Make wrappers normalize exit code 2 to shell success while
|
||||
@@ -78,6 +79,11 @@ from api.workplan_status import ( # noqa: E402
|
||||
normalize_workstream_status as _normalize_workstream_status,
|
||||
ready_review_status,
|
||||
)
|
||||
from api.classification import ( # noqa: E402
|
||||
CLASSIFICATION_FILENAME,
|
||||
load_classification_file,
|
||||
resolve_topic_domain_slug,
|
||||
)
|
||||
from api.services.lifecycle import should_activate_parent_for_active_tasks # noqa: E402
|
||||
from api.task_status import ( # noqa: E402
|
||||
CANONICAL_TASK_STATUSES,
|
||||
@@ -713,6 +719,31 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
|
||||
|
||||
repo_dir = Path(repo_path)
|
||||
workplans_dir = repo_dir / "workplans"
|
||||
repo_market_domain = str(repo.get("domain_slug") or "").strip()
|
||||
|
||||
# C-24: repo classification file missing or invalid (always WARN — migration rows too)
|
||||
class_data, class_errors, class_warnings = load_classification_file(repo_dir)
|
||||
if class_data is None:
|
||||
classified_by = str(repo.get("classified_by") or "").strip()
|
||||
if class_errors:
|
||||
detail = "; ".join(class_errors)
|
||||
else:
|
||||
detail = f"{CLASSIFICATION_FILENAME} missing on disk"
|
||||
if classified_by == "migration":
|
||||
detail = f"{detail} (DB row is migration-derived — commit a human-reviewed file when ready)"
|
||||
report.add(
|
||||
severity="WARN",
|
||||
check_id="C-24",
|
||||
message=f"Repo classification gap: {detail}",
|
||||
fixable=False,
|
||||
)
|
||||
for warning in class_warnings:
|
||||
report.add(
|
||||
severity="WARN",
|
||||
check_id="C-24",
|
||||
message=f"Repo classification advisory: {warning}",
|
||||
fixable=False,
|
||||
)
|
||||
|
||||
# C-01: workplans/ directory missing
|
||||
if not workplans_dir.is_dir():
|
||||
@@ -804,6 +835,7 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
|
||||
"body": body,
|
||||
"repo_id": repo_id,
|
||||
"domain": file_domain,
|
||||
"repo_market_domain": repo_market_domain,
|
||||
},
|
||||
)
|
||||
continue
|
||||
@@ -1708,6 +1740,7 @@ def fix_repo(
|
||||
wp_file = Path(ctx["wp_file"])
|
||||
meta = ctx["meta"]
|
||||
domain = ctx["domain"]
|
||||
repo_market_domain = str(ctx.get("repo_market_domain") or "").strip()
|
||||
repo_id_val = ctx["repo_id"]
|
||||
body = ctx.get("body", "")
|
||||
wp_id = str(meta.get("id", "")).strip()
|
||||
@@ -1717,17 +1750,23 @@ def fix_repo(
|
||||
if status not in VALID_WP_STATUSES:
|
||||
status = "active"
|
||||
|
||||
# Find topic_id for this domain
|
||||
# Find topic_id — workplan frontmatter may still use legacy
|
||||
# coordination slugs (e.g. custodian); map to market domain first.
|
||||
topic_domain = resolve_topic_domain_slug(
|
||||
domain,
|
||||
repo_market_domain=repo_market_domain or None,
|
||||
)
|
||||
topics = _api_get(api_base, "/topics")
|
||||
topic_id = None
|
||||
if isinstance(topics, list):
|
||||
for t in topics:
|
||||
if t.get("domain_slug") == domain:
|
||||
if t.get("domain_slug") == topic_domain:
|
||||
topic_id = t["id"]
|
||||
break
|
||||
if topic_id is None:
|
||||
report.fixes_applied.append(
|
||||
f"C-06 SKIP {wp_id}: no topic found for domain '{domain}'"
|
||||
f"C-06 SKIP {wp_id}: no topic found for domain "
|
||||
f"'{topic_domain}' (workplan domain={domain!r})"
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
Reference in New Issue
Block a user