diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..de01797 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,21 @@ +# JKTV AI Working Agreement + +Read `context/boot.md` and `context/capsule.json` before changing the product. +Then read the smallest relevant document and implementation module. + +## Source Boundaries + +- JKTV owns the CS2 team data model, import pipeline, player and match views, + roster analysis and self-hosted application source. +- JOps owns shared edge, deployment observation and platform authentication + facts. +- Imported match files, runtime databases, tokens and user content remain + outside Git. + +## Change Protocol + +1. Preserve L1 raw, L2 normalized and L3 product-data boundaries. +2. Keep derived statistics reproducible from source match data. +3. Reject write operations without server-side authorization and CSRF checks. +4. Update product or data architecture documentation with contract changes. +5. Run `make context-check` and `make check` before committing. diff --git a/Makefile b/Makefile index 476d1fe..971f72e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ PYTHON := .venv/bin/python -.PHONY: install bootstrap run test check l1 l2 l3 l3-all pipeline prepare-data docker-up docker-down docker-logs +.PHONY: install bootstrap run test check context-check l1 l2 l3 l3-all pipeline prepare-data docker-up docker-down docker-logs install: python3 -m venv .venv @@ -15,7 +15,10 @@ run: test: $(PYTHON) -m unittest discover -v -check: +context-check: + python3 utils/check_context.py + +check: context-check $(PYTHON) -m compileall -q web database tests wsgi.py $(PYTHON) -m unittest discover -v diff --git a/context/boot.md b/context/boot.md new file mode 100644 index 0000000..64b9ef8 --- /dev/null +++ b/context/boot.md @@ -0,0 +1,32 @@ +# JKTV Context Boot + +## Purpose + +JKTV is a private CS2 team data product. It turns imported match captures into +auditable player, match, roster, opponent, award and tactical views. + +## Repository Map + +```text +database/L1/ raw capture processing +database/L2/ normalized match facts +database/L3/ player and team product models +web/ Flask application, routes, services and templates +tests/ pipeline and application checks +docs/ product and data architecture +context/ bounded repository identity for Universal Context +``` + +## Read Order + +```text +AGENTS.md +-> context/boot.md +-> context/capsule.json +-> README.md +-> docs/FeatureRDD.md +-> relevant pipeline or web module +``` + +Runtime match captures, databases, tokens and user content are not context +inputs. Current production claims require a fresh JOps observation. diff --git a/context/capsule.json b/context/capsule.json new file mode 100644 index 0000000..7ebbde0 --- /dev/null +++ b/context/capsule.json @@ -0,0 +1,81 @@ +{ + "schema": 1, + "kind": "jcm.repository-capsule", + "repository": { + "id": "jktv", + "name": "JKTV", + "remote": "Jacky/JKTV-online", + "role": "private-team-data-product", + "summary": "Owns the self-hosted CS2 team data pipeline, player and match pages, roster analytics, awards and tactical tools." + }, + "domains": [ + "platform" + ], + "context": { + "entrypoint": "AGENTS.md", + "read_order": [ + "AGENTS.md", + "context/boot.md", + "context/capsule.json", + "context/manifest.json", + "README.md", + "docs/FeatureRDD.md", + "docs/三层数据库架构重构计划_902db62c.md" + ] + }, + "ownership": { + "owns": [ + "CS2 match import and L1 to L3 data pipeline", + "Player, match, roster, opponent and tactical product views", + "Derived statistics, awards and data integrity checks" + ], + "excludes": [ + "Shared platform edge and production operations", + "Raw private match captures and runtime databases", + "Authentication tokens and user content" + ] + }, + "contracts": null, + "services": [ + { + "id": "jktv-web", + "name": "JKTV", + "kind": "team-data-application", + "summary": "Self-hosted CS2 team history, comparison, roster and tactical application.", + "public_urls": [ + "https://tv.jacky0987.xyz/" + ], + "domains": [ + "platform" + ] + } + ], + "relationships": [ + { + "target": "jops", + "type": "operates", + "summary": "Relies on JOps for shared edge and production runtime evidence." + } + ], + "runtime": { + "authority": "none" + }, + "current_state": { + "status": "released", + "summary": "The repository baseline is the 2.0.0 Beta self-hosted product; source documentation does not claim current production health.", + "evidence": [ + "README.md", + "docs/FeatureRDD.md", + "docs/三层数据库架构重构计划_902db62c.md" + ] + }, + "facts": [ + { + "id": "jktv-product-boundary", + "scope": "platform", + "summary": "JKTV is an independently deployed CS2 team data product with a reproducible L1, L2 and L3 data pipeline.", + "authority": "canonical", + "evidence": "README.md" + } + ] +} diff --git a/context/manifest.json b/context/manifest.json new file mode 100644 index 0000000..eb91d00 --- /dev/null +++ b/context/manifest.json @@ -0,0 +1,35 @@ +{ + "schema_version": "2.0.0", + "bundle_type": "repo-local-ai", + "repository": { + "owner": "Jacky", + "name": "JKTV-online", + "role": "private-team-data-product" + }, + "state": "RELEASED", + "runtime_state": "UNVERIFIED", + "entrypoint": "AGENTS.md", + "read_order": [ + "AGENTS.md", + "context/boot.md", + "context/capsule.json", + "context/manifest.json", + "README.md", + "docs/FeatureRDD.md", + "docs/三层数据库架构重构计划_902db62c.md" + ], + "contracts": {}, + "dependencies": { + "strategy": "none" + }, + "runtime": { + "freshness": "requires_jops_observation" + }, + "artifacts": [ + "context/boot.md", + "context/capsule.json", + "README.md", + "docs/FeatureRDD.md", + "docs/三层数据库架构重构计划_902db62c.md" + ] +} diff --git a/utils/check_context.py b/utils/check_context.py new file mode 100644 index 0000000..319c90b --- /dev/null +++ b/utils/check_context.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +import json +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +CAPSULE_FILE = ROOT / "context" / "capsule.json" +MANIFEST_FILE = ROOT / "context" / "manifest.json" + + +def fail(message): + raise SystemExit(f"context_check_error={message}") + + +try: + capsule = json.loads(CAPSULE_FILE.read_text(encoding="utf-8")) + manifest = json.loads(MANIFEST_FILE.read_text(encoding="utf-8")) +except (OSError, json.JSONDecodeError) as error: + fail(f"invalid_capsule:{error}") + +if ( + capsule.get("schema") != 1 + or capsule.get("kind") != "jcm.repository-capsule" + or capsule.get("repository", {}).get("id") != "jktv" + or capsule.get("repository", {}).get("remote") != "Jacky/JKTV-online" +): + fail("invalid_identity") +if ( + manifest.get("schema_version") != "2.0.0" + or manifest.get("repository", {}).get("name") != "JKTV-online" + or "context/capsule.json" not in manifest.get("read_order", []) +): + fail("invalid_manifest") + +paths = [ + capsule.get("context", {}).get("entrypoint"), + *capsule.get("context", {}).get("read_order", []), + *capsule.get("current_state", {}).get("evidence", []), + *[fact.get("evidence") for fact in capsule.get("facts", [])], +] +for relative in paths: + if not relative or Path(relative).is_absolute() or ".." in Path(relative).parts: + fail(f"unsafe_path:{relative}") + if not (ROOT / relative).is_file(): + fail(f"missing_path:{relative}") + +print("context_check=ok")