Files
Vijay Janapa Reddi f1ffe5b6fa Rename interviews/ to staffml/ and drop the repeated prefix inside it
interviews/ held StaffML and nothing else, so the directory now carries the
product's name. Three names inside it repeated that prefix, and now line up
with vault/, vault-cli/, and paper/:

  interviews/staffml/               -> staffml/app/
  interviews/staffml-vault-worker/  -> staffml/vault-worker/
  interviews/staffml-vault-types/   -> staffml/vault-types/

Every repository path moved with it: workflow paths: triggers and variable
fallbacks, pre-commit hooks, .gitignore, contributor tooling, vault-cli path
joins, the app's tsconfig mapping to the shared types, and the docs.

Left alone on purpose:
- the published /interviews/ redirect to /staffml/ and the gh-pages subsite
  lists that protect it; those are URLs, not repository paths
- package and worker names (staffml-vault-worker, @staffml/vault-types) and
  the vitest job name, which branch protection may key on
- question YAMLs and generated track JSON, which are content-addressed.
  Three questions carry an older corruption, "recall @interviews/vault/
  questions/...", that predates this change
- the applied D1 migration 0001 and the vault CHANGELOG, which are history

codegen-hashes.txt is re-recorded; the three pinned artifacts changed only in
path strings. vault-cli: 97 passed, codegen --check clean.

interviewer.py's __main__ loader was one parent short and pointed at a
corpus.json that never existed; it now resolves staffml/app/src/data/
corpus.json. tools/validate_playbook.py and generate_interview_flashcard.py
still expect per-track directories that no longer exist; they point at the
new root but remain dead.

The GitHub repository variables STAFFML_ROOT, VAULT_DIR, and VAULT_CLI_DIR
still name the old paths and must change when this reaches origin.
2026-09-10 14:48:04 +02:00

102 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""Add explicit status / provenance / deletion_reason where the YAML is
silently relying on Pydantic defaults or violating soft-delete pairing.
Three classes of fix:
A. status field missing entirely
Pydantic defaults to 'draft', but the YAML on disk lacks the field.
Add explicit `status: draft` + `provenance: imported` so the file
no longer relies on a silent default.
B. deleted but no deletion_reason
Soft-delete pairing rule (Question.status='deleted' must carry
deletion_reason). Add placeholder text.
C. flagged with no human_reviewed
Reported but not auto-fixed; needs human disposition.
Usage:
python3 staffml/vault-cli/scripts/fix_missing_metadata.py [--dry-run]
"""
from __future__ import annotations
import argparse
import os
import sys
from collections import Counter
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(REPO_ROOT / "staffml" / "vault-cli" / "src"))
from vault_cli.models import Question # noqa: E402
from vault_cli.yaml_io import dump_str, load_file # noqa: E402
QUESTIONS_DIR = REPO_ROOT / "staffml" / "vault" / "questions"
PLACEHOLDER_REASON = (
"imported as deleted; original deletion reason was not preserved on import"
)
def write_yaml(path: Path, body: dict) -> None:
text = dump_str(body)
tmp = path.with_suffix(path.suffix + ".tmp")
tmp.write_text(text, encoding="utf-8")
os.replace(tmp, path)
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--dry-run", action="store_true")
args = ap.parse_args()
counters: Counter[str] = Counter()
for yp in QUESTIONS_DIR.rglob("*.yaml"):
body = load_file(yp)
if not isinstance(body, dict):
continue
changed = False
# Class A: status field missing → set draft + imported
if "status" not in body:
body["status"] = "draft"
if "provenance" not in body:
body["provenance"] = "imported"
counters["status-added"] += 1
changed = True
# Class B: deleted without deletion_reason
if body.get("status") == "deleted" and not body.get("deletion_reason"):
body["deletion_reason"] = PLACEHOLDER_REASON
counters["deletion-reason-added"] += 1
changed = True
if not changed:
continue
try:
Question.model_validate(body)
except Exception as e:
counters["pydantic-fail"] += 1
print(f" pydantic-fail {body.get('id')}: {str(e)[:200]}", file=sys.stderr)
continue
if args.dry_run:
print(f" [dry] {body.get('id')}: status={body.get('status')!r}, "
f"provenance={body.get('provenance')!r}, "
f"deletion_reason={'yes' if body.get('deletion_reason') else 'no'}")
else:
write_yaml(yp, body)
print(f"\ncounters: {dict(counters)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())