Spec-driven development on OpenSpec, with mechanical spec-as-source enforcement: a custom 'spec-as-source' OpenSpec schema adds file-ownership (targets) and test-verification ([@test]) metadata to every capability spec, three scripts (link check, ownership check, manifest build) keep code and specs from drifting apart, plus requirement-gathering, spec-writer, work-review, and a session-handoff skill with a proactive context-warning hook and a packaged handoff memory: the skill ships the exporter, importer, graph model, facts pipeline, Neo4j Compose runtime and operating guide to load handoffs into a local, authenticated Neo4j graph and query them.
68
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
#!/usr/bin/env python3
# GENERATED FROM SPEC — DO NOT EDIT DIRECTLY
# Source: openspec/specs/handoff-graph-model/spec.md
"""Deterministic handoff -> facts pipeline driven by the declarative graph model.
Subcommands:
validate-model check the model offline
collect ROOT... list HANDOFF-*.md under each root with SHA-256
extract ROOT... write HANDOFF-NNN.facts.json beside each handoff
ai-requests ROOT... write pending AI requests (JSON lines) for an external executor
ai-apply ROOT... validate the executor's responses and cache them
A root is a project directory containing ``.handoff/`` or a ``.handoff/``
directory itself. Extraction reads Markdown, ``.meta.yaml`` and the project
identity file; it writes only ``*.facts.json``, and only after every handoff of
every root was extracted and validated. It never creates ``graph-project-id``
unless ``--create-identity`` is given. No subcommand calls a model: AI rules
work through request and response files and ``.handoff/graph-ai-cache.json``
(see references/graph-ai-executor.md of the handoff skill). Import the result
with ``import_handoff_graph.py import --facts <dir>``, the importer beside this
script.
"""
from __future__ import annotations
import argparse
import hashlib
import importlib.util
import json
import sys
from pathlib import Path
from typing import Any
MODEL_ENGINE = Path(__file__).resolve().parent / "handoff_graph_model.py"
def _load_engine() -> Any:
name = "handoff_graph_model"
if name not in sys.modules:
spec = importlib.util.spec_from_file_location(name, MODEL_ENGINE)
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return sys.modules[name]
engine = _load_engine()
class PipelineError(Exception):
pass
def handoff_dir(root: Path) -> Path:
root = root.expanduser()
if (root / ".handoff").is_dir():
return (root / ".handoff").resolve()
if root.is_dir() and (root.name == ".handoff" or any(root.glob("HANDOFF-*.md"))):
return root.resolve()
raise PipelineError(f"{root}: no .handoff directory")
def collect(roots: list[str]) -> list[dict[str, str]]:
found: dict[str, dict[str, str]] = {}
for raw in roots:
directory = handoff_dir(Path(raw))
for path in sorted(directory.glob("HANDOFF-*.md")):
if not path.is_file() or not path.name[len("HANDOFF-"):-len(".md")].isdigit():
continue
found[str(path)] = {"file": str(path), "sha256": hashlib.sha256(path.read_bytes()).hexdigest()}
return [found[key] for key in sorted(found)]
def _build(path: Path, create_identity: bool) -> tuple[dict[str, Any], dict[str, Any], str]:
exporter = engine._load_exporter()
spans: dict[str, dict[str, Any]] = {}
try:
document, _ = exporter.build_document(path, create_identity=create_identity, spans=spans)
exporter.validate_document(document)
except exporter.ExportError as exc:
raise PipelineError(f"{path}: {exc}") from exc
return document, spans, hashlib.sha256(path.read_bytes()).hexdigest()
def load_items(roots: list[str], model: Any, create_identity: bool = False) -> tuple[list[Any], list[dict[str, Any]]]:
"""Deterministic facts of every handoff under the roots; raises after collecting every error."""
items, meta, errors = [], [], []
for entry in collect(roots):
path = Path(entry["file"])
try:
document, spans, digest = _build(path, create_identity)
facts = engine.extract(document, model, spans)
except PipelineError as exc:
errors.append(str(exc))
continue
except engine.ExtractError as exc:
errors.append(f"{path}: {exc}")
continue
items.append(engine.AIItem(document, facts, str(path.parent)))
meta.append({"path": path, "sha256": digest})
if errors:
raise PipelineError("; ".join(errors))
return items, meta
def _caches(items: list[Any]) -> dict[str, dict[str, Any]]:
try:
return {directory: engine.load_cache(directory) for directory in sorted({item.directory for item in items})}
except engine.ExtractError as exc:
raise PipelineError(str(exc)) from exc
def extract(roots: list[str], model: Any, create_identity: bool = False) -> dict[str, list[str]]:
"""Extract every handoff first, add cached AI edges, publish only when all succeeded."""
items, meta = load_items(roots, model, create_identity)
derived = engine.derived_edges(engine.build_requests(items, model), _caches(items))
exporter = engine._load_exporter()
outputs: list[tuple[Path, bytes]] = []
for item, info in zip(items, meta):
path = info["path"]
try:
facts = engine.add_derived(item.facts, derived)
# Same repository-relative rule as the sidecar exporter: never an absolute path.
data = engine.facts_document(model, item.document, facts, {
"kind": "markdown", "file": path.name,
"directory": exporter.relative_source_directory(path.parent), "sha256": info["sha256"],
})
engine.validate_facts(data, model)
except engine.ExtractError as exc:
raise PipelineError(f"{path}: {exc}") from exc
outputs.append((path.with_name(f"{path.stem}.facts.json"), engine.canonical(data)))
result: dict[str, list[str]] = {"created": [], "replaced": [], "unchanged": []}
for destination, payload in outputs:
result[exporter.publish_bytes(payload, destination)].append(str(destination))
return result
def ai_requests(roots: list[str], model: Any) -> list[dict[str, Any]]:
"""Requests of every AI rule that have no cached answer yet."""
items, _ = load_items(roots, model)
caches = _caches(items)
return [
item.request for item in engine.build_requests(items, model)
if item.request["request_id"] not in caches.get(item.directory, {})
]
def ai_apply(roots: list[str], model: Any, responses: Path) -> dict[str, int]:
"""Validate every response, then update the caches atomically; nothing is written on any error."""
items, _ = load_items(roots, model)
try:
lines = responses.read_text(encoding="utf-8").splitlines()
answers = engine.validate_responses(lines, engine.build_requests(items, model))
except (OSError, UnicodeError) as exc:
raise PipelineError(f"{responses}: could not be read") from exc
except engine.ExtractError as exc:
raise PipelineError(str(exc)) from exc
caches = _caches(items)
exporter = engine._load_exporter()
written = 0
for directory in sorted(answers):
merged = {**caches.get(directory, {}), **answers[directory]}
exporter.publish_bytes(engine.cache_bytes(merged), Path(directory) / engine.AI_CACHE_NAME)
written += len(answers[directory])
return {"answers": written, "caches": len(answers)}
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Handoff -> facts pipeline driven by the graph model")
parser.add_argument("--model", default=None, help="graph model file (default: references/graph-model.yaml of the handoff skill)")
sub = parser.add_subparsers(dest="command", required=True)
sub.add_parser("validate-model", help="validate the graph model offline")
for name, help_text in (
("collect", "list handoffs under the roots"),
("extract", "write facts beside each handoff"),
("ai-requests", "write pending AI requests as JSON lines"),
("ai-apply", "validate executor responses and store them in the AI caches"),
):
cmd = sub.add_parser(name, help=help_text)
cmd.add_argument("roots", nargs="+", help="project directories with .handoff/ or .handoff/ directories")
if name == "extract":
cmd.add_argument("--create-identity", action="store_true", help="create graph-project-id when a root has none")
if name == "ai-requests":
cmd.add_argument("--out", default=None, help="file to write (default: standard output)")
if name == "ai-apply":
cmd.add_argument("--responses", required=True, help="JSON lines produced by the executor")
return parser
def main(argv: list[str] | None = None) -> int:
args = _parser().parse_args(argv)
try:
model = engine.load_model(args.model)
except engine.ModelError as exc:
print(f"invalid model: {exc}", file=sys.stderr)
return 1
try:
if args.command == "validate-model":
print(
f"valid: model version {model.version}, {len(model.nodes)} node type(s), "
f"{len(model.relationships)} relationship type(s), {len(model.rules)} rule(s), {model.digest}"
)
elif args.command == "collect":
print(json.dumps(collect(args.roots), indent=2))
elif args.command == "ai-requests":
pending = ai_requests(args.roots, model)
text = "".join(json.dumps(item, ensure_ascii=False, sort_keys=True) + "\n" for item in pending)
if args.out:
Path(args.out).write_text(text, encoding="utf-8")
print(f"ai-requests: {len(pending)} pending request(s) written to {args.out}")
else:
sys.stdout.write(text)
elif args.command == "ai-apply":
result = ai_apply(args.roots, model, Path(args.responses))
print(f"ai-apply: {result['answers']} answer(s) stored in {result['caches']} cache file(s)")
else:
result = extract(args.roots, model, args.create_identity)
print(
f"extract: {sum(len(v) for v in result.values())} facts file(s) "
f"({len(result['created'])} created, {len(result['replaced'])} replaced, "
f"{len(result['unchanged'])} unchanged), model version {model.version}"
)
except PipelineError as exc:
print(f"{args.command} failed: {exc}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main()).tessl-plugin
rules
skills
handoff
handoff-skill
openspec-apply-change
openspec-archive-change
openspec-explore
openspec-propose
openspec-sync-specs
plan-judge
plan-mode
prompt-engineer
prompt-loop
requirement-gathering
spec-as-source-setup
templates
openspec-schema
spec-as-source
templates
spec-ci-sync
spec-loop
spec-rebuild
spec-verify
spec-writer
work-review