From 4df09a3246fbedd6137825edb93b7ad4ae20d4a5 Mon Sep 17 00:00:00 2001 From: MakiforDevelop Date: Sun, 31 May 2026 14:18:56 +0800 Subject: [PATCH 1/2] =?UTF-8?q?refactor(snapshot):=20=E6=8A=BD=E5=87=BA=20?= =?UTF-8?q?models.py=EF=BC=88God-file=20=E6=8B=86=E5=88=86=20Phase=201a?= =?UTF-8?q?=EF=BC=8CFacade=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit core.py 1556 行的 model 層(2 type alias + 6 Pydantic model + _Candidate + ConstructCard warnings filter)抽到新檔 snapshot/models.py。core.py 以 re-export 保持所有既有 import 路徑(from ...snapshot.core import SnapshotBundle 等)完全不變。core.py 1556 → 1453 行。 純搬移、零行為改變。models.py 不 import core.py(無循環依賴)。 驗證: pytest 327 passed(= baseline)+ ruff clean。 Constraint: 23★ public repo,Facade 保 ~25 處 build_snapshot_bundle caller 不破 Directive: models.py 為依賴底層,只能被 import 不可 import core(單向) Rejected: 一次拆完 core.py(models/review/renderers/generator)| 風險集中, 改逐刀驗證;本刀只動 model 層 Not-tested: 無(純結構搬移,既有測試全綠即覆蓋) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/virtualme/snapshot/core.py | 125 +++---------------------------- src/virtualme/snapshot/models.py | 119 +++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 114 deletions(-) create mode 100644 src/virtualme/snapshot/models.py diff --git a/src/virtualme/snapshot/core.py b/src/virtualme/snapshot/core.py index a2b34da..3679821 100644 --- a/src/virtualme/snapshot/core.py +++ b/src/virtualme/snapshot/core.py @@ -2,23 +2,27 @@ import json import re -import warnings -from dataclasses import dataclass from datetime import UTC, datetime from pathlib import Path from typing import Literal -from pydantic import BaseModel, Field - from virtualme.interview.pii import scrub_pii from virtualme.interview.triples import PersonaTriple +from virtualme.snapshot.models import ( + ConstructCard, + ConstructCardReview, + EvidenceItem, + MiniBlindTestItem, + ReviewEvidenceQuality, + ReviewVerdict, + SnapshotBundle, + SoulLiteHypothesis, + _Candidate, +) from virtualme.storage.db import DB, Anchor, Dimension, Layer SNAPSHOT_SCHEMA_VERSION = "0.1" -ReviewVerdict = Literal["like_me", "unlike_me", "unsure", "missing_context"] -ReviewEvidenceQuality = Literal["none", "low", "medium", "medium_high", "high"] - CORE_DIMENSIONS = ( Dimension.SOUL, Dimension.BOUNDARIES, @@ -86,113 +90,6 @@ "它", } -warnings.filterwarnings( - "ignore", - message='Field name "register" in "ConstructCard" shadows an attribute in parent "BaseModel"', - category=UserWarning, -) - - -class EvidenceItem(BaseModel): - kind: str - dimension: Dimension | None = None - layer: Layer | None = None - content: str - source_anchor_ids: list[int] = Field(default_factory=list) - source_turn_ids: list[int] = Field(default_factory=list) - source_question_ids: list[str] = Field(default_factory=list) - confidence: float | None = None - - -class SoulLiteHypothesis(BaseModel): - id: str - dimension: Dimension - hypothesis: str - confidence: str - evidence: list[EvidenceItem] - missing_evidence: str - suggested_follow_up: str - needs_verification: bool - - -class MiniBlindTestItem(BaseModel): - id: str - dimension: Dimension - scenario: str - what_to_compare: str - evidence_hint: str - - -class ConstructCard(BaseModel): - id: str - title: str - decision_rule: str - trigger_context: str - protected_value: str - traded_value: str | None = None - default_action: str - refused_action: str | None = None - exception_rule: str | None = None - register: str | None = None - falsifier: str - supporting_evidence: list[EvidenceItem] - disconfirming_evidence: list[EvidenceItem] - source_anchor_ids: list[int] - source_turn_ids: list[int] - source_question_ids: list[str] - dimension_tags: list[Dimension] - confidence_level: Literal["insufficient", "draft", "plausible", "validated"] - confidence_reason: str - confidence_checks: dict[str, bool] - missing_evidence: list[str] - blind_test_probe: str | None = None - feedback_routes: list[str] - extraction_method: Literal["rule_based", "llm_assisted", "human_curated"] - policy_status: Literal["espoused_only", "behavior_supported", "contradicted", "validated"] - stability_scope: str | None = None - context_dependence: str | None = None - exception_archetype: Literal[ - "relational_credit", - "asymmetric_leverage", - "operational_reciprocity", - ] | None = None - - -class ConstructCardReview(BaseModel): - card_id: str - verdict: ReviewVerdict - reviewer: str | None = None - reviewed_at: str | None = None - notes: str | None = None - concrete_case: str | None = None - exception_note: str | None = None - counterexample_note: str | None = None - exact_wording_note: str | None = None - pressure_note: str | None = None - decision_tradeoff_note: str | None = None - evidence_quality: ReviewEvidenceQuality = "none" - status_after_review: str | None = None - confidence_level: Literal["insufficient", "draft", "plausible", "validated"] | None = None - policy_status: Literal["espoused_only", "behavior_supported", "contradicted", "validated"] | None = None - - -class SnapshotBundle(BaseModel): - schema_version: str = SNAPSHOT_SCHEMA_VERSION - interviewee_id: str - generated_at: str - construct_cards: list[ConstructCard] - hypotheses: list[SoulLiteHypothesis] - mini_blind_test: list[MiniBlindTestItem] - feedback_routes: list[str] - - -@dataclass(frozen=True) -class _Candidate: - dimension: Dimension - content: str - evidence: EvidenceItem - weight: int - async def build_snapshot_bundle(db: DB, interviewee_id: str) -> SnapshotBundle: anchors = await db.load_anchors_summary(interviewee_id) diff --git a/src/virtualme/snapshot/models.py b/src/virtualme/snapshot/models.py new file mode 100644 index 0000000..a1b3622 --- /dev/null +++ b/src/virtualme/snapshot/models.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import warnings +from dataclasses import dataclass +from typing import Literal + +from pydantic import BaseModel, Field + +from virtualme.storage.db import Dimension, Layer + +ReviewVerdict = Literal["like_me", "unlike_me", "unsure", "missing_context"] +ReviewEvidenceQuality = Literal["none", "low", "medium", "medium_high", "high"] + +warnings.filterwarnings( + "ignore", + message='Field name "register" in "ConstructCard" shadows an attribute in parent "BaseModel"', + category=UserWarning, +) + + +class EvidenceItem(BaseModel): + kind: str + dimension: Dimension | None = None + layer: Layer | None = None + content: str + source_anchor_ids: list[int] = Field(default_factory=list) + source_turn_ids: list[int] = Field(default_factory=list) + source_question_ids: list[str] = Field(default_factory=list) + confidence: float | None = None + + +class SoulLiteHypothesis(BaseModel): + id: str + dimension: Dimension + hypothesis: str + confidence: str + evidence: list[EvidenceItem] + missing_evidence: str + suggested_follow_up: str + needs_verification: bool + + +class MiniBlindTestItem(BaseModel): + id: str + dimension: Dimension + scenario: str + what_to_compare: str + evidence_hint: str + + +class ConstructCard(BaseModel): + id: str + title: str + decision_rule: str + trigger_context: str + protected_value: str + traded_value: str | None = None + default_action: str + refused_action: str | None = None + exception_rule: str | None = None + register: str | None = None + falsifier: str + supporting_evidence: list[EvidenceItem] + disconfirming_evidence: list[EvidenceItem] + source_anchor_ids: list[int] + source_turn_ids: list[int] + source_question_ids: list[str] + dimension_tags: list[Dimension] + confidence_level: Literal["insufficient", "draft", "plausible", "validated"] + confidence_reason: str + confidence_checks: dict[str, bool] + missing_evidence: list[str] + blind_test_probe: str | None = None + feedback_routes: list[str] + extraction_method: Literal["rule_based", "llm_assisted", "human_curated"] + policy_status: Literal["espoused_only", "behavior_supported", "contradicted", "validated"] + stability_scope: str | None = None + context_dependence: str | None = None + exception_archetype: Literal[ + "relational_credit", + "asymmetric_leverage", + "operational_reciprocity", + ] | None = None + + +class ConstructCardReview(BaseModel): + card_id: str + verdict: ReviewVerdict + reviewer: str | None = None + reviewed_at: str | None = None + notes: str | None = None + concrete_case: str | None = None + exception_note: str | None = None + counterexample_note: str | None = None + exact_wording_note: str | None = None + pressure_note: str | None = None + decision_tradeoff_note: str | None = None + evidence_quality: ReviewEvidenceQuality = "none" + status_after_review: str | None = None + confidence_level: Literal["insufficient", "draft", "plausible", "validated"] | None = None + policy_status: Literal["espoused_only", "behavior_supported", "contradicted", "validated"] | None = None + + +class SnapshotBundle(BaseModel): + schema_version: str = "0.1" + interviewee_id: str + generated_at: str + construct_cards: list[ConstructCard] + hypotheses: list[SoulLiteHypothesis] + mini_blind_test: list[MiniBlindTestItem] + feedback_routes: list[str] + + +@dataclass(frozen=True) +class _Candidate: + dimension: Dimension + content: str + evidence: EvidenceItem + weight: int From 4e0d16c7b3e5d50e8eb04c637d6da4843c45ec32 Mon Sep 17 00:00:00 2001 From: MakiforDevelop Date: Sun, 31 May 2026 14:30:23 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(examples):=20=E5=8A=A0=E5=90=88?= =?UTF-8?q?=E6=88=90=20demo=20persona=20=E2=80=94=20=E8=AE=93=E4=BA=BA?= =?UTF-8?q?=E4=B8=8D=E7=94=A8=E8=B7=91=208=20=E9=80=B1=E5=B0=B1=E7=9C=8B?= =?UTF-8?q?=E5=88=B0=E6=88=90=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clone 者最大的障礙是「看不到我會拿到什麼」(examples/ 原本空的,export 又需要 populated DB)。新增: - scripts/seed_demo.py:用 18 個合成雙語錨點(虛構人物,非真人)建臨時 DB,跑真實 export pipeline 產出 archive。PRINCIPLE+≥3 question_ids → triangulated core truths(demo 有 5 個) - examples/sample-maker/:真 pipeline 產出的 11 檔 persona archive(8 維 markdown + START_HERE + index + manifest.json),雙語展示 CJK 路徑 - examples/README.md:明確標示 synthetic + 怎麼重生 - README「8 週後你會拿到什麼」加 examples 指引 North star: 讓 VirtualMe 成熟到值得 reference/clone — example output 是 比拆 God file 更高槓桿的 clone-worthiness 訊號。 Constraint: example 必須是真 pipeline 產出,不可手編(才代表真實輸出) Directive: 人物 generic 不影射真人 + 清楚標 synthetic Not-tested: seed script 無單元測試(產出已人工檢視;export pipeline 既有測試覆蓋) Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 2 + examples/README.md | 28 ++++++ examples/sample-maker/BOUNDARIES.md | 61 ++++++++++++ examples/sample-maker/HISTORY.md | 48 ++++++++++ examples/sample-maker/JOURNAL.md | 21 ++++ examples/sample-maker/PEOPLE.md | 48 ++++++++++ examples/sample-maker/SKILL.md | 62 ++++++++++++ examples/sample-maker/SOUL.md | 75 +++++++++++++++ examples/sample-maker/START_HERE.md | 34 +++++++ examples/sample-maker/STATE.md | 34 +++++++ examples/sample-maker/VOICE.md | 62 ++++++++++++ examples/sample-maker/index.md | 17 ++++ examples/sample-maker/manifest.json | 132 +++++++++++++++++++++++++ pyproject.toml | 1 + scripts/seed_demo.py | 143 ++++++++++++++++++++++++++++ 15 files changed, 768 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/sample-maker/BOUNDARIES.md create mode 100644 examples/sample-maker/HISTORY.md create mode 100644 examples/sample-maker/JOURNAL.md create mode 100644 examples/sample-maker/PEOPLE.md create mode 100644 examples/sample-maker/SKILL.md create mode 100644 examples/sample-maker/SOUL.md create mode 100644 examples/sample-maker/START_HERE.md create mode 100644 examples/sample-maker/STATE.md create mode 100644 examples/sample-maker/VOICE.md create mode 100644 examples/sample-maker/index.md create mode 100644 examples/sample-maker/manifest.json create mode 100644 scripts/seed_demo.py diff --git a/README.md b/README.md index 7a45249..47e5ae4 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ VirtualMe 把這個發現延伸成可上線的 pipeline: ## 8 週後你會拿到什麼 +> 🔎 **先看成品**:[`examples/sample-maker/`](examples/sample-maker/) 是用真實 export pipeline 從合成資料產出的 demo persona archive(虛構人物、雙語)。不用跑 8 週就能看到輸出長怎樣,從 [`START_HERE.md`](examples/sample-maker/START_HERE.md) 開始。 + 8 個 persona markdown 檔案(你的 archive;匯出時另含 `START_HERE.md`、`index.md`、`manifest.json` 作為入口、索引與機器可讀 metadata): | 檔案 | 內容 | diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..6e7b82f --- /dev/null +++ b/examples/README.md @@ -0,0 +1,28 @@ +# Examples + +## `sample-maker/` — a generated demo persona archive + +This folder is a **real export from the VirtualMe pipeline**, so you can see what +you get *before* committing to an 8-week interview. + +> ⚠️ **The persona is synthetic.** `sample-maker` is a fictional composite — not a +> real person. The anchors are hand-written demo data, deliberately bilingual +> (English + Traditional Chinese) to show the CJK-aware extraction path. + +What to look at: + +- **[`sample-maker/START_HERE.md`](sample-maker/START_HERE.md)** — the entry point a real user would read first. +- **[`sample-maker/SOUL.md`](sample-maker/SOUL.md)** — core identity, with *triangulated* "Core Truths" (a principle that surfaced across ≥3 questions) separated from "Emerging Patterns", each with collapsible provenance. +- **[`sample-maker/manifest.json`](sample-maker/manifest.json)** — the machine-readable index that ships beside the markdown. + +## Regenerate it + +The archive is produced by the real export pipeline from synthetic seed data: + +```bash +python scripts/seed_demo.py +``` + +This seeds a throwaway database with the fictional persona, runs +`virtualme.export.export_markdown`, and writes the archive to `examples/sample-maker/`. +Edit the `ANCHORS` list in [`scripts/seed_demo.py`](../scripts/seed_demo.py) to change the demo. diff --git a/examples/sample-maker/BOUNDARIES.md b/examples/sample-maker/BOUNDARIES.md new file mode 100644 index 0000000..a016ced --- /dev/null +++ b/examples/sample-maker/BOUNDARIES.md @@ -0,0 +1,61 @@ +--- +schema_version: "0.5" +dimension: BOUNDARIES +exported_at: 2026-05-31T06:28:05+00:00 +anchor_count: 3 +triangulated_count: 2 +emerging_count: 1 +anchor_content_pii_scrubbed: true +--- + +# BOUNDARIES + +Refusals, privacy rules, and persona update protocol. + +## Core Truths + +- + + > Never lets an automated agent send anything outward without a human review step — draft → review → ship, every time. + +
+ Provenance + + - Layer: principle + - Status: triangulated + - Questions: Q08, Q19, Q33 + - Turns: 5 + +
+ +- + + > Keeps private/sensitive context local; refuses to put it in shared or third-party spaces. + +
+ Provenance + + - Layer: principle + - Status: triangulated + - Questions: Q12, Q26, Q34 + - Turns: 6 + +
+ + +## Emerging Patterns + +- + + > 不在週末處理非緊急的工作訊息 — 保護休息與家庭時間。 + +
+ Provenance + + - Layer: pattern + - Status: emerging + - Questions: Q21 + - Turns: 7 + +
+ diff --git a/examples/sample-maker/HISTORY.md b/examples/sample-maker/HISTORY.md new file mode 100644 index 0000000..8614d4b --- /dev/null +++ b/examples/sample-maker/HISTORY.md @@ -0,0 +1,48 @@ +--- +schema_version: "0.5" +dimension: HISTORY +exported_at: 2026-05-31T06:28:05+00:00 +anchor_count: 2 +triangulated_count: 0 +emerging_count: 2 +anchor_content_pii_scrubbed: true +--- + +# HISTORY + +Life events and durable personal timeline context. + +## Core Truths + +_(no core truths yet)_ + +## Emerging Patterns + +- + + > Moved from a sales/operations role into product, then into building personal AI infrastructure as a long-running side project. + +
+ Provenance + + - Layer: fact + - Status: emerging + - Questions: Q01 + - Turns: 16 + +
+ +- + + > 曾把一個副專案從 demo 養成有真實使用者的小產品。 + +
+ Provenance + + - Layer: fact + - Status: emerging + - Questions: Q02 + - Turns: 17 + +
+ diff --git a/examples/sample-maker/JOURNAL.md b/examples/sample-maker/JOURNAL.md new file mode 100644 index 0000000..44014dd --- /dev/null +++ b/examples/sample-maker/JOURNAL.md @@ -0,0 +1,21 @@ +--- +schema_version: "0.5" +dimension: JOURNAL +exported_at: 2026-05-31T06:28:05+00:00 +anchor_count: 0 +triangulated_count: 0 +emerging_count: 0 +anchor_content_pii_scrubbed: true +--- + +# JOURNAL + +Reflections, interpretations, and periodic event notes. + +## Core Truths + +_(no core truths yet)_ + +## Emerging Patterns + +_(no emerging patterns yet)_ diff --git a/examples/sample-maker/PEOPLE.md b/examples/sample-maker/PEOPLE.md new file mode 100644 index 0000000..19604bf --- /dev/null +++ b/examples/sample-maker/PEOPLE.md @@ -0,0 +1,48 @@ +--- +schema_version: "0.5" +dimension: PEOPLE +exported_at: 2026-05-31T06:28:05+00:00 +anchor_count: 2 +triangulated_count: 0 +emerging_count: 2 +anchor_content_pii_scrubbed: true +--- + +# PEOPLE + +Relationship context and recurring people schemas. + +## Core Truths + +_(no core truths yet)_ + +## Emerging Patterns + +- + + > Collaborates with a small set of named AI agents, each given one clear role rather than one agent doing everything. + +
+ Provenance + + - Layer: fact + - Status: emerging + - Questions: Q20 + - Turns: 14 + +
+ +- + + > Has a partner and family whose time is explicitly protected on the calendar. + +
+ Provenance + + - Layer: fact + - Status: emerging + - Questions: Q25 + - Turns: 15 + +
+ diff --git a/examples/sample-maker/SKILL.md b/examples/sample-maker/SKILL.md new file mode 100644 index 0000000..6be5f5c --- /dev/null +++ b/examples/sample-maker/SKILL.md @@ -0,0 +1,62 @@ +--- +schema_version: "0.5" +dimension: SKILL +exported_at: 2026-05-31T06:28:05+00:00 +anchor_count: 3 +triangulated_count: 0 +emerging_count: 3 +anchor_content_pii_scrubbed: true +--- + +# SKILL + +Domain know-how, practices, and task preferences. + +## Core Truths + +_(no core truths yet)_ + +## Emerging Patterns + +- + + > Builds multi-agent systems with CLI + plain files instead of heavy frameworks; values things that still run when a vendor disappears. + +
+ Provenance + + - Layer: pattern + - Status: emerging + - Questions: Q16, Q30 + - Turns: 11 + +
+ +- + + > Runs a small home lab (single mini server + a VPS) with Docker; treats infrastructure as replaceable cattle, not pets. + +
+ Provenance + + - Layer: fact + - Status: emerging + - Questions: Q17 + - Turns: 12 + +
+ +- + + > 熟悉本地 LLM 部署、向量檢索與 RAG pipeline。 + +
+ Provenance + + - Layer: fact + - Status: emerging + - Questions: Q23 + - Turns: 13 + +
+ diff --git a/examples/sample-maker/SOUL.md b/examples/sample-maker/SOUL.md new file mode 100644 index 0000000..d90c23c --- /dev/null +++ b/examples/sample-maker/SOUL.md @@ -0,0 +1,75 @@ +--- +schema_version: "0.5" +dimension: SOUL +exported_at: 2026-05-31T06:28:05+00:00 +anchor_count: 4 +triangulated_count: 3 +emerging_count: 1 +anchor_content_pii_scrubbed: true +--- + +# SOUL + +Identity, values, and durable red lines. + +## Core Truths + +- + + > Ships imperfect things on purpose — a working v0.5 today beats a perfect v1.0 that never ships. 寧可今天出一個會動的 v0.5,也不要永遠出不了的完美 v1.0。 + +
+ Provenance + + - Layer: principle + - Status: triangulated + - Questions: Q03, Q11, Q24 + - Turns: 1 + +
+ +- + + > Treats reversible decisions as cheap and irreversible ones as expensive — moves fast on the former, slows right down on the latter. + +
+ Provenance + + - Layer: principle + - Status: triangulated + - Questions: Q07, Q18, Q31 + - Turns: 2 + +
+ +- + + > Optimises for future attention, not raw output — 最稀缺的資源是專注力,不是工時。 + +
+ Provenance + + - Layer: principle + - Status: triangulated + - Questions: Q05, Q22, Q29 + - Turns: 3 + +
+ + +## Emerging Patterns + +- + + > Reaches for the smallest experiment that could disprove an idea before committing to it. + +
+ Provenance + + - Layer: pattern + - Status: emerging + - Questions: Q14 + - Turns: 4 + +
+ diff --git a/examples/sample-maker/START_HERE.md b/examples/sample-maker/START_HERE.md new file mode 100644 index 0000000..0db0e38 --- /dev/null +++ b/examples/sample-maker/START_HERE.md @@ -0,0 +1,34 @@ +# Start Here: sample-maker + +This folder is your VirtualMe persona archive: human-editable Markdown files +extracted from the interview process, with a machine-readable manifest beside them. + +- Exported at: 2026-05-31T06:28:05+00:00 +- Persona files: 8 +- Total anchors: 18 +- Core truths: 5 + +## Recommended Reading Order + +1. [SOUL.md](SOUL.md) - core identity and values. +2. [VOICE.md](VOICE.md) - how the agent should sound. +3. [BOUNDARIES.md](BOUNDARIES.md) - what the agent should not do. +4. [index.md](index.md) - complete file list and counts. + +## Archive Files + +- [SOUL.md](SOUL.md): Identity, values, and durable red lines. +- [VOICE.md](VOICE.md): Voice patterns and reusable expression samples. +- [SKILL.md](SKILL.md): Domain know-how, practices, and task preferences. +- [PEOPLE.md](PEOPLE.md): Relationship context and recurring people schemas. +- [HISTORY.md](HISTORY.md): Life events and durable personal timeline context. +- [JOURNAL.md](JOURNAL.md): Reflections, interpretations, and periodic event notes. +- [BOUNDARIES.md](BOUNDARIES.md): Refusals, privacy rules, and persona update protocol. +- [STATE.md](STATE.md): Current-state snapshot that may become stale over time. + +## Machine-Readable Metadata + +- [manifest.json](manifest.json) contains schema version, counts, and payload file hashes. +- Markdown frontmatter contains per-file dimension metadata. +- Provenance details are folded under each item so the main text stays readable. +- PII scrubbing applies to exported anchor content; archive IDs and folder names are unchanged. diff --git a/examples/sample-maker/STATE.md b/examples/sample-maker/STATE.md new file mode 100644 index 0000000..50cbb18 --- /dev/null +++ b/examples/sample-maker/STATE.md @@ -0,0 +1,34 @@ +--- +schema_version: "0.5" +dimension: STATE +exported_at: 2026-05-31T06:28:05+00:00 +anchor_count: 1 +triangulated_count: 0 +emerging_count: 1 +anchor_content_pii_scrubbed: true +--- + +# STATE + +Current-state snapshot that may become stale over time. + +## Core Truths + +_(no core truths yet)_ + +## Emerging Patterns + +- + + > Currently maintaining several small open-source repos that together form a personal knowledge stack. + +
+ Provenance + + - Layer: fact + - Status: emerging + - Questions: Q32 + - Turns: 18 + +
+ diff --git a/examples/sample-maker/VOICE.md b/examples/sample-maker/VOICE.md new file mode 100644 index 0000000..3824808 --- /dev/null +++ b/examples/sample-maker/VOICE.md @@ -0,0 +1,62 @@ +--- +schema_version: "0.5" +dimension: VOICE +exported_at: 2026-05-31T06:28:05+00:00 +anchor_count: 3 +triangulated_count: 0 +emerging_count: 3 +anchor_content_pii_scrubbed: true +--- + +# VOICE + +Voice patterns and reusable expression samples. + +## Core Truths + +_(no core truths yet)_ + +## Emerging Patterns + +- + + > Writes notes and commits in [Person A], code and APIs in English — 中英混用是常態,不刻意統一。 + +
+ Provenance + + - Layer: pattern + - Status: emerging + - Questions: Q09, Q27 + - Turns: 8 + +
+ +- + + > Direct and a little blunt; skips hedging. Says 'this is wrong because X' rather than 'maybe consider X'. + +
+ Provenance + + - Layer: pattern + - Status: emerging + - Questions: Q10, Q28 + - Turns: 9 + +
+ +- + + > Prefers concrete examples over abstract advice — 「給我看 code」勝過「跟我講理論」。 + +
+ Provenance + + - Layer: fact + - Status: emerging + - Questions: Q15 + - Turns: 10 + +
+ diff --git a/examples/sample-maker/index.md b/examples/sample-maker/index.md new file mode 100644 index 0000000..c9b5b83 --- /dev/null +++ b/examples/sample-maker/index.md @@ -0,0 +1,17 @@ +# VirtualMe Export: sample-maker + +- Generated at: 2026-05-31T06:28:05+00:00 +- Total anchors: 18 +- Triangulated principles: 5 +- Schema version: 0.5 + +## Persona Files + +- [SOUL.md](SOUL.md): 4 anchors, 3 triangulated +- [VOICE.md](VOICE.md): 3 anchors, 0 triangulated +- [SKILL.md](SKILL.md): 3 anchors, 0 triangulated +- [PEOPLE.md](PEOPLE.md): 2 anchors, 0 triangulated +- [HISTORY.md](HISTORY.md): 2 anchors, 0 triangulated +- [JOURNAL.md](JOURNAL.md): 0 anchors, 0 triangulated +- [BOUNDARIES.md](BOUNDARIES.md): 3 anchors, 2 triangulated +- [STATE.md](STATE.md): 1 anchors, 0 triangulated diff --git a/examples/sample-maker/manifest.json b/examples/sample-maker/manifest.json new file mode 100644 index 0000000..aa2b08e --- /dev/null +++ b/examples/sample-maker/manifest.json @@ -0,0 +1,132 @@ +{ + "schema_version": "0.5", + "export_id": "1d7957d9-e727-43bc-8838-d4f79cfac0b7", + "interviewee_id": "sample-maker", + "exported_at": "2026-05-31T06:28:05+00:00", + "persona_files": [ + "SOUL.md", + "VOICE.md", + "SKILL.md", + "PEOPLE.md", + "HISTORY.md", + "JOURNAL.md", + "BOUNDARIES.md", + "STATE.md" + ], + "archive_files": [ + "BOUNDARIES.md", + "HISTORY.md", + "JOURNAL.md", + "PEOPLE.md", + "SKILL.md", + "SOUL.md", + "START_HERE.md", + "STATE.md", + "VOICE.md", + "index.md", + "manifest.json" + ], + "human_entrypoint": "START_HERE.md", + "technical_index": "index.md", + "pii_scrub_scope": "anchor_content", + "dimensions": { + "SOUL": { + "file": "SOUL.md", + "description": "Identity, values, and durable red lines.", + "anchor_count": 4, + "triangulated_count": 3, + "emerging_count": 1 + }, + "VOICE": { + "file": "VOICE.md", + "description": "Voice patterns and reusable expression samples.", + "anchor_count": 3, + "triangulated_count": 0, + "emerging_count": 3 + }, + "SKILL": { + "file": "SKILL.md", + "description": "Domain know-how, practices, and task preferences.", + "anchor_count": 3, + "triangulated_count": 0, + "emerging_count": 3 + }, + "PEOPLE": { + "file": "PEOPLE.md", + "description": "Relationship context and recurring people schemas.", + "anchor_count": 2, + "triangulated_count": 0, + "emerging_count": 2 + }, + "HISTORY": { + "file": "HISTORY.md", + "description": "Life events and durable personal timeline context.", + "anchor_count": 2, + "triangulated_count": 0, + "emerging_count": 2 + }, + "JOURNAL": { + "file": "JOURNAL.md", + "description": "Reflections, interpretations, and periodic event notes.", + "anchor_count": 0, + "triangulated_count": 0, + "emerging_count": 0 + }, + "BOUNDARIES": { + "file": "BOUNDARIES.md", + "description": "Refusals, privacy rules, and persona update protocol.", + "anchor_count": 3, + "triangulated_count": 2, + "emerging_count": 1 + }, + "STATE": { + "file": "STATE.md", + "description": "Current-state snapshot that may become stale over time.", + "anchor_count": 1, + "triangulated_count": 0, + "emerging_count": 1 + } + }, + "payload_files": { + "BOUNDARIES.md": { + "sha256": "sha256:6197ecb310e6b29120a27ccaba7bd90b9494150f162809d9999f3d06765de5ad", + "bytes": 1033 + }, + "HISTORY.md": { + "sha256": "sha256:7758a0bfcb71240ad766555095335c33bb49e315a1f2acbe9a6c08bf692971b7", + "bytes": 777 + }, + "JOURNAL.md": { + "sha256": "sha256:9a243eb9bbeeca72230fa077993db8d013abae7fce87b43e95c41f65ebf9940b", + "bytes": 338 + }, + "PEOPLE.md": { + "sha256": "sha256:8f7e1e1400655cd7033a9114f14570382084cdb6104e45d1367d1e18d7d4ed46", + "bytes": 771 + }, + "SKILL.md": { + "sha256": "sha256:f8e4b16c6a305ca19e41b39e2e5e32906ad34f66598819391400411f99244f03", + "bytes": 1032 + }, + "SOUL.md": { + "sha256": "sha256:72c746dae91197e02c17d23e7f66e89c690bb8cd9cacdfa983eddf8e5306c076", + "bytes": 1374 + }, + "START_HERE.md": { + "sha256": "sha256:899d80b3a119d902795a57ee81cf909716c0075e8c36e8d5b8d5b35d28063aa8", + "bytes": 1541 + }, + "STATE.md": { + "sha256": "sha256:b15f4dbc1160880b867dba5b8fcd8701f8f08dca20a0959cde609b3234489819", + "bytes": 544 + }, + "VOICE.md": { + "sha256": "sha256:781d9c1255e226675b9a578770d30d1e63f7eedaaae7e80061e945f689b83ff2", + "bytes": 1044 + }, + "index.md": { + "sha256": "sha256:dc522cb4105b46945ecf91d21c0a0f3b8913a316a8fe8bbc48f085bb52c5a811", + "bytes": 584 + } + } +} diff --git a/pyproject.toml b/pyproject.toml index e3385ca..099fff0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ ignore = ["E501"] [tool.ruff.lint.per-file-ignores] "src/virtualme/snapshot/behavior_profile.py" = ["RUF001", "RUF002", "RUF003"] +"scripts/seed_demo.py" = ["RUF001", "RUF002", "RUF003"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/scripts/seed_demo.py b/scripts/seed_demo.py new file mode 100644 index 0000000..40c9713 --- /dev/null +++ b/scripts/seed_demo.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +"""Seed a synthetic demo persona and export it to examples/. + +This populates a throwaway database with a SYNTHETIC, FICTIONAL persona +(no real person) and runs the real export pipeline, so a fresh cloner can +see what a VirtualMe persona archive looks like without doing 8 weeks of +interviews. + +The persona is bilingual (English + Traditional Chinese) on purpose, to +show the CJK-aware extraction path. + +Usage: + python scripts/seed_demo.py + python scripts/seed_demo.py --out examples --interviewee sample-maker +""" + +from __future__ import annotations + +import argparse +import asyncio +import sys +import tempfile +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) + +from virtualme.export.markdown import export_markdown +from virtualme.storage.db import DB, Dimension, Layer, init_db + +# --- SYNTHETIC DEMO DATA — fictional persona, not a real person --- +# (dimension, layer, content, question_ids) +# PRINCIPLE-layer anchors with >=3 distinct question_ids export as +# "triangulated" core truths. +ANCHORS: list[tuple[Dimension, Layer, str, list[str]]] = [ + # SOUL — core identity / values (triangulated principles) + (Dimension.SOUL, Layer.PRINCIPLE, + "Ships imperfect things on purpose — a working v0.5 today beats a perfect " + "v1.0 that never ships. 寧可今天出一個會動的 v0.5,也不要永遠出不了的完美 v1.0。", + ["Q03", "Q11", "Q24"]), + (Dimension.SOUL, Layer.PRINCIPLE, + "Treats reversible decisions as cheap and irreversible ones as expensive — " + "moves fast on the former, slows right down on the latter.", + ["Q07", "Q18", "Q31"]), + (Dimension.SOUL, Layer.PRINCIPLE, + "Optimises for future attention, not raw output — 最稀缺的資源是專注力,不是工時。", + ["Q05", "Q22", "Q29"]), + (Dimension.SOUL, Layer.PATTERN, + "Reaches for the smallest experiment that could disprove an idea before " + "committing to it.", + ["Q14"]), + # BOUNDARIES — red lines (principles) + (Dimension.BOUNDARIES, Layer.PRINCIPLE, + "Never lets an automated agent send anything outward without a human review " + "step — draft → review → ship, every time.", + ["Q08", "Q19", "Q33"]), + (Dimension.BOUNDARIES, Layer.PRINCIPLE, + "Keeps private/sensitive context local; refuses to put it in shared or " + "third-party spaces.", + ["Q12", "Q26", "Q34"]), + (Dimension.BOUNDARIES, Layer.PATTERN, + "不在週末處理非緊急的工作訊息 — 保護休息與家庭時間。", + ["Q21"]), + # VOICE — how the agent should sound (patterns) + (Dimension.VOICE, Layer.PATTERN, + "Writes notes and commits in Traditional Chinese, code and APIs in English — " + "中英混用是常態,不刻意統一。", + ["Q09", "Q27"]), + (Dimension.VOICE, Layer.PATTERN, + "Direct and a little blunt; skips hedging. Says 'this is wrong because X' " + "rather than 'maybe consider X'.", + ["Q10", "Q28"]), + (Dimension.VOICE, Layer.FACT, + "Prefers concrete examples over abstract advice — 「給我看 code」勝過「跟我講理論」。", + ["Q15"]), + # SKILL — domain know-how (facts/patterns) + (Dimension.SKILL, Layer.PATTERN, + "Builds multi-agent systems with CLI + plain files instead of heavy " + "frameworks; values things that still run when a vendor disappears.", + ["Q16", "Q30"]), + (Dimension.SKILL, Layer.FACT, + "Runs a small home lab (single mini server + a VPS) with Docker; treats " + "infrastructure as replaceable cattle, not pets.", + ["Q17"]), + (Dimension.SKILL, Layer.FACT, + "熟悉本地 LLM 部署、向量檢索與 RAG pipeline。", + ["Q23"]), + # PEOPLE — relationships (facts) + (Dimension.PEOPLE, Layer.FACT, + "Collaborates with a small set of named AI agents, each given one clear role " + "rather than one agent doing everything.", + ["Q20"]), + (Dimension.PEOPLE, Layer.FACT, + "Has a partner and family whose time is explicitly protected on the calendar.", + ["Q25"]), + # HISTORY — life narrative (facts) + (Dimension.HISTORY, Layer.FACT, + "Moved from a sales/operations role into product, then into building personal " + "AI infrastructure as a long-running side project.", + ["Q01"]), + (Dimension.HISTORY, Layer.FACT, + "曾把一個副專案從 demo 養成有真實使用者的小產品。", + ["Q02"]), + # STATE — recent context (fact) + (Dimension.STATE, Layer.FACT, + "Currently maintaining several small open-source repos that together form a " + "personal knowledge stack.", + ["Q32"]), +] + + +async def seed_and_export(interviewee: str, out_dir: Path) -> list[Path]: + with tempfile.TemporaryDirectory() as tmp: + db_path = str(Path(tmp) / "demo.db") + await init_db(db_path) + db = DB(db_path) + await db.init() + for i, (dimension, layer, content, question_ids) in enumerate(ANCHORS): + await db.save_anchor( + interviewee_id=interviewee, + dimension=dimension, + layer=layer, + content=content, + source_turn_ids=[i + 1], + source_question_ids=question_ids, + model="synthetic-demo", + ) + return await export_markdown(db, interviewee, out_dir) + + +async def main() -> None: + parser = argparse.ArgumentParser(description="Seed a synthetic demo persona and export it.") + parser.add_argument("--interviewee", default="sample-maker") + parser.add_argument("--out", type=Path, default=Path("examples")) + args = parser.parse_args() + + written = await seed_and_export(args.interviewee, args.out) + print(f"Exported {len(written)} files to {args.out / args.interviewee}/") + for path in written: + print(f" - {path}") + + +if __name__ == "__main__": + asyncio.run(main())