From 65096be9d9485bc11093ea24047c473271396d60 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 08:48:10 +0000 Subject: [PATCH 1/5] Check skill instructions structurally instead of word for word The entrypoint test pinned ~35 phrases to SKILL.md, so any reshaping of the instructions broke it. It now checks the contract instead: the frontmatter name and invocations, that every bundled path the instructions mention exists, that every prompt is loaded by something, and that the documented research floors are metrics merge_research.py actually reports. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_017tXoVWiktX2vSUJzrrdBLJ --- SKILL.md | 2 +- tests/test_skill_entrypoint_docs.py | 82 +++++++++++++++++------------ 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/SKILL.md b/SKILL.md index e0e8e92c..055fe457 100644 --- a/SKILL.md +++ b/SKILL.md @@ -11,7 +11,7 @@ allowed-tools: Read, Write, Edit, Bash > **Skill Root**: Before reading a bundled prompt or running a bundled script, resolve the absolute directory of the `SKILL.md` that the host actually loaded. In the instructions below, `{distilly_skill_root}` means that exact directory. Claude Code exposes it as `${CLAUDE_SKILL_DIR}`; on every other host, use the loaded-skill path supplied by that host's discovery context. Do not assume the shell's current working directory is the Skill root, and do not guess or hard-code an install path. If the host does not expose the loaded path or more than one Distilly installation is ambiguous, ask the user to identify the active installation before running code. > -> Keep the shell in the user's current workspace so relative output paths such as `./skills/...` remain project-local. Resolve every `tools/...` and `prompts/...` resource against `{distilly_skill_root}`. For example, execute the bundled `tools/example.py` as `python3 "{distilly_skill_root}/tools/example.py"`; replace the placeholder with the resolved absolute path in the actual tool call. +> Keep the shell in the user's current workspace so relative output paths such as `./skills/...` remain project-local. Resolve every `tools/...` and `prompts/...` resource against `{distilly_skill_root}`. For example, run `tools/owner_profile.py` as `python3 "{distilly_skill_root}/tools/owner_profile.py"`; replace the placeholder with the resolved absolute path in the actual tool call. # Distilly Creator diff --git a/tests/test_skill_entrypoint_docs.py b/tests/test_skill_entrypoint_docs.py index 4875ddbe..d871e3a5 100644 --- a/tests/test_skill_entrypoint_docs.py +++ b/tests/test_skill_entrypoint_docs.py @@ -42,6 +42,16 @@ re.compile(r"(?=|=) \d+`") + + +def _instruction_files() -> dict[str, str]: + """Everything the agent may read while running the Skill: the router plus prompts.""" + paths = [ROOT / "SKILL.md", *sorted((ROOT / "prompts").rglob("*.md"))] + paths.extend(sorted((ROOT / "references").rglob("*.md"))) + return {path.relative_to(ROOT).as_posix(): path.read_text(encoding="utf-8") for path in paths} class SkillEntrypointDocsTest(unittest.TestCase): @@ -98,42 +108,46 @@ def _assert_readme_quick_start( def test_root_skill_uses_distilly_entrypoint(self) -> None: content = (ROOT / "SKILL.md").read_text(encoding="utf-8") - self.assertIn("name: distilly", content) - self.assertIn("`/distilly`", content) - self.assertIn("`$distilly`", content) - self.assertIn("`/skill:distilly`", content) - self.assertNotIn("name: dot-skill", content) - self.assertNotIn("`/dot-skill`", content) - self.assertIn("compatible hosts", content.lower()) - self.assertIn("Grok Build", content) - self.assertIn("Grok Bot", content) - self.assertIn("Pi coding agent", content) - self.assertIn("Management Operations", content) - self.assertIn("tools/skill_writer.py", content) - self.assertIn("tools/research/xquik_public_posts.py", content) - self.assertIn("prompts/celebrity/research.md", content) - self.assertIn("budget-unfriendly", content) - self.assertIn("references/celebrity_budget_unfriendly_framework.md", content) - self.assertIn("01_core_profile.md", content) - self.assertIn("03_expression_and_reception.md", content) - self.assertIn("Files scanned >= 3", content) - self.assertIn("Unique URLs >= 2", content) - self.assertIn("Potential long quote lines = 0", content) - self.assertIn("actual inspected pages", content) - self.assertIn("01_writings.md", content) - self.assertIn("06_timeline.md", content) - self.assertIn("Files scanned >= 6", content) - self.assertIn("Unique URLs >= 8", content) - self.assertIn("Primary-source markers >= 3", content) - self.assertIn("research_audit.md", content) - self.assertIn("--work-patch /tmp/distilly_{slug}_work_patch.md", content) - self.assertIn("Do not hand-edit `work.md`", content) + frontmatter = content.split("---", 2)[1] + self.assertIn("name: distilly", frontmatter) + self.assertNotIn("dot-skill", content) + for invocation in ("`/distilly`", "`$distilly`", "`/skill:distilly`"): + self.assertIn(invocation, content) + # Bundled resources resolve against the loaded Skill, never the shell cwd. self.assertIn("{distilly_skill_root}", content) self.assertIn("${CLAUDE_SKILL_DIR}", content) - self.assertIn("Do not assume the shell's current working directory", content) - self.assertNotIn("python3 tools/", content) - self.assertNotIn("`/list-skills`", content) - self.assertNotIn("Compatibility aliases:", content) + + def test_instruction_files_reference_existing_resources(self) -> None: + for path, content in _instruction_files().items(): + self.assertIsNone( + BARE_TOOL_CALL.search(content), + f"{path} runs a bundled tool relative to the shell cwd", + ) + for reference in BUNDLED_PATH.findall(content): + self.assertTrue( + (ROOT / reference).exists(), + f"{path} references missing {reference}", + ) + + def test_every_prompt_is_reachable(self) -> None: + referenced = set() + for content in _instruction_files().values(): + referenced.update(BUNDLED_PATH.findall(content)) + referenced.update( + BUNDLED_PATH.findall((ROOT / "tools" / "skill_presets.py").read_text(encoding="utf-8")) + ) + for prompt_path in sorted((ROOT / "prompts").rglob("*.md")): + relative = prompt_path.relative_to(ROOT).as_posix() + self.assertTrue(relative in referenced, f"nothing loads {relative}") + + def test_research_floors_match_merge_summary(self) -> None: + summary_source = (ROOT / "tools" / "research" / "merge_research.py").read_text(encoding="utf-8") + floors = set() + for content in _instruction_files().values(): + floors.update(RESEARCH_FLOOR.findall(content)) + self.assertTrue(floors, "no research quality floors documented") + for metric in floors: + self.assertIn(f"- {metric}:", summary_source, f"merge summary never reports {metric!r}") def test_readme_quick_start_and_install_detail_contract(self) -> None: readme = (ROOT / "README.md").read_text(encoding="utf-8") From 20dcb8982eb05ce83a21f4922080479bdc427116 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 08:48:58 +0000 Subject: [PATCH 2/5] Give colleague and shared prompts their own folders, fold references/ in The colleague prompts sat in the prompts/ root, so prompts/intake.md read like a generic intake when it is the colleague one. Colleague prompts now live in prompts/colleague/ and the three family-agnostic ones in prompts/shared/. references/ only held the two budget-unfriendly celebrity files, which move next to the prompts that read them. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_017tXoVWiktX2vSUJzrrdBLJ --- SKILL.md | 22 +++++++------- bin/distilly.mjs | 1 - package.json | 1 - prompts/celebrity/budget_unfriendly/audit.md | 2 +- .../celebrity/budget_unfriendly/framework.md | 0 .../budget_unfriendly/persona_builder.md | 2 +- .../celebrity/budget_unfriendly/synthesis.md | 2 +- .../celebrity/budget_unfriendly/template.md | 0 prompts/{ => colleague}/intake.md | 0 prompts/{ => colleague}/merger.md | 0 prompts/{ => colleague}/persona_analyzer.md | 0 prompts/{ => colleague}/persona_builder.md | 0 prompts/{ => shared}/correction_handler.md | 0 prompts/{ => shared}/work_analyzer.md | 0 prompts/{ => shared}/work_builder.md | 0 tests/test_skill_entrypoint_docs.py | 10 ++----- tests/test_skill_writer.py | 2 +- tools/skill_presets.py | 30 +++++++++---------- 18 files changed, 32 insertions(+), 40 deletions(-) rename references/celebrity_budget_unfriendly_framework.md => prompts/celebrity/budget_unfriendly/framework.md (100%) rename references/celebrity_budget_unfriendly_template.md => prompts/celebrity/budget_unfriendly/template.md (100%) rename prompts/{ => colleague}/intake.md (100%) rename prompts/{ => colleague}/merger.md (100%) rename prompts/{ => colleague}/persona_analyzer.md (100%) rename prompts/{ => colleague}/persona_builder.md (100%) rename prompts/{ => shared}/correction_handler.md (100%) rename prompts/{ => shared}/work_analyzer.md (100%) rename prompts/{ => shared}/work_builder.md (100%) diff --git a/SKILL.md b/SKILL.md index 055fe457..1797c1d0 100644 --- a/SKILL.md +++ b/SKILL.md @@ -117,7 +117,7 @@ Default to `budget-friendly`. Only switch to `budget-unfriendly` when the user e Choose the intake prompt by character family: -- `colleague` → `prompts/intake.md` +- `colleague` → `prompts/colleague/intake.md` - `relationship` → `prompts/relationship/intake.md` - `celebrity` → `prompts/celebrity/intake.md` @@ -384,14 +384,14 @@ First resolve the execution matrix for the selected character family: | character | intake | persona analyzer | persona builder | merger | storage root | |-----------|--------|------------------|-----------------|--------|--------------| -| `colleague` | `prompts/intake.md` | `prompts/persona_analyzer.md` | `prompts/persona_builder.md` | `prompts/merger.md` | `./skills/colleague/{slug}` | +| `colleague` | `prompts/colleague/intake.md` | `prompts/colleague/persona_analyzer.md` | `prompts/colleague/persona_builder.md` | `prompts/colleague/merger.md` | `./skills/colleague/{slug}` | | `relationship` | `prompts/relationship/intake.md` | `prompts/relationship/persona_analyzer.md` | `prompts/relationship/persona_builder.md` | `prompts/relationship/merger.md` | `./skills/relationship/{slug}` | | `celebrity` | `prompts/celebrity/intake.md` | `prompts/celebrity/persona_analyzer.md` | `prompts/celebrity/persona_builder.md` | `prompts/celebrity/merger.md` | `./skills/celebrity/{slug}` | Shared across all families: -- Work analyzer: `prompts/work_analyzer.md` -- Work builder: `prompts/work_builder.md` -- Correction handler: `prompts/correction_handler.md` +- Work analyzer: `prompts/shared/work_analyzer.md` +- Work builder: `prompts/shared/work_builder.md` +- Correction handler: `prompts/shared/correction_handler.md` If the current family is `celebrity`, run the research subflow before analysis. @@ -474,7 +474,7 @@ Read `XQUIK_API_KEY` only from the shell; never print or store it. Treat the JSO 1. First read: - `prompts/celebrity/budget_unfriendly/research.md` - - `references/celebrity_budget_unfriendly_framework.md` + - `prompts/celebrity/budget_unfriendly/framework.md` 2. Create the research directories first: ```bash mkdir -p "{skill_dir}/knowledge/research/raw" "{skill_dir}/knowledge/research/merged" "{skill_dir}/knowledge/research/reviews" @@ -507,7 +507,7 @@ Read `XQUIK_API_KEY` only from the shell; never print or store it. Treat the JSO 9. Then read: - `prompts/celebrity/budget_unfriendly/audit.md` - `prompts/celebrity/budget_unfriendly/synthesis.md` - - `references/celebrity_budget_unfriendly_template.md` + - `prompts/celebrity/budget_unfriendly/template.md` 10. First write `knowledge/research/reviews/research_audit.md` - The audit must produce an explicit `PASS / FAIL` - The audit must verify: source hierarchy compliance (no blacklisted sources), primary-source ratio > 50%, taste principle compliance, cold figure assessment @@ -546,7 +546,7 @@ Shared rules for both celebrity profiles: Once the family is resolved, analyze along two tracks: **Track A (Work Skill)**: -- Refer to `prompts/work_analyzer.md` +- Refer to `prompts/shared/work_analyzer.md` - Extract: responsible systems, technical standards, workflow, output preferences, experience - For `celebrity`, interpret `work` as methods, judgment frameworks, and decision patterns rather than literal job scope @@ -565,11 +565,11 @@ Once the family is resolved, analyze along two tracks: ### Step 4: Generate and Preview -Use `prompts/work_builder.md` to generate Work content. +Use `prompts/shared/work_builder.md` to generate Work content. Use the family-specific persona builder to generate Persona content. Mapping: -- `colleague` → `prompts/persona_builder.md` +- `colleague` → `prompts/colleague/persona_builder.md` - `relationship` → `prompts/relationship/persona_builder.md` - `celebrity` → `prompts/celebrity/persona_builder.md` - `celebrity` + `budget-unfriendly` → `prompts/celebrity/budget_unfriendly/persona_builder.md` @@ -715,7 +715,7 @@ When user provides new files or text: When user expresses "that's wrong" / "he should be": -1. Refer to `prompts/correction_handler.md` to identify correction content +1. Refer to `prompts/shared/correction_handler.md` to identify correction content 2. Determine if it belongs to Work (technical/workflow) or Persona (personality/communication) 3. If it belongs to Work: - Generate `/tmp/distilly_{slug}_work_patch.md` diff --git a/bin/distilly.mjs b/bin/distilly.mjs index 7460a406..731857f9 100755 --- a/bin/distilly.mjs +++ b/bin/distilly.mjs @@ -20,7 +20,6 @@ const packageMetadata = JSON.parse( const payloadEntries = [ "SKILL.md", "prompts", - "references", "tools", "requirements.txt", "INSTALL.md", diff --git a/package.json b/package.json index 453f911c..9e70208e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "bin/", "SKILL.md", "prompts/", - "references/", "tools/", "requirements.txt", "INSTALL.md", diff --git a/prompts/celebrity/budget_unfriendly/audit.md b/prompts/celebrity/budget_unfriendly/audit.md index 89b4c0a7..e502bb67 100644 --- a/prompts/celebrity/budget_unfriendly/audit.md +++ b/prompts/celebrity/budget_unfriendly/audit.md @@ -8,7 +8,7 @@ Read: - all files under `knowledge/research/raw/` - `knowledge/research/merged/summary.md` -- `references/celebrity_budget_unfriendly_framework.md` +- `prompts/celebrity/budget_unfriendly/framework.md` Write the audit to `knowledge/research/reviews/research_audit.md`. diff --git a/references/celebrity_budget_unfriendly_framework.md b/prompts/celebrity/budget_unfriendly/framework.md similarity index 100% rename from references/celebrity_budget_unfriendly_framework.md rename to prompts/celebrity/budget_unfriendly/framework.md diff --git a/prompts/celebrity/budget_unfriendly/persona_builder.md b/prompts/celebrity/budget_unfriendly/persona_builder.md index 0aa4c043..a51f33f4 100644 --- a/prompts/celebrity/budget_unfriendly/persona_builder.md +++ b/prompts/celebrity/budget_unfriendly/persona_builder.md @@ -8,7 +8,7 @@ Generate a deeper `persona.md` for a public figure using the full research pipel - research audit - synthesis review - validation review -- `references/celebrity_budget_unfriendly_template.md` +- `prompts/celebrity/budget_unfriendly/template.md` This mode produces a richer, more evidence-backed persona than the standard builder. diff --git a/prompts/celebrity/budget_unfriendly/synthesis.md b/prompts/celebrity/budget_unfriendly/synthesis.md index f391df8e..b727b3fa 100644 --- a/prompts/celebrity/budget_unfriendly/synthesis.md +++ b/prompts/celebrity/budget_unfriendly/synthesis.md @@ -6,7 +6,7 @@ Read: - the six-track research notes - `knowledge/research/reviews/research_audit.md` -- `references/celebrity_budget_unfriendly_framework.md` +- `prompts/celebrity/budget_unfriendly/framework.md` Build a synthesis review that separates: diff --git a/references/celebrity_budget_unfriendly_template.md b/prompts/celebrity/budget_unfriendly/template.md similarity index 100% rename from references/celebrity_budget_unfriendly_template.md rename to prompts/celebrity/budget_unfriendly/template.md diff --git a/prompts/intake.md b/prompts/colleague/intake.md similarity index 100% rename from prompts/intake.md rename to prompts/colleague/intake.md diff --git a/prompts/merger.md b/prompts/colleague/merger.md similarity index 100% rename from prompts/merger.md rename to prompts/colleague/merger.md diff --git a/prompts/persona_analyzer.md b/prompts/colleague/persona_analyzer.md similarity index 100% rename from prompts/persona_analyzer.md rename to prompts/colleague/persona_analyzer.md diff --git a/prompts/persona_builder.md b/prompts/colleague/persona_builder.md similarity index 100% rename from prompts/persona_builder.md rename to prompts/colleague/persona_builder.md diff --git a/prompts/correction_handler.md b/prompts/shared/correction_handler.md similarity index 100% rename from prompts/correction_handler.md rename to prompts/shared/correction_handler.md diff --git a/prompts/work_analyzer.md b/prompts/shared/work_analyzer.md similarity index 100% rename from prompts/work_analyzer.md rename to prompts/shared/work_analyzer.md diff --git a/prompts/work_builder.md b/prompts/shared/work_builder.md similarity index 100% rename from prompts/work_builder.md rename to prompts/shared/work_builder.md diff --git a/tests/test_skill_entrypoint_docs.py b/tests/test_skill_entrypoint_docs.py index d871e3a5..f34620f2 100644 --- a/tests/test_skill_entrypoint_docs.py +++ b/tests/test_skill_entrypoint_docs.py @@ -42,7 +42,7 @@ re.compile(r"(?=|=) \d+`") @@ -50,7 +50,6 @@ def _instruction_files() -> dict[str, str]: """Everything the agent may read while running the Skill: the router plus prompts.""" paths = [ROOT / "SKILL.md", *sorted((ROOT / "prompts").rglob("*.md"))] - paths.extend(sorted((ROOT / "references").rglob("*.md"))) return {path.relative_to(ROOT).as_posix(): path.read_text(encoding="utf-8") for path in paths} @@ -269,7 +268,7 @@ def test_non_chinese_surfaces_do_not_mix_chinese_copy(self) -> None: ROOT / "INSTALL_EN.md", ROOT / "prompts" / "celebrity" / "research.md", ROOT / "prompts" / "celebrity" / "budget_unfriendly" / "research.md", - ROOT / "references" / "celebrity_budget_unfriendly_framework.md", + ROOT / "prompts" / "celebrity" / "budget_unfriendly" / "framework.md", ROOT / ".github" / "PULL_REQUEST_TEMPLATE.md", ] single_language_paths.extend( @@ -304,11 +303,6 @@ def test_non_chinese_surfaces_do_not_mix_chinese_copy(self) -> None: HAN_CHARACTER.search(prompt_path.read_text(encoding="utf-8")), f"Chinese copy remains in {prompt_path.relative_to(ROOT)}", ) - for reference_path in sorted((ROOT / "references").rglob("*.md")): - self.assertIsNone( - HAN_CHARACTER.search(reference_path.read_text(encoding="utf-8")), - f"Chinese copy remains in {reference_path.relative_to(ROOT)}", - ) for name in ("README_JA.md", "ROADMAP_JA.md"): content = (ROOT / "docs" / "lang" / name).read_text(encoding="utf-8") diff --git a/tests/test_skill_writer.py b/tests/test_skill_writer.py index fbf876f7..d23ce529 100644 --- a/tests/test_skill_writer.py +++ b/tests/test_skill_writer.py @@ -342,7 +342,7 @@ def test_create_celebrity_budget_unfriendly_embeds_profile_config(self) -> None: saved_meta["engine"]["research_profile_bundle"].values(), ) self.assertIn( - "references/celebrity_budget_unfriendly_framework.md", + "prompts/celebrity/budget_unfriendly/framework.md", saved_meta["engine"]["research_profile_references"], ) self.assertEqual(manifest["research_profile"], "budget-unfriendly") diff --git a/tools/skill_presets.py b/tools/skill_presets.py index 7e65e20d..c702fbbe 100644 --- a/tools/skill_presets.py +++ b/tools/skill_presets.py @@ -36,13 +36,13 @@ "storage_root": "skills/colleague", "prompt_bundle": { "preset": "distilly.colleague.v1", - "intake": "prompts/intake.md", - "work_analyzer": "prompts/work_analyzer.md", - "persona_analyzer": "prompts/persona_analyzer.md", - "work_builder": "prompts/work_builder.md", - "persona_builder": "prompts/persona_builder.md", - "merger": "prompts/merger.md", - "correction_handler": "prompts/correction_handler.md", + "intake": "prompts/colleague/intake.md", + "work_analyzer": "prompts/shared/work_analyzer.md", + "persona_analyzer": "prompts/colleague/persona_analyzer.md", + "work_builder": "prompts/shared/work_builder.md", + "persona_builder": "prompts/colleague/persona_builder.md", + "merger": "prompts/colleague/merger.md", + "correction_handler": "prompts/shared/correction_handler.md", }, "legacy_storage_root": "colleagues", "skill_name_prefix": "colleague", @@ -64,12 +64,12 @@ "prompt_bundle": { "preset": "distilly.relationship.v1", "intake": "prompts/relationship/intake.md", - "work_analyzer": "prompts/work_analyzer.md", + "work_analyzer": "prompts/shared/work_analyzer.md", "persona_analyzer": "prompts/relationship/persona_analyzer.md", - "work_builder": "prompts/work_builder.md", + "work_builder": "prompts/shared/work_builder.md", "persona_builder": "prompts/relationship/persona_builder.md", "merger": "prompts/relationship/merger.md", - "correction_handler": "prompts/correction_handler.md", + "correction_handler": "prompts/shared/correction_handler.md", }, "legacy_storage_root": "skills/relationship", "skill_name_prefix": "relationship", @@ -98,12 +98,12 @@ "preset": "distilly.celebrity.v1", "intake": "prompts/celebrity/intake.md", "research": "prompts/celebrity/research.md", - "work_analyzer": "prompts/work_analyzer.md", + "work_analyzer": "prompts/shared/work_analyzer.md", "persona_analyzer": "prompts/celebrity/persona_analyzer.md", - "work_builder": "prompts/work_builder.md", + "work_builder": "prompts/shared/work_builder.md", "persona_builder": "prompts/celebrity/persona_builder.md", "merger": "prompts/celebrity/merger.md", - "correction_handler": "prompts/correction_handler.md", + "correction_handler": "prompts/shared/correction_handler.md", }, "default_research_profile": DEFAULT_RESEARCH_PROFILE, "research_profiles": { @@ -136,8 +136,8 @@ "persona_builder": "prompts/celebrity/budget_unfriendly/persona_builder.md", }, "references": [ - "references/celebrity_budget_unfriendly_framework.md", - "references/celebrity_budget_unfriendly_template.md", + "prompts/celebrity/budget_unfriendly/framework.md", + "prompts/celebrity/budget_unfriendly/template.md", ], "merge_strategy": "deep", "quality_profile": "budget-unfriendly", From 7fd859fc18f5a6c2726ff2081aca0a855273aa93 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 08:51:32 +0000 Subject: [PATCH 3/5] Split SKILL.md into a short router and on-demand flow files Hosts load SKILL.md in full on every trigger, so an owner's first run read 782 lines to use about 80. SKILL.md is now a 41-line router: the skill-root contract, which flow to read for each request, and the rules every flow shares. The flows live in prompts/flows/ (owner, create, sources, celebrity_research, evolve, manage) and each names the prompts it needs. While moving the text, the router and flows stop restating the prompts: the intake questions, the per-family persona builder list and the research taste rules now live only in the prompt that owns them. That also drops the wrong claim that the celebrity intake has four questions (it has five). Import options are in A-E order, and the list, rollback and delete commands are one template instead of three copies. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_017tXoVWiktX2vSUJzrrdBLJ --- SKILL.md | 771 +--------------------------- prompts/flows/celebrity_research.md | 150 ++++++ prompts/flows/create.md | 165 ++++++ prompts/flows/evolve.md | 66 +++ prompts/flows/manage.md | 18 + prompts/flows/owner.md | 19 + prompts/flows/sources.md | 264 ++++++++++ tests/test_owner_profile.py | 4 +- tests/test_skill_entrypoint_docs.py | 8 + 9 files changed, 708 insertions(+), 757 deletions(-) create mode 100644 prompts/flows/celebrity_research.md create mode 100644 prompts/flows/create.md create mode 100644 prompts/flows/evolve.md create mode 100644 prompts/flows/manage.md create mode 100644 prompts/flows/owner.md create mode 100644 prompts/flows/sources.md diff --git a/SKILL.md b/SKILL.md index 1797c1d0..cd71099c 100644 --- a/SKILL.md +++ b/SKILL.md @@ -17,766 +17,25 @@ allowed-tools: Read, Write, Edit, Bash > Distilly was formerly **Colleague Skill / colleague-skill**. The current Skill frontmatter name and creator entrypoint are both `distilly`. -## Trigger Conditions +## Route the request -Activate when the user says any of the following: -- `/distilly` -- "Help me create a skill" -- "I want to distill someone" -- "Create a new skill" -- "Make a skill for XX" -- "Set up my profile" / "Get to know me" / "Who am I to you", or a host's first run with no owner profile yet → go straight to the "Owner Profile" flow below +Read only the flow file the request needs, then follow it. -Compatible hosts: -- Claude Code -- OpenClaw -- Hermes -- Codex -- DeepSeek Harness -- Pi coding agent -- Grok Build -- OpenCode +| The user says | Flow | +|---------------|------| +| "Set up my profile" / "Get to know me" / "Who am I to you", "distill myself", or a host's first run with no owner profile yet | `prompts/flows/owner.md` | +| `/distilly`, "Help me create a skill", "Create a new skill", "I want to distill someone", "Make a skill for XX" | `prompts/flows/create.md` (its Step 0 sends `owner` back to the owner flow) | +| "I have new files" / "append", "That's wrong" / "He wouldn't do that" / "He should be", `/update-skill {character} {slug}`, or the alias `/update-colleague {slug}` | `prompts/flows/evolve.md` | +| "Show my skills", roll back or delete a generated skill | `prompts/flows/manage.md` | -Explicit invocation differs among hosts that expose it: use `/distilly` in Claude Code, Hermes, DeepSeek Harness, and Grok Build; use `/distilly` in OpenClaw, or `/skill distilly` when its native slash is not registered; use `$distilly` or choose it through `/skills` in Codex; use `/skill:distilly` in Pi. OpenCode uses native Skill discovery and loading; do not invent a dedicated command. +The create flow pulls in `prompts/flows/sources.md` for importing material and, for `celebrity`, `prompts/flows/celebrity_research.md`. Each flow names the prompts it needs; read those when the flow gets there, not up front. -Grok Bot can save a workflow as a private Skill, but its official documentation does not describe direct local `SKILL.md` directory imports. Do not present this repository as a direct Grok Bot install; migrate the workflow manually into a saved Skill or wait for a dedicated adapter. +## Rules for every flow -Enter evolution mode when the user says: -- "I have new files" / "append" -- "That's wrong" / "He wouldn't do that" / "He should be" -- `/update-skill {character} {slug}` +- **Storage**: generated skills live under `./skills/colleague/{slug}/`, `./skills/relationship/{slug}/` or `./skills/celebrity/{slug}/` in the user's workspace. For a global path, pass `--base-dir` with that family's storage root. +- **Writes**: create and update generated skills only through `tools/skill_writer.py`; never hand-build or hand-edit their files. +- **Credentials**: App Secrets, access or refresh tokens, Bot Tokens and OAuth codes never enter the conversation. Never ask for them; the user runs a collector's `--setup` / `--login` in their own terminal. `prompts/flows/sources.md` has the details. -Compatibility update alias: -- `/update-colleague {slug}` +## Compatible hosts -When the user asks to see generated skills, use the list commands in "Management Operations" below. - ---- - -## Tool Usage Rules - -This Skill runs in any compatible host that can read local files and execute Bash / Python commands. Use the following tool conventions: - -| Task | Tool | -|------|------| -| Read PDF documents | `Read` tool (native PDF support) | -| Read image screenshots | `Read` tool (native image support) | -| Read MD/TXT files | `Read` tool | -| Parse Lark message JSON export | `Bash` → `python3 "{distilly_skill_root}/tools/feishu_parser.py"` | -| Lark auto-collect (recommended) | `Bash` → `python3 "{distilly_skill_root}/tools/feishu_auto_collector.py"` | -| Lark docs (browser session) | `Bash` → `python3 "{distilly_skill_root}/tools/feishu_browser.py"` | -| Lark docs (MCP App Token) | `Bash` → `python3 "{distilly_skill_root}/tools/feishu_mcp_client.py"` | -| DingTalk auto-collect | `Bash` → `python3 "{distilly_skill_root}/tools/dingtalk_auto_collector.py"` | -| Collect public X post candidates | `Bash` → `python3 "{distilly_skill_root}/tools/research/xquik_public_posts.py"` | -| Parse email .eml/.mbox | `Bash` → `python3 "{distilly_skill_root}/tools/email_parser.py"` | -| Write/update Skill files | `Write` / `Edit` tool | -| Version management | `Bash` → `python3 "{distilly_skill_root}/tools/version_manager.py"` | -| List existing Skills | `Bash` → `python3 "{distilly_skill_root}/tools/skill_writer.py" --action list` | - -**Base directories**: -- `colleague` → `./skills/colleague/{slug}/` -- `relationship` → `./skills/relationship/{slug}/` -- `celebrity` → `./skills/celebrity/{slug}/` - -For a global path, use `--base-dir` with the storage root for that character family. - -**Credential rules (Lark / DingTalk / Slack)**: App Secrets, user_access_token, refresh_token, Bot Tokens and OAuth codes must never enter the conversation context. -- **Never** ask the user to send you these values, and never put them in command arguments, script source, output files or replies. -- For setup, ask the user to run the script's `--setup` **in their own terminal** (plus `--login` for Lark private chats). Secrets are typed with hidden input and stored in the OS keychain (when `keyring` is installed) or in `~/.distilly/*_config.json` (mode 0600). The user may instead export environment variables (`FEISHU_APP_ID` / `FEISHU_APP_SECRET` / `FEISHU_USER_ACCESS_TOKEN` / `DINGTALK_APP_KEY` / `DINGTALK_APP_SECRET` / `SLACK_BOT_TOKEN`). `--setup` / `--login` refuse to run without an interactive terminal, so you cannot run them on the user's behalf. -- Use `--check` to see whether credentials are ready; it shows only the source (env / keychain / config file), never the values. -- If you write your own script against the APIs, load credentials from environment variables or `credentials.resolve(...)` in `tools/credentials.py`, and never print them. -- If the user pastes a secret into the chat anyway: do not repeat or use it, tell them to rotate it on the open platform, then reconfigure as above. - -The Lark-labelled compatibility collectors currently connect to the China-region `open.feishu.cn` / `feishu.cn` endpoints. International `larksuite.com` tenant routing is not implemented yet. - ---- - -## Main Flow: Create a New Skill - -### Step 0: Confirm the character family - -If the user entered `/distilly`, first confirm which family should be distilled: - -1. `owner`: the user themself (the default on a personal agent's first run) -2. `colleague` -3. `relationship` -4. `celebrity` - -For `owner` (including "distill myself"), do not continue with Steps 1-5 below; use the "Owner Profile" flow instead. - -If the host already passed an explicit family, lock the character family immediately. - -If the current family is `celebrity`, also confirm the research profile: - -1. `budget-friendly` -2. `budget-unfriendly` - -Default to `budget-friendly`. Only switch to `budget-unfriendly` when the user explicitly wants deeper research, higher confidence, or accepts a slower and more expensive distillation pass. - -### Step 1: Basic Info Collection - -Choose the intake prompt by character family: - -- `colleague` → `prompts/colleague/intake.md` -- `relationship` → `prompts/relationship/intake.md` -- `celebrity` → `prompts/celebrity/intake.md` - -For `colleague` and `relationship`, ask only 3 questions. -For `celebrity`, use the 4-question intake in `prompts/celebrity/intake.md`; the fourth question must confirm `research_profile`. - -The default 3 base questions are: - -1. **Alias / Codename** (required) -2. **Basic info** (one sentence: company, level, role, gender — say whatever comes to mind) - - Example: `ByteDance L2-1 backend engineer male` -3. **Personality profile** (one sentence: MBTI, zodiac, traits, corporate culture, impressions) - - Example: `INTJ Capricorn blame-shifter ByteDance-style strict in CR but never explains why` - -Everything except the alias can be skipped. Summarize and confirm before moving to the next step. - -### Step 2: Source Material Import - -Ask the user how they'd like to provide materials: - -``` -How would you like to provide source materials? - - [A] Lark Auto-Collect (recommended) - Enter name, auto-pull messages + docs + spreadsheets - - [B] DingTalk Auto-Collect - Enter name, auto-pull docs + spreadsheets - Messages collected via browser (DingTalk API doesn't support message history) - - [C] Lark Link - Provide doc/Wiki link (browser session or MCP) - - [D] Upload Files - PDF / images / exported JSON / email .eml - - [E] Paste Text - Copy-paste text directly - -Can mix and match, or skip entirely (generate from manual info only). -``` - ---- - -#### Option A: Lark Auto-Collect (Recommended) - -First-time setup. Check whether credentials are ready: -```bash -python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" --check -``` -If not, ask the user to run this **in their own terminal** (do not run it yourself, and do not ask for the App Secret in chat): -```bash -python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" --setup -``` - -**Group chat collection** (uses tenant_access_token, bot must be in the group): -```bash -python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" \ - --name "{name}" \ - --output-dir ./knowledge/{slug} \ - --msg-limit 1000 \ - --doc-limit 20 -``` - -**Private chat (P2P) collection** (requires user_access_token + p2p chat_id): - -Private messages can only be accessed via user identity (user_access_token). App identity cannot access private chats. - -**Prerequisites** (the user completes these on the Open Platform and in their own terminal; you only guide): -1. **Lark app credentials**: `app_id` / `app_secret` configured via `--setup` or environment variables (confirm with `--check`) -2. **User scopes**: The app must have these user scopes enabled: - - `im:message` — read/send messages as user - - `im:chat` — read chat list as user -3. **Redirect URL**: add `http://www.example.com` under the app's "Security Settings → Redirect URLs" - -If the user is missing any of these, guide them through setup. Don't assume anything is pre-configured. - -**Getting user_access_token**: ask the user to run this **in their own terminal**: -```bash -python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" --login -``` -The script prints the authorization URL; after authorizing in the browser, the user pastes the callback URL into the terminal (hidden input) and the token goes straight into the keychain or config file. Never ask the user to send the code or token in chat. - -When the token expires (about 2 hours), you can refresh it yourself with no secret input: -```bash -python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" --refresh -``` - -**Getting the p2p chat_id**: - -Users typically don't know their chat_id. The user can open the private chat in Lark and find the `oc_` chat_id in the chat link or developer tools. If you write a script to obtain it, the script must read the token from `credentials.resolve("feishu", ...)` or environment variables and must not print it: - -- **Method**: Send a message to the other user's open_id — the response includes the chat_id (this really sends a message, so ask the user first) - ```python - POST https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id - Header: Authorization: Bearer - Body: {"receive_id": "{target_open_id}", "msg_type": "text", "content": "{\"text\":\"hello\"}"} - # The chat_id in the response is the p2p chat ID - ``` -- **Important**: `GET /im/v1/chats` does NOT return p2p chats — this is an API limitation, not a permission issue. Do not try to use it for finding private chats. -- If the user doesn't know the target's open_id, use tenant_access_token to search contacts: - ```python - GET https://open.feishu.cn/open-apis/contact/v3/scopes - # Returns open_ids of all users visible to the app - ``` - -**Running collection**: - -Once credentials are ready and you have the chat_id (the script loads the token itself; it never appears on the command line): -```bash -python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" \ - --open-id {target_open_id} \ - --p2p-chat-id {chat_id} \ - --name "{name}" \ - --output-dir ./knowledge/{slug} \ - --msg-limit 1000 -``` - -**Flexibility principle**: The above API calls don't have to go through the collector script. If the script doesn't work or doesn't fit the scenario, write Python scripts directly against the same endpoints, but load credentials through `credentials.resolve("feishu", feishu_auto_collector.load_config())` or environment variables; they must never appear in script source, command arguments or output. Key API reference: -- Get token: `POST /auth/v3/tenant_access_token/internal` (wrapped by `get_tenant_token` in the collector) -- Send message (get chat_id): `POST /im/v1/messages?receive_id_type=open_id` -- Fetch messages: `GET /im/v1/messages?container_id_type=chat&container_id={chat_id}` -- Search contacts: `GET /contact/v3/scopes`, `GET /contact/v3/users/{user_id}` - -Auto-collected content: -- Group chats: messages sent by them (system messages and stickers filtered) -- Private chats: full conversation with both parties (for context understanding) -- Lark docs and Wikis they created/edited -- Related spreadsheets (if accessible) - -After collection, `Read` the output files: -- `knowledge/{slug}/messages.txt` → messages (group + private) -- `knowledge/{slug}/docs.txt` → document content -- `knowledge/{slug}/collection_summary.json` → collection summary - -If collection fails, diagnose the error and attempt to fix it. Common issues: -- Group chat: bot not added to the group -- Private chat: user_access_token expired (2-hour TTL; run `--refresh`, or ask the user to re-run `--login` in their own terminal if there is no refresh_token) -- Insufficient permissions: guide user to enable scopes and re-authorize -- Or switch to Option B/C - ---- - -#### Option B: DingTalk Auto-Collect - -First-time setup. Run `--check` first; if not configured, ask the user to run `--setup` **in their own terminal** (hidden AppSecret input), or to export `DINGTALK_APP_KEY` / `DINGTALK_APP_SECRET`. Never ask for the AppSecret in chat: -```bash -python3 "{distilly_skill_root}/tools/dingtalk_auto_collector.py" --check -python3 "{distilly_skill_root}/tools/dingtalk_auto_collector.py" --setup # run by the user -``` - -Then enter the name: -```bash -python3 "{distilly_skill_root}/tools/dingtalk_auto_collector.py" \ - --name "{name}" \ - --output-dir ./knowledge/{slug} \ - --msg-limit 500 \ - --doc-limit 20 \ - --show-browser # add this flag on first use to complete DingTalk login -``` - -Collected content: -- DingTalk docs and knowledge bases they created/edited -- Spreadsheets -- Messages (⚠️ DingTalk API doesn't support message history — auto-switches to browser scraping) - -After collection, `Read`: -- `knowledge/{slug}/docs.txt` -- `knowledge/{slug}/bitables.txt` -- `knowledge/{slug}/messages.txt` - -If message collection fails, prompt user to upload chat screenshots. - ---- - -#### Option D: Upload Files - -- **PDF / Images**: `Read` tool directly -- **Lark message JSON export**: - ```bash - python3 "{distilly_skill_root}/tools/feishu_parser.py" --file {path} --target "{name}" --output /tmp/feishu_out.txt - ``` - Then `Read /tmp/feishu_out.txt` -- **Email files .eml / .mbox**: - ```bash - python3 "{distilly_skill_root}/tools/email_parser.py" --file {path} --target "{name}" --output /tmp/email_out.txt - ``` - Then `Read /tmp/email_out.txt` -- **Markdown / TXT**: `Read` tool directly - ---- - -#### Option C: Lark Link - -When the user provides a Lark doc/Wiki link, ask which method to use: - -``` -Lark link detected. Choose read method: - - [1] Browser Method (recommended) - Reuses your local Chrome login session - ✅ Works with internal docs requiring permissions - ✅ No token configuration needed - ⚠️ Requires Chrome + playwright installed locally - - [2] MCP Method - Uses a Lark App Token via the official API - ✅ Stable, no browser dependency - ✅ Can read messages (needs chat ID) - ⚠️ Requires App ID / App Secret setup - ⚠️ Internal docs need admin authorization for the app - -Choose [1/2]: -``` - -**Option 1 (Browser)**: -```bash -python3 "{distilly_skill_root}/tools/feishu_browser.py" \ - --url "{feishu_url}" \ - --target "{name}" \ - --output /tmp/feishu_doc_out.txt -``` -First use will open a browser window for login (one-time). - -**Option 2 (MCP)**: - -First-time setup (shares Lark credentials with Option A). Run `--check` first; if not configured, ask the user to run `--setup` **in their own terminal**: -```bash -python3 "{distilly_skill_root}/tools/feishu_mcp_client.py" --check -python3 "{distilly_skill_root}/tools/feishu_mcp_client.py" --setup # run by the user -``` - -Then read directly: -```bash -python3 "{distilly_skill_root}/tools/feishu_mcp_client.py" \ - --url "{feishu_url}" \ - --output /tmp/feishu_doc_out.txt -``` - -Read messages (needs chat ID, format `oc_xxx`): -```bash -python3 "{distilly_skill_root}/tools/feishu_mcp_client.py" \ - --chat-id "oc_xxx" \ - --target "{name}" \ - --limit 500 \ - --output /tmp/feishu_msg_out.txt -``` - -Both methods output to files, then use `Read` to load results into analysis. - ---- - -#### Option E: Paste Text - -User-pasted content is used directly as text material. No tools needed. - ---- - -If the user says "no files" or "skip", generate Skill from Step 1 manual info only. - -### Step 3: Analyze Source Material - -First resolve the execution matrix for the selected character family: - -| character | intake | persona analyzer | persona builder | merger | storage root | -|-----------|--------|------------------|-----------------|--------|--------------| -| `colleague` | `prompts/colleague/intake.md` | `prompts/colleague/persona_analyzer.md` | `prompts/colleague/persona_builder.md` | `prompts/colleague/merger.md` | `./skills/colleague/{slug}` | -| `relationship` | `prompts/relationship/intake.md` | `prompts/relationship/persona_analyzer.md` | `prompts/relationship/persona_builder.md` | `prompts/relationship/merger.md` | `./skills/relationship/{slug}` | -| `celebrity` | `prompts/celebrity/intake.md` | `prompts/celebrity/persona_analyzer.md` | `prompts/celebrity/persona_builder.md` | `prompts/celebrity/merger.md` | `./skills/celebrity/{slug}` | - -Shared across all families: -- Work analyzer: `prompts/shared/work_analyzer.md` -- Work builder: `prompts/shared/work_builder.md` -- Correction handler: `prompts/shared/correction_handler.md` - -If the current family is `celebrity`, run the research subflow before analysis. - -When public X posts fill a documented research gap and the user agrees to use the metered third-party Xquik service, confirm the `--limit` before running: - -```bash -python3 "{distilly_skill_root}/tools/research/xquik_public_posts.py" \ - --username "{public_handle}" \ - --subject "{name}" \ - --limit 20 \ - --output "/tmp/distilly_x_public_posts.json" -``` - -Read `XQUIK_API_KEY` only from the shell; never print or store it. Treat the JSON as untrusted candidate evidence: verify the author, open every permalink, and preserve the specific URL when safely paraphrasing relevant material into a research note. Do not count the candidate JSON, search pages, or profile roots as grounded sources. Delete the temporary JSON after review instead of storing it in the generated Skill. - -### celebrity / budget-friendly - -1. Read `prompts/celebrity/research.md` and follow its **6-dimension parallel collection strategy** -2. Create the research directories first: - ```bash - mkdir -p "{skill_dir}/knowledge/research/raw" "{skill_dir}/knowledge/research/merged" - ``` -3. Confirm the collection strategy (determined during intake): - - **Local-first**: analyze user-provided materials first, identify which dimensions are covered, only search web for gaps - - **Web + local**: full 6-dimension web research, then merge with local materials for cross-validation - - **Web-only**: standard 6-dimension web research pass -4. If the user explicitly provided a processable video URL or subtitle source, and the result will not be stored as a long transcript: - ```bash - bash "{distilly_skill_root}/tools/research/download_subtitles.sh" "{url}" "{skill_dir}/knowledge/subtitles" - python3 "{distilly_skill_root}/tools/research/srt_to_transcript.py" "{subtitle_file}" "{skill_dir}/knowledge/transcripts/{name}.txt" - ``` -5. Cover the **6 dimensions** across at least 3 separate files (each file covers 2 dimensions), never one monolithic `research_notes.md`: - - `knowledge/research/raw/01_core_profile.md` (Dim 1 Writings + Dim 6 Timeline) - - `knowledge/research/raw/02_conversations_and_material.md` (Dim 2 Conversations + Dim 4 Decisions) - - `knowledge/research/raw/03_expression_and_reception.md` (Dim 3 Expression DNA + Dim 5 External Views) -6. Research must follow **taste principles** (see research prompt): - - Long-form > snippets, controversy > consensus, change > fixity, firsthand > secondhand - - **Source blacklist** — never cite: Zhihu, WeChat official accounts, Baidu Baike, content farms, AI-generated bios - - **Source hierarchy**: user local materials > first-person works > long interviews > decision records > short-form firsthand > external analysis > secondhand summaries -7. Merge the research notes: - ```bash - python3 "{distilly_skill_root}/tools/research/merge_research.py" "{skill_dir}" - ``` - Output: `knowledge/research/merged/summary.md` -8. Read `knowledge/research/merged/summary.md` and confirm: - - `Files scanned >= 3` - - `Unique URLs >= 2` - - `Potential long quote lines = 0` - - URLs in notes are actual inspected pages, not platform roots, search/topic pages, or placeholder paths - If these do not hold, extend the research notes before continuing or explicitly record the collection limits. -9. **Quality checkpoint (Phase 1.5)**: before entering analysis, show the user a structured collection summary: - ``` - ┌──────────────────────────────┬──────────┬─────────────────────────────┐ - │ Dimension │ Sources │ Key Finding │ - ├──────────────────────────────┼──────────┼─────────────────────────────┤ - │ 1 Writings │ N │ [core thesis / gap] │ - │ 2 Conversations │ N │ [key pattern / gap] │ - │ 3 Expression DNA │ N │ [style marker / gap] │ - │ 4 Decisions │ N │ [decision pattern / gap] │ - │ 5 External Views │ N │ [outside view / gap] │ - │ 6 Timeline │ N │ [trajectory / gap] │ - ├──────────────────────────────┼──────────┼─────────────────────────────┤ - │ Contradictions │ N │ [summary] │ - │ Thin dimensions │ [list] │ Backfill plan: [plan] │ - │ Cold figure? │ yes/no │ │ - └──────────────────────────────┴──────────┴─────────────────────────────┘ - ``` - Wait for user confirmation before continuing. If the user flags issues or wants more depth, extend research first. -10. **Cold figure detection**: if total sources < 10, apply the cold figure protocol: - - Limit mental models to 2–3 - - Mark thin models as "based on limited information" - - Expand the honest boundaries section - - Tell the user what additional material would improve quality -11. Celebrity analysis must prioritize: - - primary materials (source weight 1-3) - - merged research summary - - explicit user notes - -### celebrity / budget-unfriendly - -1. First read: - - `prompts/celebrity/budget_unfriendly/research.md` - - `prompts/celebrity/budget_unfriendly/framework.md` -2. Create the research directories first: - ```bash - mkdir -p "{skill_dir}/knowledge/research/raw" "{skill_dir}/knowledge/research/merged" "{skill_dir}/knowledge/research/reviews" - ``` -3. Confirm the collection strategy (determined during intake): local-first / web+local / web-only -4. Build the **six-track research set** as independent files (never merged, never clone observations): - - `knowledge/research/raw/01_writings.md` (Dim 1: Writings / systematic thought) - - `knowledge/research/raw/02_conversations.md` (Dim 2: Conversations under pressure) - - `knowledge/research/raw/03_expression_dna.md` (Dim 3: Linguistic fingerprint) - - `knowledge/research/raw/04_decisions.md` (Dim 4: Behavior and choices) - - `knowledge/research/raw/05_external_views.md` (Dim 5: External views and criticism) - - `knowledge/research/raw/06_timeline.md` (Dim 6: Cognitive trajectory) -5. Research must follow **taste principles + source blacklist + source hierarchy** (see research prompt). Every evidence item must carry a source weight (1-7) annotation. -6. Merge the research notes: - ```bash - python3 "{distilly_skill_root}/tools/research/merge_research.py" "{skill_dir}" - ``` -7. Read `knowledge/research/merged/summary.md` and confirm the minimum floor: - - `Files scanned >= 6` - - `Unique URLs >= 8` - - `Primary-source markers >= 3` - - `Source metadata blocks >= 6` - - `Contradiction bullets >= 6` - - `Inference bullets >= 6` - - `Potential long quote lines = 0` - - `Track coverage count = 6` - - URLs in notes are actual inspected pages, not platform roots, search/topic pages, or placeholder paths - If these do not hold, keep filling the weak tracks before continuing to any review stage. -8. **Quality checkpoint (Phase 1.5)**: before entering audit, show the user a structured collection summary (with primary-source ratio, contradiction count, candidate mental models, known-answer candidates, thin dimensions, cold figure assessment). Wait for user confirmation before continuing. -9. Then read: - - `prompts/celebrity/budget_unfriendly/audit.md` - - `prompts/celebrity/budget_unfriendly/synthesis.md` - - `prompts/celebrity/budget_unfriendly/template.md` -10. First write `knowledge/research/reviews/research_audit.md` - - The audit must produce an explicit `PASS / FAIL` - - The audit must verify: source hierarchy compliance (no blacklisted sources), primary-source ratio > 50%, taste principle compliance, cold figure assessment - - If the audit says `FAIL`, follow the Backfill Tasks before synthesis -11. **Extraction checkpoint (Phase 2.5)**: after audit PASS, show the user a summary of candidate mental models (with triple-gate verdict, evidence anchors, failure modes). Confirm reasonableness before synthesis. -12. Then write `knowledge/research/reviews/synthesis.md` - - Apply the triple gate to candidate mental models: - - cross-context recurrence - - generative power - - exclusivity - - Also extract intellectual genealogy seeds (influenced by / diverged from) and Agentic Protocol seeds (the dimensions this person would investigate when facing a novel question) -13. Then use `prompts/celebrity/budget_unfriendly/validation.md` to write: - - `knowledge/research/reviews/validation.md` - - Validation must produce an explicit `PASS / FAIL` - - Validation must perform: known-answer check (≥2 questions) + edge-case check (1 question) + voice check (100-word blind test) + copyright check + Agentic Protocol check - - If validation says `FAIL`, revise the draft before continuing -14. Budget-unfriendly celebrity analysis must prioritize: - - six-track raw notes - - merged research summary - - research audit - - synthesis review (with genealogy + Agentic Protocol seeds) - - validation review - - explicit user notes - -Shared rules for both celebrity profiles: - -- If external collection fails or a platform blocks access: - - tell the user exactly what was blocked - - preserve the raw research notes and merged summary - - continue generation with the available materials - - treat `source_grounding` as incomplete - - **never** invent URLs, quotes, titles, or generic homepage links just to satisfy the checker -- **Do not** store full transcripts, full subtitles, or long verbatim source passages in the repository -- Keep the stored notes paraphrased, structured, and copyright-safe - -Once the family is resolved, analyze along two tracks: - -**Track A (Work Skill)**: -- Refer to `prompts/shared/work_analyzer.md` -- Extract: responsible systems, technical standards, workflow, output preferences, experience -- For `celebrity`, interpret `work` as methods, judgment frameworks, and decision patterns rather than literal job scope - -**Track B (Persona)**: -- Use the family-specific persona analyzer -- If `celebrity` with `research_profile=budget-unfriendly`, use: - - `prompts/celebrity/budget_unfriendly/persona_analyzer.md` -- Translate user-provided tags into concrete behavior rules -- Extract from materials: communication style, decision patterns, interpersonal behavior -- For `celebrity`, retain: - - mental models - - decision heuristics - - expression DNA - - contradictions - - honest boundaries - -### Step 4: Generate and Preview - -Use `prompts/shared/work_builder.md` to generate Work content. -Use the family-specific persona builder to generate Persona content. - -Mapping: -- `colleague` → `prompts/colleague/persona_builder.md` -- `relationship` → `prompts/relationship/persona_builder.md` -- `celebrity` → `prompts/celebrity/persona_builder.md` -- `celebrity` + `budget-unfriendly` → `prompts/celebrity/budget_unfriendly/persona_builder.md` - -Show the user a summary (5-8 lines each), ask: -``` -Work Skill Summary: - - Responsible for: {xxx} - - Tech stack: {xxx} - - CR focus: {xxx} - ... - -Persona Summary: - - Core personality: {xxx} - - Communication style: {xxx} - - Decision pattern: {xxx} - ... - -Confirm generation? Or need adjustments? -``` - -### Step 5: Write Files - -After user confirmation, do not hand-build a `skills/colleague/{slug}`-style tree. Always go through the writer: - -1. Resolve the current storage root: - - `colleague` → `./skills/colleague` - - `relationship` → `./skills/relationship` - - `celebrity` → `./skills/celebrity` -2. Use the `Write` tool to create three temporary files: - - `/tmp/distilly_{slug}_meta.json` - - `/tmp/distilly_{slug}_work.md` - - `/tmp/distilly_{slug}_persona.md` -3. The temporary meta file must include at least: - - `name` - - `display_name` - - `character` - - `research_profile` (required when `character=celebrity`) - - `classification.language` (must match the user's language, for example `zh-CN` or `en`) - - `profile` - - `tags` - - `knowledge_sources` -4. Then call: - ```bash - python3 "{distilly_skill_root}/tools/skill_writer.py" \ - --action create \ - --character {character} \ - --research-profile {research_profile} \ - --slug {slug} \ - --name "{name}" \ - --meta /tmp/distilly_{slug}_meta.json \ - --work /tmp/distilly_{slug}_work.md \ - --persona /tmp/distilly_{slug}_persona.md \ - --base-dir {resolved_base_dir} - ``` -5. This command will generate: - - `SKILL.md` - - `work.md` - - `persona.md` - - `work_skill.md` - - `persona_skill.md` - - `manifest.json` - - `meta.json` - - To install the generated role skill into a host, append the relevant flag: - - Claude Code: `--install-claude-skill` - - OpenClaw: `--install-openclaw-skill` - - Codex: `--install-codex-skill` - - Hermes: run `python3 "{distilly_skill_root}/tools/install_generated_skill.py" --skill-dir "{resolved_base_dir}/{slug}" --host hermes --force`; for a trusted project, append `--skills-dir .hermes/skills`, run `hermes skills trust`, then start a new session or run `/reload-skills`. Use `~/.agents/skills` only when it is explicitly configured in Hermes `skills.external_dirs` - - DeepSeek Harness: run `python3 "{distilly_skill_root}/tools/install_generated_skill.py" --skill-dir "{resolved_base_dir}/{slug}" --host deepseek-harness --force`; append `--skills-dir .dsh/skills` for a project install - - Pi: run `python3 "{distilly_skill_root}/tools/install_generated_skill.py" --skill-dir "{resolved_base_dir}/{slug}" --host pi --force`; append `--skills-dir .pi/skills` for a project install, then invoke it with `/skill:{character}-{slug}` - - Grok Build: run `python3 "{distilly_skill_root}/tools/install_generated_skill.py" --skill-dir "{resolved_base_dir}/{slug}" --host grok-build --force`; append `--skills-dir .grok/skills` for a project install - - OpenCode: run `python3 "{distilly_skill_root}/tools/install_generated_skill.py" --skill-dir "{resolved_base_dir}/{slug}" --host opencode --force`; append `--skills-dir .opencode/skills` for a project install - - The shared installer writes only the self-contained `SKILL.md` and install metadata and normalizes legacy frontmatter in the installed copy. Do not manually copy the whole generated directory; it may contain private source material - - Claude Code on Windows: optionally add `--install-claude-command-shim` -6. If the current family is `celebrity`, run a quality check after creation: - ```bash - python3 "{distilly_skill_root}/tools/research/quality_check.py" "{resolved_base_dir}/{slug}/SKILL.md" --profile {research_profile} - ``` -7. If `source_grounding` still fails for a `celebrity` skill: - - you may add honest limitation notes and a grounded source summary - - only add URLs when they are real, specific, and traceable sources - - **never** use site roots, topic pages, search pages, or other generic links as fake grounding - - if no verified external sources exist, keep the FAIL state and explain what source material is still missing - -When reporting success, return the correct family-specific location instead of assuming colleague storage. - ---- - -## Owner Profile - -Build a profile of the agent's owner so the agent knows who it works for from the first session. It is **context** the agent reads every session, not a role-play Skill: do not generate a `{character}-{slug}` Skill and never write "you are ". - -1. Gather information with `{distilly_skill_root}/prompts/owner/intake.md`: first **discover** the sources this agent already has (the tools, connectors and memory in this session, the platform the agent runs on such as Grok's X account, and the user files, connector configs and exports that `tools/owner_sources.py discover` finds on this machine) instead of walking a fixed list; propose which to read, ask the user which to use and whether to add anything else; read only what they pick, read-only; then ask only the questions the sources did not answer. Use only the host's own tools; never ask for API keys or OAuth setup on this path, and do not use the Lark, DingTalk or Slack collectors here. -2. Build the profile with `{distilly_skill_root}/prompts/owner/profile_builder.md`, write it to `/tmp/distilly_owner_profile.md`, show the full text to the user and apply their corrections. -3. After the user approves, save it: - ```bash - python3 "{distilly_skill_root}/tools/owner_profile.py" save --profile /tmp/distilly_owner_profile.md - ``` -4. Link it into the current host's always-on user file. Show the `--dry-run` result first and write only after the user agrees: - ```bash - python3 "{distilly_skill_root}/tools/owner_profile.py" link --host claude-code --dry-run - python3 "{distilly_skill_root}/tools/owner_profile.py" link --host claude-code - ``` - Claude Code uses `~/.claude/CLAUDE.md`; OpenClaw (`--host openclaw`) uses the workspace `USER.md`. Other hosts are not supported yet; tell the user the full profile is at `~/.distilly/owner/profile.md`. -5. Only the text between the Distilly markers changes; everything else in the file is left alone and the previous file is backed up. Delete `/tmp/distilly_owner_profile.md` afterwards. - -Updates: when the user corrects something or adds information, edit the full profile, then run `save` and `link` again. To undo: `owner_profile.py unlink --host `. - ---- - -## Evolution Mode: Append Files - -When user provides new files or text: - -1. Read new content using Step 2 methods -2. Resolve the base dir for the current family -3. `Read` existing `{resolved_base_dir}/{slug}/work.md` and `persona.md` -4. Use the family-specific merger prompt for incremental analysis -5. Archive current version (Bash): - ```bash - python3 "{distilly_skill_root}/tools/version_manager.py" \ - --action backup \ - --character {character} \ - --slug {slug} \ - --base-dir {resolved_base_dir} - ``` -6. Write work/persona delta into temporary patch files -7. Call: - ```bash - python3 "{distilly_skill_root}/tools/skill_writer.py" \ - --action update \ - --character {character} \ - --slug {slug} \ - --work-patch /tmp/distilly_{slug}_work_patch.md \ - --persona-patch /tmp/distilly_{slug}_persona_patch.md \ - --base-dir {resolved_base_dir} - ``` -8. If the current family is `celebrity`, run the quality check again after the update - ---- - -## Evolution Mode: Conversation Correction - -When user expresses "that's wrong" / "he should be": - -1. Refer to `prompts/shared/correction_handler.md` to identify correction content -2. Determine if it belongs to Work (technical/workflow) or Persona (personality/communication) -3. If it belongs to Work: - - Generate `/tmp/distilly_{slug}_work_patch.md` - - The patch must be one or more replaceable `##` sections - - Call: - ```bash - python3 "{distilly_skill_root}/tools/skill_writer.py" \ - --action update \ - --character {character} \ - --slug {slug} \ - --work-patch /tmp/distilly_{slug}_work_patch.md \ - --base-dir {resolved_base_dir} - ``` -4. If it belongs to Persona: - - Write the correction record to `/tmp/distilly_{slug}_correction.json` - - For a single correction, write `{scene, wrong, correct}` - - For multiple persona corrections, write `{"persona_corrections": [{...}, {...}]}` - - Call: - ```bash - python3 "{distilly_skill_root}/tools/skill_writer.py" \ - --action update \ - --character {character} \ - --slug {slug} \ - --correction-json /tmp/distilly_{slug}_correction.json \ - --base-dir {resolved_base_dir} - ``` -5. If the current family is `celebrity`, run the quality check again after the update -6. Do not hand-edit `work.md`, `persona.md`, `SKILL.md`, or `meta.json`; always update through `skill_writer.py` - ---- - -## Management Operations - -List skills across the three families: -```bash -python3 "{distilly_skill_root}/tools/skill_writer.py" --action list --character colleague --base-dir ./skills/colleague -python3 "{distilly_skill_root}/tools/skill_writer.py" --action list --character relationship --base-dir ./skills/relationship -python3 "{distilly_skill_root}/tools/skill_writer.py" --action list --character celebrity --base-dir ./skills/celebrity -``` - -Roll back a specific skill version: -```bash -# colleague -python3 "{distilly_skill_root}/tools/version_manager.py" --action rollback --character colleague --slug {slug} --version {version} --base-dir ./skills/colleague - -# relationship -python3 "{distilly_skill_root}/tools/version_manager.py" --action rollback --character relationship --slug {slug} --version {version} --base-dir ./skills/relationship - -# celebrity -python3 "{distilly_skill_root}/tools/version_manager.py" --action rollback --character celebrity --slug {slug} --version {version} --base-dir ./skills/celebrity -``` - -Delete a specific skill: -After confirming the character family: -```bash -# colleague -rm -rf skills/colleague/{slug} - -# relationship -rm -rf skills/relationship/{slug} - -# celebrity -rm -rf skills/celebrity/{slug} -``` +Claude Code, OpenClaw, Hermes, Codex, DeepSeek Harness, Pi coding agent, Grok Build and OpenCode. Invoke with `/distilly` in Claude Code, Hermes, DeepSeek Harness and Grok Build; `/distilly` or `/skill distilly` in OpenClaw; `$distilly` or `/skills` in Codex; `/skill:distilly` in Pi. OpenCode loads Skills natively and has no dedicated command. Grok Bot cannot import this directory directly; see `INSTALL_EN.md`. diff --git a/prompts/flows/celebrity_research.md b/prompts/flows/celebrity_research.md new file mode 100644 index 00000000..d731cb81 --- /dev/null +++ b/prompts/flows/celebrity_research.md @@ -0,0 +1,150 @@ +# Celebrity Research + +Runs before Step 3 analysis in `prompts/flows/create.md` when the family is `celebrity`. Follow the section for the chosen `research_profile`. Resolve every `prompts/...` and `tools/...` path against `{distilly_skill_root}` (see `SKILL.md`). + +## celebrity / budget-friendly + +1. Read `prompts/celebrity/research.md` and follow its **6-dimension parallel collection strategy** +2. Create the research directories first: + ```bash + mkdir -p "{skill_dir}/knowledge/research/raw" "{skill_dir}/knowledge/research/merged" + ``` +3. Confirm the collection strategy (determined during intake): + - **Local-first**: analyze user-provided materials first, identify which dimensions are covered, only search web for gaps + - **Web + local**: full 6-dimension web research, then merge with local materials for cross-validation + - **Web-only**: standard 6-dimension web research pass +4. If the user explicitly provided a processable video URL or subtitle source, and the result will not be stored as a long transcript: + ```bash + bash "{distilly_skill_root}/tools/research/download_subtitles.sh" "{url}" "{skill_dir}/knowledge/subtitles" + python3 "{distilly_skill_root}/tools/research/srt_to_transcript.py" "{subtitle_file}" "{skill_dir}/knowledge/transcripts/{name}.txt" + ``` +5. Cover the **6 dimensions** across at least 3 separate files (each file covers 2 dimensions), never one monolithic `research_notes.md`: + - `knowledge/research/raw/01_core_profile.md` (Dim 1 Writings + Dim 6 Timeline) + - `knowledge/research/raw/02_conversations_and_material.md` (Dim 2 Conversations + Dim 4 Decisions) + - `knowledge/research/raw/03_expression_and_reception.md` (Dim 3 Expression DNA + Dim 5 External Views) +6. Follow the taste principles, source blacklist and source hierarchy in `prompts/celebrity/research.md`. +7. Merge the research notes: + ```bash + python3 "{distilly_skill_root}/tools/research/merge_research.py" "{skill_dir}" + ``` + Output: `knowledge/research/merged/summary.md` +8. Read `knowledge/research/merged/summary.md` and confirm: + - `Files scanned >= 3` + - `Unique URLs >= 2` + - `Potential long quote lines = 0` + - URLs in notes are actual inspected pages, not platform roots, search/topic pages, or placeholder paths + If these do not hold, extend the research notes before continuing or explicitly record the collection limits. +9. **Quality checkpoint (Phase 1.5)**: before entering analysis, show the user a structured collection summary: + ``` + ┌──────────────────────────────┬──────────┬─────────────────────────────┐ + │ Dimension │ Sources │ Key Finding │ + ├──────────────────────────────┼──────────┼─────────────────────────────┤ + │ 1 Writings │ N │ [core thesis / gap] │ + │ 2 Conversations │ N │ [key pattern / gap] │ + │ 3 Expression DNA │ N │ [style marker / gap] │ + │ 4 Decisions │ N │ [decision pattern / gap] │ + │ 5 External Views │ N │ [outside view / gap] │ + │ 6 Timeline │ N │ [trajectory / gap] │ + ├──────────────────────────────┼──────────┼─────────────────────────────┤ + │ Contradictions │ N │ [summary] │ + │ Thin dimensions │ [list] │ Backfill plan: [plan] │ + │ Cold figure? │ yes/no │ │ + └──────────────────────────────┴──────────┴─────────────────────────────┘ + ``` + Wait for user confirmation before continuing. If the user flags issues or wants more depth, extend research first. +10. **Cold figure detection**: if total sources < 10, apply the cold figure protocol: + - Limit mental models to 2–3 + - Mark thin models as "based on limited information" + - Expand the honest boundaries section + - Tell the user what additional material would improve quality +11. Celebrity analysis must prioritize: + - primary materials (source weight 1-3) + - merged research summary + - explicit user notes + +## celebrity / budget-unfriendly + +1. First read: + - `prompts/celebrity/budget_unfriendly/research.md` + - `prompts/celebrity/budget_unfriendly/framework.md` +2. Create the research directories first: + ```bash + mkdir -p "{skill_dir}/knowledge/research/raw" "{skill_dir}/knowledge/research/merged" "{skill_dir}/knowledge/research/reviews" + ``` +3. Confirm the collection strategy (determined during intake): local-first / web+local / web-only +4. Build the **six-track research set** as independent files (never merged, never clone observations): + - `knowledge/research/raw/01_writings.md` (Dim 1: Writings / systematic thought) + - `knowledge/research/raw/02_conversations.md` (Dim 2: Conversations under pressure) + - `knowledge/research/raw/03_expression_dna.md` (Dim 3: Linguistic fingerprint) + - `knowledge/research/raw/04_decisions.md` (Dim 4: Behavior and choices) + - `knowledge/research/raw/05_external_views.md` (Dim 5: External views and criticism) + - `knowledge/research/raw/06_timeline.md` (Dim 6: Cognitive trajectory) +5. Follow the taste principles, source blacklist and source hierarchy in `prompts/celebrity/budget_unfriendly/research.md`. Every evidence item must carry a source weight (1-7) annotation. +6. Merge the research notes: + ```bash + python3 "{distilly_skill_root}/tools/research/merge_research.py" "{skill_dir}" + ``` +7. Read `knowledge/research/merged/summary.md` and confirm the minimum floor: + - `Files scanned >= 6` + - `Unique URLs >= 8` + - `Primary-source markers >= 3` + - `Source metadata blocks >= 6` + - `Contradiction bullets >= 6` + - `Inference bullets >= 6` + - `Potential long quote lines = 0` + - `Track coverage count = 6` + - URLs in notes are actual inspected pages, not platform roots, search/topic pages, or placeholder paths + If these do not hold, keep filling the weak tracks before continuing to any review stage. +8. **Quality checkpoint (Phase 1.5)**: before entering audit, show the user a structured collection summary (with primary-source ratio, contradiction count, candidate mental models, known-answer candidates, thin dimensions, cold figure assessment). Wait for user confirmation before continuing. +9. Then read: + - `prompts/celebrity/budget_unfriendly/audit.md` + - `prompts/celebrity/budget_unfriendly/synthesis.md` + - `prompts/celebrity/budget_unfriendly/template.md` +10. First write `knowledge/research/reviews/research_audit.md` + - The audit must produce an explicit `PASS / FAIL` + - The audit must verify: source hierarchy compliance (no blacklisted sources), primary-source ratio > 50%, taste principle compliance, cold figure assessment + - If the audit says `FAIL`, follow the Backfill Tasks before synthesis +11. **Extraction checkpoint (Phase 2.5)**: after audit PASS, show the user a summary of candidate mental models (with triple-gate verdict, evidence anchors, failure modes). Confirm reasonableness before synthesis. +12. Then write `knowledge/research/reviews/synthesis.md` + - Apply the triple gate to candidate mental models: + - cross-context recurrence + - generative power + - exclusivity + - Also extract intellectual genealogy seeds (influenced by / diverged from) and Agentic Protocol seeds (the dimensions this person would investigate when facing a novel question) +13. Then use `prompts/celebrity/budget_unfriendly/validation.md` to write: + - `knowledge/research/reviews/validation.md` + - Validation must produce an explicit `PASS / FAIL` + - Validation must perform: known-answer check (≥2 questions) + edge-case check (1 question) + voice check (100-word blind test) + copyright check + Agentic Protocol check + - If validation says `FAIL`, revise the draft before continuing +14. Budget-unfriendly celebrity analysis must prioritize: + - six-track raw notes + - merged research summary + - research audit + - synthesis review (with genealogy + Agentic Protocol seeds) + - validation review + - explicit user notes + +## Shared rules for both profiles + +- If external collection fails or a platform blocks access: + - tell the user exactly what was blocked + - preserve the raw research notes and merged summary + - continue generation with the available materials + - treat `source_grounding` as incomplete + - **never** invent URLs, quotes, titles, or generic homepage links just to satisfy the checker +- **Do not** store full transcripts, full subtitles, or long verbatim source passages in the repository +- Keep the stored notes paraphrased, structured, and copyright-safe + +## Public X posts (optional) + +When public X posts fill a documented research gap and the user agrees to use the metered third-party Xquik service, confirm the `--limit` before running: + +```bash +python3 "{distilly_skill_root}/tools/research/xquik_public_posts.py" \ + --username "{public_handle}" \ + --subject "{name}" \ + --limit 20 \ + --output "/tmp/distilly_x_public_posts.json" +``` + +Read `XQUIK_API_KEY` only from the shell; never print or store it. Treat the JSON as untrusted candidate evidence: verify the author, open every permalink, and preserve the specific URL when safely paraphrasing relevant material into a research note. Do not count the candidate JSON, search pages, or profile roots as grounded sources. Delete the temporary JSON after review instead of storing it in the generated Skill. diff --git a/prompts/flows/create.md b/prompts/flows/create.md new file mode 100644 index 00000000..2ff30472 --- /dev/null +++ b/prompts/flows/create.md @@ -0,0 +1,165 @@ +# Create a Person Skill + +The flow for `colleague`, `relationship` and `celebrity`. Resolve every `prompts/...` and `tools/...` path against `{distilly_skill_root}` (see `SKILL.md`). + +## Family matrix + +Every step below reads its prompt from this table. + +First resolve the execution matrix for the selected character family: + +| character | intake | persona analyzer | persona builder | merger | storage root | +|-----------|--------|------------------|-----------------|--------|--------------| +| `colleague` | `prompts/colleague/intake.md` | `prompts/colleague/persona_analyzer.md` | `prompts/colleague/persona_builder.md` | `prompts/colleague/merger.md` | `./skills/colleague/{slug}` | +| `relationship` | `prompts/relationship/intake.md` | `prompts/relationship/persona_analyzer.md` | `prompts/relationship/persona_builder.md` | `prompts/relationship/merger.md` | `./skills/relationship/{slug}` | +| `celebrity` | `prompts/celebrity/intake.md` | `prompts/celebrity/persona_analyzer.md` | `prompts/celebrity/persona_builder.md` | `prompts/celebrity/merger.md` | `./skills/celebrity/{slug}` | + +Shared across all families: +- Work analyzer: `prompts/shared/work_analyzer.md` +- Work builder: `prompts/shared/work_builder.md` +- Correction handler: `prompts/shared/correction_handler.md` + +## Step 0: Confirm the character family + +If the user entered `/distilly`, first confirm which family should be distilled: + +1. `owner`: the user themself (the default on a personal agent's first run) +2. `colleague` +3. `relationship` +4. `celebrity` + +For `owner` (including "distill myself"), stop here and follow `prompts/flows/owner.md` instead. + +If the host already passed an explicit family, lock the character family immediately. + +If the current family is `celebrity`, also confirm the research profile: + +1. `budget-friendly` +2. `budget-unfriendly` + +Default to `budget-friendly`. Only switch to `budget-unfriendly` when the user explicitly wants deeper research, higher confidence, or accepts a slower and more expensive distillation pass. + + +## Step 1: Basic Info Collection + +Follow the family's intake prompt from the matrix above. It owns the questions, their order and the confirmation summary; for `celebrity` it also settles the collection strategy and `research_profile`. Everything except the alias can be skipped. Summarize and confirm before moving on. + +## Step 2: Source Material Import + +Follow `prompts/flows/sources.md` to offer the import options and read what the user provides. + +If the user says "no files" or "skip", generate the Skill from the Step 1 answers only. + +## Step 3: Analyze Source Material + +If the family is `celebrity`, first run `prompts/flows/celebrity_research.md` for the chosen research profile. + +Then analyze along two tracks: + +**Track A (Work Skill)**: +- Refer to `prompts/shared/work_analyzer.md` +- Extract: responsible systems, technical standards, workflow, output preferences, experience +- For `celebrity`, interpret `work` as methods, judgment frameworks, and decision patterns rather than literal job scope + +**Track B (Persona)**: +- Use the family-specific persona analyzer +- If `celebrity` with `research_profile=budget-unfriendly`, use: + - `prompts/celebrity/budget_unfriendly/persona_analyzer.md` +- Translate user-provided tags into concrete behavior rules +- Extract from materials: communication style, decision patterns, interpersonal behavior +- For `celebrity`, retain: + - mental models + - decision heuristics + - expression DNA + - contradictions + - honest boundaries + + +## Step 4: Generate and Preview + +Use `prompts/shared/work_builder.md` to generate Work content. +Use the family-specific persona builder to generate Persona content. +Use the family's persona builder from the matrix; `celebrity` + `budget-unfriendly` uses `prompts/celebrity/budget_unfriendly/persona_builder.md`. + +Show the user a summary (5-8 lines each), ask: +``` +Work Skill Summary: + - Responsible for: {xxx} + - Tech stack: {xxx} + - CR focus: {xxx} + ... + +Persona Summary: + - Core personality: {xxx} + - Communication style: {xxx} + - Decision pattern: {xxx} + ... + +Confirm generation? Or need adjustments? +``` + + +## Step 5: Write Files + +After user confirmation, do not hand-build a `skills/colleague/{slug}`-style tree. Always go through the writer: + +1. Resolve the current storage root: + - `colleague` → `./skills/colleague` + - `relationship` → `./skills/relationship` + - `celebrity` → `./skills/celebrity` +2. Use the `Write` tool to create three temporary files: + - `/tmp/distilly_{slug}_meta.json` + - `/tmp/distilly_{slug}_work.md` + - `/tmp/distilly_{slug}_persona.md` +3. The temporary meta file must include at least: + - `name` + - `display_name` + - `character` + - `research_profile` (required when `character=celebrity`) + - `classification.language` (must match the user's language, for example `zh-CN` or `en`) + - `profile` + - `tags` + - `knowledge_sources` +4. Then call: + ```bash + python3 "{distilly_skill_root}/tools/skill_writer.py" \ + --action create \ + --character {character} \ + --research-profile {research_profile} \ + --slug {slug} \ + --name "{name}" \ + --meta /tmp/distilly_{slug}_meta.json \ + --work /tmp/distilly_{slug}_work.md \ + --persona /tmp/distilly_{slug}_persona.md \ + --base-dir {resolved_base_dir} + ``` +5. This command will generate: + - `SKILL.md` + - `work.md` + - `persona.md` + - `work_skill.md` + - `persona_skill.md` + - `manifest.json` + - `meta.json` + - To install the generated role skill into a host, append the relevant flag: + - Claude Code: `--install-claude-skill` + - OpenClaw: `--install-openclaw-skill` + - Codex: `--install-codex-skill` + - Hermes: run `python3 "{distilly_skill_root}/tools/install_generated_skill.py" --skill-dir "{resolved_base_dir}/{slug}" --host hermes --force`; for a trusted project, append `--skills-dir .hermes/skills`, run `hermes skills trust`, then start a new session or run `/reload-skills`. Use `~/.agents/skills` only when it is explicitly configured in Hermes `skills.external_dirs` + - DeepSeek Harness: run `python3 "{distilly_skill_root}/tools/install_generated_skill.py" --skill-dir "{resolved_base_dir}/{slug}" --host deepseek-harness --force`; append `--skills-dir .dsh/skills` for a project install + - Pi: run `python3 "{distilly_skill_root}/tools/install_generated_skill.py" --skill-dir "{resolved_base_dir}/{slug}" --host pi --force`; append `--skills-dir .pi/skills` for a project install, then invoke it with `/skill:{character}-{slug}` + - Grok Build: run `python3 "{distilly_skill_root}/tools/install_generated_skill.py" --skill-dir "{resolved_base_dir}/{slug}" --host grok-build --force`; append `--skills-dir .grok/skills` for a project install + - OpenCode: run `python3 "{distilly_skill_root}/tools/install_generated_skill.py" --skill-dir "{resolved_base_dir}/{slug}" --host opencode --force`; append `--skills-dir .opencode/skills` for a project install + - The shared installer writes only the self-contained `SKILL.md` and install metadata and normalizes legacy frontmatter in the installed copy. Do not manually copy the whole generated directory; it may contain private source material + - Claude Code on Windows: optionally add `--install-claude-command-shim` +6. If the current family is `celebrity`, run a quality check after creation: + ```bash + python3 "{distilly_skill_root}/tools/research/quality_check.py" "{resolved_base_dir}/{slug}/SKILL.md" --profile {research_profile} + ``` +7. If `source_grounding` still fails for a `celebrity` skill: + - you may add honest limitation notes and a grounded source summary + - only add URLs when they are real, specific, and traceable sources + - **never** use site roots, topic pages, search pages, or other generic links as fake grounding + - if no verified external sources exist, keep the FAIL state and explain what source material is still missing + +When reporting success, return the correct family-specific location instead of assuming colleague storage. diff --git a/prompts/flows/evolve.md b/prompts/flows/evolve.md new file mode 100644 index 00000000..d69a8948 --- /dev/null +++ b/prompts/flows/evolve.md @@ -0,0 +1,66 @@ +# Evolve an Existing Skill + +Resolve every `prompts/...` and `tools/...` path against `{distilly_skill_root}` (see `SKILL.md`). + +## Append new material + +When user provides new files or text: + +1. Read new content with `prompts/flows/sources.md` +2. Resolve the base dir for the current family +3. `Read` existing `{resolved_base_dir}/{slug}/work.md` and `persona.md` +4. Use the family's merger prompt (matrix in `prompts/flows/create.md`) for incremental analysis +5. Archive current version (Bash): + ```bash + python3 "{distilly_skill_root}/tools/version_manager.py" \ + --action backup \ + --character {character} \ + --slug {slug} \ + --base-dir {resolved_base_dir} + ``` +6. Write work/persona delta into temporary patch files +7. Call: + ```bash + python3 "{distilly_skill_root}/tools/skill_writer.py" \ + --action update \ + --character {character} \ + --slug {slug} \ + --work-patch /tmp/distilly_{slug}_work_patch.md \ + --persona-patch /tmp/distilly_{slug}_persona_patch.md \ + --base-dir {resolved_base_dir} + ``` +8. If the current family is `celebrity`, run the quality check again after the update + +## Correct from conversation + +When user expresses "that's wrong" / "he should be": + +1. Refer to `prompts/shared/correction_handler.md` to identify correction content +2. Determine if it belongs to Work (technical/workflow) or Persona (personality/communication) +3. If it belongs to Work: + - Generate `/tmp/distilly_{slug}_work_patch.md` + - The patch must be one or more replaceable `##` sections + - Call: + ```bash + python3 "{distilly_skill_root}/tools/skill_writer.py" \ + --action update \ + --character {character} \ + --slug {slug} \ + --work-patch /tmp/distilly_{slug}_work_patch.md \ + --base-dir {resolved_base_dir} + ``` +4. If it belongs to Persona: + - Write the correction record to `/tmp/distilly_{slug}_correction.json` + - For a single correction, write `{scene, wrong, correct}` + - For multiple persona corrections, write `{"persona_corrections": [{...}, {...}]}` + - Call: + ```bash + python3 "{distilly_skill_root}/tools/skill_writer.py" \ + --action update \ + --character {character} \ + --slug {slug} \ + --correction-json /tmp/distilly_{slug}_correction.json \ + --base-dir {resolved_base_dir} + ``` +5. If the current family is `celebrity`, run the quality check again after the update +6. Do not hand-edit `work.md`, `persona.md`, `SKILL.md`, or `meta.json`; always update through `skill_writer.py` diff --git a/prompts/flows/manage.md b/prompts/flows/manage.md new file mode 100644 index 00000000..5493f2c3 --- /dev/null +++ b/prompts/flows/manage.md @@ -0,0 +1,18 @@ +# Manage Generated Skills + +Resolve every `tools/...` path against `{distilly_skill_root}` (see `SKILL.md`). `{character}` is `colleague`, `relationship` or `celebrity`; confirm it with the user before rolling back or deleting. + +List the skills of one family: +```bash +python3 "{distilly_skill_root}/tools/skill_writer.py" --action list --character {character} --base-dir ./skills/{character} +``` + +Roll back a specific skill version: +```bash +python3 "{distilly_skill_root}/tools/version_manager.py" --action rollback --character {character} --slug {slug} --version {version} --base-dir ./skills/{character} +``` + +Delete a specific skill: +```bash +rm -rf skills/{character}/{slug} +``` diff --git a/prompts/flows/owner.md b/prompts/flows/owner.md new file mode 100644 index 00000000..4674604b --- /dev/null +++ b/prompts/flows/owner.md @@ -0,0 +1,19 @@ +# Owner Profile + +Build a profile of the agent's owner so the agent knows who it works for from the first session. It is **context** the agent reads every session, not a role-play Skill: do not generate a `{character}-{slug}` Skill and never write "you are ". + +1. Gather information with `{distilly_skill_root}/prompts/owner/intake.md`: first **discover** the sources this agent already has (the tools, connectors and memory in this session, the platform the agent runs on such as Grok's X account, and the user files, connector configs and exports that `tools/owner_sources.py discover` finds on this machine) instead of walking a fixed list; propose which to read, ask the user which to use and whether to add anything else; read only what they pick, read-only; then ask only the questions the sources did not answer. Use only the host's own tools; never ask for API keys or OAuth setup on this path, and do not use the Lark, DingTalk or Slack collectors here. +2. Build the profile with `{distilly_skill_root}/prompts/owner/profile_builder.md`, write it to `/tmp/distilly_owner_profile.md`, show the full text to the user and apply their corrections. +3. After the user approves, save it: + ```bash + python3 "{distilly_skill_root}/tools/owner_profile.py" save --profile /tmp/distilly_owner_profile.md + ``` +4. Link it into the current host's always-on user file. Show the `--dry-run` result first and write only after the user agrees: + ```bash + python3 "{distilly_skill_root}/tools/owner_profile.py" link --host claude-code --dry-run + python3 "{distilly_skill_root}/tools/owner_profile.py" link --host claude-code + ``` + Claude Code uses `~/.claude/CLAUDE.md`; OpenClaw (`--host openclaw`) uses the workspace `USER.md`. Other hosts are not supported yet; tell the user the full profile is at `~/.distilly/owner/profile.md`. +5. Only the text between the Distilly markers changes; everything else in the file is left alone and the previous file is backed up. Delete `/tmp/distilly_owner_profile.md` afterwards. + +Updates: when the user corrects something or adds information, edit the full profile, then run `save` and `link` again. To undo: `owner_profile.py unlink --host `. diff --git a/prompts/flows/sources.md b/prompts/flows/sources.md new file mode 100644 index 00000000..17da0e57 --- /dev/null +++ b/prompts/flows/sources.md @@ -0,0 +1,264 @@ +# Source Material Import + +Used by Step 2 of `prompts/flows/create.md` and by the append flow in `prompts/flows/evolve.md`. Resolve every `prompts/...` and `tools/...` path against `{distilly_skill_root}` (see `SKILL.md`). + +| Task | Tool | +|------|------| +| Read PDF documents | `Read` tool (native PDF support) | +| Read image screenshots | `Read` tool (native image support) | +| Read MD/TXT files | `Read` tool | +| Parse Lark message JSON export | `Bash` → `python3 "{distilly_skill_root}/tools/feishu_parser.py"` | +| Lark auto-collect (recommended) | `Bash` → `python3 "{distilly_skill_root}/tools/feishu_auto_collector.py"` | +| Lark docs (browser session) | `Bash` → `python3 "{distilly_skill_root}/tools/feishu_browser.py"` | +| Lark docs (MCP App Token) | `Bash` → `python3 "{distilly_skill_root}/tools/feishu_mcp_client.py"` | +| DingTalk auto-collect | `Bash` → `python3 "{distilly_skill_root}/tools/dingtalk_auto_collector.py"` | +| Parse email .eml/.mbox | `Bash` → `python3 "{distilly_skill_root}/tools/email_parser.py"` | + +**Credential rules (Lark / DingTalk / Slack)**: App Secrets, user_access_token, refresh_token, Bot Tokens and OAuth codes must never enter the conversation context. +- **Never** ask the user to send you these values, and never put them in command arguments, script source, output files or replies. +- For setup, ask the user to run the script's `--setup` **in their own terminal** (plus `--login` for Lark private chats). Secrets are typed with hidden input and stored in the OS keychain (when `keyring` is installed) or in `~/.distilly/*_config.json` (mode 0600). The user may instead export environment variables (`FEISHU_APP_ID` / `FEISHU_APP_SECRET` / `FEISHU_USER_ACCESS_TOKEN` / `DINGTALK_APP_KEY` / `DINGTALK_APP_SECRET` / `SLACK_BOT_TOKEN`). `--setup` / `--login` refuse to run without an interactive terminal, so you cannot run them on the user's behalf. +- Use `--check` to see whether credentials are ready; it shows only the source (env / keychain / config file), never the values. +- If you write your own script against the APIs, load credentials from environment variables or `credentials.resolve(...)` in `tools/credentials.py`, and never print them. +- If the user pastes a secret into the chat anyway: do not repeat or use it, tell them to rotate it on the open platform, then reconfigure as above. + +The Lark-labelled compatibility collectors currently connect to the China-region `open.feishu.cn` / `feishu.cn` endpoints. International `larksuite.com` tenant routing is not implemented yet. + +## Choose a source + +Ask the user how they'd like to provide materials: + +``` +How would you like to provide source materials? + + [A] Lark Auto-Collect (recommended) + Enter name, auto-pull messages + docs + spreadsheets + + [B] DingTalk Auto-Collect + Enter name, auto-pull docs + spreadsheets + Messages collected via browser (DingTalk API doesn't support message history) + + [C] Lark Link + Provide doc/Wiki link (browser session or MCP) + + [D] Upload Files + PDF / images / exported JSON / email .eml + + [E] Paste Text + Copy-paste text directly + +Can mix and match, or skip entirely (generate from manual info only). +``` + +--- + +### Option A: Lark Auto-Collect (Recommended) + +First-time setup. Check whether credentials are ready: +```bash +python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" --check +``` +If not, ask the user to run this **in their own terminal** (do not run it yourself, and do not ask for the App Secret in chat): +```bash +python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" --setup +``` + +**Group chat collection** (uses tenant_access_token, bot must be in the group): +```bash +python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" \ + --name "{name}" \ + --output-dir ./knowledge/{slug} \ + --msg-limit 1000 \ + --doc-limit 20 +``` + +**Private chat (P2P) collection** (requires user_access_token + p2p chat_id): + +Private messages can only be accessed via user identity (user_access_token). App identity cannot access private chats. + +**Prerequisites** (the user completes these on the Open Platform and in their own terminal; you only guide): +1. **Lark app credentials**: `app_id` / `app_secret` configured via `--setup` or environment variables (confirm with `--check`) +2. **User scopes**: The app must have these user scopes enabled: + - `im:message` — read/send messages as user + - `im:chat` — read chat list as user +3. **Redirect URL**: add `http://www.example.com` under the app's "Security Settings → Redirect URLs" + +If the user is missing any of these, guide them through setup. Don't assume anything is pre-configured. + +**Getting user_access_token**: ask the user to run this **in their own terminal**: +```bash +python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" --login +``` +The script prints the authorization URL; after authorizing in the browser, the user pastes the callback URL into the terminal (hidden input) and the token goes straight into the keychain or config file. Never ask the user to send the code or token in chat. + +When the token expires (about 2 hours), you can refresh it yourself with no secret input: +```bash +python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" --refresh +``` + +**Getting the p2p chat_id**: + +Users typically don't know their chat_id. The user can open the private chat in Lark and find the `oc_` chat_id in the chat link or developer tools. If you write a script to obtain it, the script must read the token from `credentials.resolve("feishu", ...)` or environment variables and must not print it: + +- **Method**: Send a message to the other user's open_id — the response includes the chat_id (this really sends a message, so ask the user first) + ```python + POST https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id + Header: Authorization: Bearer + Body: {"receive_id": "{target_open_id}", "msg_type": "text", "content": "{\"text\":\"hello\"}"} + # The chat_id in the response is the p2p chat ID + ``` +- **Important**: `GET /im/v1/chats` does NOT return p2p chats — this is an API limitation, not a permission issue. Do not try to use it for finding private chats. +- If the user doesn't know the target's open_id, use tenant_access_token to search contacts: + ```python + GET https://open.feishu.cn/open-apis/contact/v3/scopes + # Returns open_ids of all users visible to the app + ``` + +**Running collection**: + +Once credentials are ready and you have the chat_id (the script loads the token itself; it never appears on the command line): +```bash +python3 "{distilly_skill_root}/tools/feishu_auto_collector.py" \ + --open-id {target_open_id} \ + --p2p-chat-id {chat_id} \ + --name "{name}" \ + --output-dir ./knowledge/{slug} \ + --msg-limit 1000 +``` + +**Flexibility principle**: The above API calls don't have to go through the collector script. If the script doesn't work or doesn't fit the scenario, write Python scripts directly against the same endpoints, but load credentials through `credentials.resolve("feishu", feishu_auto_collector.load_config())` or environment variables; they must never appear in script source, command arguments or output. Key API reference: +- Get token: `POST /auth/v3/tenant_access_token/internal` (wrapped by `get_tenant_token` in the collector) +- Send message (get chat_id): `POST /im/v1/messages?receive_id_type=open_id` +- Fetch messages: `GET /im/v1/messages?container_id_type=chat&container_id={chat_id}` +- Search contacts: `GET /contact/v3/scopes`, `GET /contact/v3/users/{user_id}` + +Auto-collected content: +- Group chats: messages sent by them (system messages and stickers filtered) +- Private chats: full conversation with both parties (for context understanding) +- Lark docs and Wikis they created/edited +- Related spreadsheets (if accessible) + +After collection, `Read` the output files: +- `knowledge/{slug}/messages.txt` → messages (group + private) +- `knowledge/{slug}/docs.txt` → document content +- `knowledge/{slug}/collection_summary.json` → collection summary + +If collection fails, diagnose the error and attempt to fix it. Common issues: +- Group chat: bot not added to the group +- Private chat: user_access_token expired (2-hour TTL; run `--refresh`, or ask the user to re-run `--login` in their own terminal if there is no refresh_token) +- Insufficient permissions: guide user to enable scopes and re-authorize +- Or switch to Option B/C + +--- + +### Option B: DingTalk Auto-Collect + +First-time setup. Run `--check` first; if not configured, ask the user to run `--setup` **in their own terminal** (hidden AppSecret input), or to export `DINGTALK_APP_KEY` / `DINGTALK_APP_SECRET`. Never ask for the AppSecret in chat: +```bash +python3 "{distilly_skill_root}/tools/dingtalk_auto_collector.py" --check +python3 "{distilly_skill_root}/tools/dingtalk_auto_collector.py" --setup # run by the user +``` + +Then enter the name: +```bash +python3 "{distilly_skill_root}/tools/dingtalk_auto_collector.py" \ + --name "{name}" \ + --output-dir ./knowledge/{slug} \ + --msg-limit 500 \ + --doc-limit 20 \ + --show-browser # add this flag on first use to complete DingTalk login +``` + +Collected content: +- DingTalk docs and knowledge bases they created/edited +- Spreadsheets +- Messages (⚠️ DingTalk API doesn't support message history — auto-switches to browser scraping) + +After collection, `Read`: +- `knowledge/{slug}/docs.txt` +- `knowledge/{slug}/bitables.txt` +- `knowledge/{slug}/messages.txt` + +If message collection fails, prompt user to upload chat screenshots. + +--- + +### Option C: Lark Link + +When the user provides a Lark doc/Wiki link, ask which method to use: + +``` +Lark link detected. Choose read method: + + [1] Browser Method (recommended) + Reuses your local Chrome login session + ✅ Works with internal docs requiring permissions + ✅ No token configuration needed + ⚠️ Requires Chrome + playwright installed locally + + [2] MCP Method + Uses a Lark App Token via the official API + ✅ Stable, no browser dependency + ✅ Can read messages (needs chat ID) + ⚠️ Requires App ID / App Secret setup + ⚠️ Internal docs need admin authorization for the app + +Choose [1/2]: +``` + +**Option 1 (Browser)**: +```bash +python3 "{distilly_skill_root}/tools/feishu_browser.py" \ + --url "{feishu_url}" \ + --target "{name}" \ + --output /tmp/feishu_doc_out.txt +``` +First use will open a browser window for login (one-time). + +**Option 2 (MCP)**: + +First-time setup (shares Lark credentials with Option A). Run `--check` first; if not configured, ask the user to run `--setup` **in their own terminal**: +```bash +python3 "{distilly_skill_root}/tools/feishu_mcp_client.py" --check +python3 "{distilly_skill_root}/tools/feishu_mcp_client.py" --setup # run by the user +``` + +Then read directly: +```bash +python3 "{distilly_skill_root}/tools/feishu_mcp_client.py" \ + --url "{feishu_url}" \ + --output /tmp/feishu_doc_out.txt +``` + +Read messages (needs chat ID, format `oc_xxx`): +```bash +python3 "{distilly_skill_root}/tools/feishu_mcp_client.py" \ + --chat-id "oc_xxx" \ + --target "{name}" \ + --limit 500 \ + --output /tmp/feishu_msg_out.txt +``` + +Both methods output to files, then use `Read` to load results into analysis. + +--- + +### Option D: Upload Files + +- **PDF / Images**: `Read` tool directly +- **Lark message JSON export**: + ```bash + python3 "{distilly_skill_root}/tools/feishu_parser.py" --file {path} --target "{name}" --output /tmp/feishu_out.txt + ``` + Then `Read /tmp/feishu_out.txt` +- **Email files .eml / .mbox**: + ```bash + python3 "{distilly_skill_root}/tools/email_parser.py" --file {path} --target "{name}" --output /tmp/email_out.txt + ``` + Then `Read /tmp/email_out.txt` +- **Markdown / TXT**: `Read` tool directly + +--- + +### Option E: Paste Text + +User-pasted content is used directly as text material. No tools needed. diff --git a/tests/test_owner_profile.py b/tests/test_owner_profile.py index 9549b450..c84c2698 100644 --- a/tests/test_owner_profile.py +++ b/tests/test_owner_profile.py @@ -201,7 +201,9 @@ def test_skill_writer_refuses_owner(self) -> None: self.assertIn("owner_profile.py", result.stderr) def test_root_skill_documents_owner_flow(self) -> None: - content = (REPO_ROOT / "SKILL.md").read_text(encoding="utf-8") + router = (REPO_ROOT / "SKILL.md").read_text(encoding="utf-8") + self.assertIn("prompts/flows/owner.md", router) + content = (REPO_ROOT / "prompts" / "flows" / "owner.md").read_text(encoding="utf-8") self.assertIn("prompts/owner/intake.md", content) self.assertIn("tools/owner_profile.py", content) for prompt in ("intake.md", "profile_builder.md"): diff --git a/tests/test_skill_entrypoint_docs.py b/tests/test_skill_entrypoint_docs.py index f34620f2..75ba362c 100644 --- a/tests/test_skill_entrypoint_docs.py +++ b/tests/test_skill_entrypoint_docs.py @@ -44,6 +44,7 @@ HAN_CHARACTER = re.compile(r"[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]") BUNDLED_PATH = re.compile(r"\b((?:prompts|tools)/[\w./-]+\.(?:md|py|sh))\b") BARE_TOOL_CALL = re.compile(r"(?:python3|bash) \"?tools/") +ROUTER_MAX_LINES = 80 RESEARCH_FLOOR = re.compile(r"`([A-Z][\w -]+?) (?:>=|=) \d+`") @@ -116,6 +117,13 @@ def test_root_skill_uses_distilly_entrypoint(self) -> None: self.assertIn("{distilly_skill_root}", content) self.assertIn("${CLAUDE_SKILL_DIR}", content) + def test_router_stays_short_and_routes_every_flow(self) -> None: + router = (ROOT / "SKILL.md").read_text(encoding="utf-8") + # Hosts load SKILL.md in full on every trigger; flow detail loads on demand. + self.assertLessEqual(len(router.splitlines()), ROUTER_MAX_LINES) + for flow in sorted((ROOT / "prompts" / "flows").glob("*.md")): + self.assertIn(flow.relative_to(ROOT).as_posix(), router, f"router never reaches {flow.name}") + def test_instruction_files_reference_existing_resources(self) -> None: for path, content in _instruction_files().items(): self.assertIsNone( From 73e98a7920e740c083cbb9e28e65d86c723613da Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 08:53:03 +0000 Subject: [PATCH 4/5] Merge the two celebrity research prompts into one prompts/celebrity/research.md and budget_unfriendly/research.md shared the taste principles, source hierarchy, blacklist, tooling and the six dimension briefs, with small drift between the copies. One prompt now holds the shared part once (taking the fuller wording of each section) and ends with a budget-friendly and a budget-unfriendly output contract, each keeping its own files, quality floor and checkpoint. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_017tXoVWiktX2vSUJzrrdBLJ --- .../celebrity/budget_unfriendly/research.md | 367 ------------------ prompts/celebrity/research.md | 356 +++++++++++------ prompts/flows/celebrity_research.md | 4 +- tests/test_skill_entrypoint_docs.py | 1 - tests/test_skill_writer.py | 29 +- tools/skill_presets.py | 2 +- 6 files changed, 249 insertions(+), 510 deletions(-) delete mode 100644 prompts/celebrity/budget_unfriendly/research.md diff --git a/prompts/celebrity/budget_unfriendly/research.md b/prompts/celebrity/budget_unfriendly/research.md deleted file mode 100644 index 0950ecdd..00000000 --- a/prompts/celebrity/budget_unfriendly/research.md +++ /dev/null @@ -1,367 +0,0 @@ -# Celebrity Budget-Unfriendly Research Prompt - -## Task - -Run a deep, multi-dimensional celebrity research pass with maximum evidence rigor. - -This mode is intentionally expensive. It should only be used when the user wants a higher-confidence skill and accepts a slower workflow. - -The goal is not just to collect notes. The goal is to gather enough grounded evidence to: -- survive a later audit, synthesis pass, and validation pass -- reconstruct this person's actual cognitive operating system -- distinguish their genuine thinking patterns from generic wisdom - ---- - -## Taste Principles - -These principles govern what to collect and what to skip. They apply at every stage. - -1. **Long-form > snippets**: A 3000-word essay reveals more thinking structure than 50 tweets -2. **Controversy > consensus**: Disputed positions expose distinctive thinking better than universally praised ones -3. **Change > fixity**: Where someone changed their mind is more informative than where they stayed consistent -4. **Firsthand > secondhand**: Their own words always outrank someone else's summary -5. **Craft discussion > biography**: How they talk about their process matters more than their life story -6. **Repeated patterns > one-off quotes**: A pattern that appears 5 times across contexts beats one viral line -7. **Failure discussion > success narrative**: How they talk about what went wrong reveals more than victory laps - ---- - -## Source Quality Hierarchy - -Prioritize sources in this strict order: - -1. **User-provided local materials** (transcripts, PDFs, screenshots — ground truth, highest weight) -2. **First-person authored works** (books, essays, newsletters, blog posts) -3. **Long-form interviews and conversations** (podcasts 30+ min, video interviews, fireside chats) -4. **Documented decisions and turning points** (case studies, public records, reported actions with attribution) -5. **Short-form first-person content** (social media posts, short Q&A, tweet threads) -6. **External analysis and criticism** (profiles, reviews, biographies by others) -7. **Secondhand summaries** (use only to locate primary sources, never as standalone evidence) - -### Source Blacklist — Permanently Excluded - -**Chinese context:** -- Zhihu — unverifiable anonymous answers, heavy hearsay -- WeChat official accounts — mostly repackaged secondhand content -- Baidu Baike — unreliable, often outdated or promotional -- Sohu / NetEase / Tencent auto-generated news aggregation pages - -**General:** -- Content farms and SEO-optimized summary sites -- AI-generated biography pages -- Listicles ("10 lessons from X") unless they directly quote and link primary sources -- Wikipedia as standalone evidence (acceptable only as a pointer to find real sources) -- Any source that is itself a summary of summaries - -### Recommended Sources by Region - -**Chinese figures:** -- Bilibili original long-form videos and interviews -- Xiaoyuzhou full podcast episodes -- Authoritative media: 36Kr, LatePost, Caixin, GeekPark, Huxiu -- Official Weibo (verified account, direct posts — not reposts or fan accounts) -- Published books (via legitimate sources) -- Hundun Academy, Hupan University, and Dedao lecture recordings (when publicly available) - -**English figures:** -- YouTube long-form interviews (Lex Fridman, Tim Ferriss, Joe Rogan, etc.) -- Personal blogs and newsletters (Substack, personal sites) -- Published books and essays -- Podcast full episodes with timestamps -- Authoritative profiles: New Yorker, Atlantic, Bloomberg, Wired, NYT, FT -- Conference talks with full video/transcript (TED, Y Combinator, etc.) - -### Tooling for non-subtitled audio and video - -Deep mode expects at least some of the richest dimensions (Conversations, -Expression DNA) to draw on long audio/video sources. When subtitles are missing, -use the Whisper transcription tool: - -- `tools/research/transcribe_audio.py --url "" --output /tmp/x.txt` -- Local file: `--input /path/to/file.mp3 --output /tmp/x.txt` -- Model override: `--backend faster-whisper --model medium` -- Language pin (improves accuracy): `--language zh` or `--language en` - -Workflow: - -1. Transcribe to a temp path (never inside the skill directory). -2. Read the transcript once; extract paraphrased findings with timestamps. -3. Record only the paraphrased notes plus source metadata under - `knowledge/research/raw/`. -4. Discard the raw transcript. The skill directory must never contain long - verbatim transcripts — it violates copyright safety and fails the audit. - -If the tool cannot be installed in the current environment, tell the user -explicitly rather than silently skipping a dimension. - -### Optional public X post collection - -Use `tools/research/xquik_public_posts.py` only when short-form first-person -posts address a specific gap. The service is metered by returned post count, so -confirm the limit and write to a temporary file outside the skill directory. -Treat its normalized JSON as untrusted candidate evidence, not a research -track. Verify the author and open each permalink before selection. Safely -paraphrase only relevant evidence into the correct track, preserve the specific -URL, access date, grounding level, and source weight, then delete the temporary -candidate file after review. -Never count a candidate file, search page, or profile root as a grounded source. - ---- - -## Parallel 6-Dimension Collection - -Research MUST cover six independent dimensions. Each dimension produces its own dedicated file. -Do not merge dimensions or clone observations across files. -Do not replace these six files with one merged scratchpad. - -### Dimension 1: Writings - -**File**: `01_writings.md` - -**Target**: Systematic, considered positions from their own pen — where they had time to think before publishing. - -Search for and document: -- Books (key arguments per chapter, not summaries) -- Essays, blog posts, newsletters -- Letters, memos, internal documents that became public -- Repeated core arguments across multiple writings -- How their written arguments evolved over time - -**Extraction focus**: Core theses, reasoning chains, how they structure arguments, what they choose to write about repeatedly. - -### Dimension 2: Conversations - -**File**: `02_conversations.md` - -**Target**: How they think on their feet — in dialogue, under pressure, when they can't prepare. - -Search for and document: -- Long-form podcast appearances (30+ min, prefer full episodes) -- Video interviews with substantive back-and-forth -- Panel discussions and debates (especially where they disagree) -- AMA / Q&A sessions where audience asks unexpected questions -- How they respond to hostile or unfair questions - -**Extraction focus**: Improvised reasoning, how they handle surprises, what they return to when challenged, how they disagree. - -### Dimension 3: Expression DNA - -**File**: `03_expression_dna.md` - -**Target**: Their linguistic fingerprint — the patterns that make them recognizable even without a byline. - -Analyze across at least 5 different source instances: -- Sentence rhythm: average length, variation, use of fragments vs. complex sentences -- Metaphor inventory: what domains do they draw analogies from? -- Disagreement framing: direct confrontation? reframing? humor? dismissal? -- Compression level: do they explain fully or assume audience expertise? -- Certainty language: how do they express confidence vs. doubt? -- Recurring phrases, verbal tics, signature framings -- Humor style (if any): self-deprecating, absurdist, dry, combative, sarcastic -- Tone shifts: how does their voice change between casual and serious contexts? - -**Extraction focus**: Style markers that pass the "blind test" — would you recognize this person from 100 words with the name removed? - -### Dimension 4: Decisions - -**File**: `04_decisions.md` - -**Target**: What they actually did, not just what they said they'd do. - -Search for and document: -- Major bets: what they invested time/money/reputation in, and the reasoning -- Reversals: where they changed direction and what triggered it -- Tradeoffs: what they explicitly chose to sacrifice for what -- Failures: how they discuss what went wrong (or avoid discussing it) -- Hiring / team decisions: who they chose to work with and why -- Timing decisions: when they moved fast vs. when they waited - -**Extraction focus**: Decision patterns, risk tolerance, what evidence moves them, gap between stated values and revealed preferences. - -### Dimension 5: External Views - -**File**: `05_external_views.md` - -**Target**: The version of this person that exists in other people's heads — especially where it diverges from their self-narrative. - -Search for and document: -- Substantive criticism (not personal attacks) -- Contradictions pointed out by credible observers -- How collaborators, opponents, and successors describe them -- Known blindspots identified by domain peers -- Praise that reveals something non-obvious -- The gap between their public persona and reported private behavior - -**Extraction focus**: Where the outside view diverges from the inside view. What others see that they don't. - -### Dimension 6: Timeline - -**File**: `06_timeline.md` - -**Target**: How their thinking has evolved — not just a resume of what happened when. - -Build a timeline that tracks: -- Key milestones with **cognitive impact** (how did this event change their thinking?) -- Intellectual turning points — when did a core belief shift? -- Phase transitions: were there distinct eras in their thinking? -- Recent developments (last 12–24 months) -- What they used to believe but no longer do - -**Extraction focus**: The trajectory of their cognitive evolution, not a chronological biography. - ---- - -## Cold Figure Protocol - -If during research you find that multiple dimensions have very thin coverage: - -1. **Do not fabricate** evidence, quotes, or attributions to fill gaps -2. **Mark each thin dimension explicitly** with what's missing and why -3. **Count total grounded sources**: if < 10, this is a cold figure -4. For cold figures: - - Limit mental models to 2–3 maximum - - Add "based on limited information" markers to every thin dimension - - Expand the honest boundaries section substantially - - Tell the user what specific additional material would improve quality - - Consider whether to continue or recommend switching to a better-documented figure - ---- - -## Required Output Contract - -Create six raw research note files under `knowledge/research/raw/`: - -- `01_writings.md` -- `02_conversations.md` -- `03_expression_dna.md` -- `04_decisions.md` -- `05_external_views.md` -- `06_timeline.md` - -Each file must include: - -```md -# - -## Collection Metadata -- Dimension: [number and name] -- Collection strategy: [web-only / web+local / local-first] -- Sources searched: [count] -- Sources used: [count] -- Primary vs secondary ratio: [X:Y] - -## Source Metadata -(repeat for each source) -- URL: ... -- Source type: book / essay / interview / podcast / talk / social post / article / profile / decision record -- Grounding level: primary / secondary -- Access note: public / partial / blocked -- Source weight: [1-7, per hierarchy above] -- Date: [publication or access date] - -## Evidence -- ... - -## Patterns and Repeated Themes -- ... - -## Contradictions -- ... - -## Inferences (clearly marked) -- ... - -## Gaps and Missing Information -- ... -``` - -### Minimum Quality Floor (budget-unfriendly) - -Across the full six-track set: - -- at least 6 raw note files (one per dimension) -- at least 8 grounded source URLs -- at least 3 primary-source markers -- at least 6 source metadata blocks (one per file minimum) -- at least 6 contradiction bullets across the full set -- at least 6 inference bullets across the full set -- 0 potential long quote lines (copyright safety) -- track coverage count = 6 (no dimension skipped) - -Grounded URLs must be actual inspected pages, not platform roots or placeholder paths. - -### Keep Each Track Distinct - -- Do not clone the same observations into all six files -- Each dimension has a specific extraction focus — honor it -- If a finding is relevant to multiple dimensions, put the primary evidence in the most relevant file and add a cross-reference note in the other - ---- - -## Quality Checkpoint - -After completing all six research files, before proceeding to audit, produce a structured summary: - -``` -┌──────────────────────────────┬──────────┬──────────┬─────────────────────────────┐ -│ Dimension │ Sources │ Primary% │ Key Finding │ -├──────────────────────────────┼──────────┼──────────┼─────────────────────────────┤ -│ 1 Writings │ N │ XX% │ [core thesis / gap] │ -│ 2 Conversations │ N │ XX% │ [key pattern / gap] │ -│ 3 Expression DNA │ N │ XX% │ [style marker / gap] │ -│ 4 Decisions │ N │ XX% │ [decision pattern / gap] │ -│ 5 External Views │ N │ XX% │ [outside perspective / gap] │ -│ 6 Timeline │ N │ XX% │ [trajectory / gap] │ -├──────────────────────────────┼──────────┼──────────┼─────────────────────────────┤ -│ Total grounded URLs │ N │ │ │ -│ Primary-source markers │ N │ │ Target: ≥3 │ -│ Contradictions found │ N │ │ [summary of top 3] │ -│ Candidate mental models │ N │ │ [brief list] │ -│ Known-answer candidates │ N │ │ [questions with evidence] │ -│ Thin dimensions │ [list] │ │ Backfill plan: [plan] │ -│ Cold figure? │ yes/no │ │ │ -└──────────────────────────────┴──────────┴──────────┴─────────────────────────────┘ -``` - -Present this to the user and wait for confirmation before proceeding to audit. - ---- - -## Audit Readiness - -The research pass is not done when the sixth file exists. It is only done when the six-track set looks strong enough to pass a separate research audit. - -Before moving on, make sure the notes support: - -- at least 3 candidate mental models with cross-dimensional evidence -- at least 2 candidate known-answer questions with evidence anchors -- at least 1 credible edge-case question that would require extrapolation -- a visible contradiction inventory with at least 3 substantive tensions -- enough expression DNA samples to pass a blind style test - ---- - -## Research Rules - -- Prefer first-person material over commentary -- Prefer long-form interviews over short clips -- Prefer repeated patterns over one-off spicy quotes -- Prefer direct discussion of craft, choices, and tradeoffs over shallow biography -- Record source gaps honestly — gaps are information too -- Only record URLs you actually opened and inspected -- Generic homepages, topic pages, search pages, and placeholder media roots do not count -- Keep each track meaningfully different; each dimension has a unique extraction focus -- Build enough evidence for later known-answer and edge-case validation - -## Copyright Safety - -- Do not write full transcripts into the repository -- Do not paste long excerpts from books, subtitles, or articles -- Keep any direct quote very short (under 2 sentences) -- Summarize in your own words with source attribution -- Do not store subtitle dumps, transcript-like blocks, or long copied dialogue - -## Output Constraint - -Write in the user's language. diff --git a/prompts/celebrity/research.md b/prompts/celebrity/research.md index 20a28426..6bd175d3 100644 --- a/prompts/celebrity/research.md +++ b/prompts/celebrity/research.md @@ -1,11 +1,13 @@ -# Celebrity Research Prompt (Budget-Friendly) +# Celebrity Research Prompt ## Task -Run a structured, multi-dimensional research pass on a public figure before distillation. +Run a structured, multi-dimensional research pass on a public figure before distillation. One prompt serves both research profiles: everything up to "Output Contract" applies to both, then follow the section for the chosen `research_profile`. -The goal is not to collect trivia or compile a biography. -The goal is to extract enough grounded evidence to reconstruct: +- **budget-friendly** (default): a lighter pass that covers six dimensions across three note files. +- **budget-unfriendly**: a deep pass with one file per dimension that must later survive a research audit, a synthesis pass and a validation pass. Use it only when the user wants a higher-confidence skill and accepts a slower, more expensive workflow. + +The goal is not to collect trivia or compile a biography. The goal is to extract enough grounded evidence to reconstruct: - mental models (how they think) - decision heuristics (how they choose) @@ -13,11 +15,13 @@ The goal is to extract enough grounded evidence to reconstruct: - anti-patterns (what they reject) - honest boundaries (where evidence runs out) +and to distinguish their genuine thinking patterns from generic wisdom. + --- ## Taste Principles -These principles govern what to collect and what to skip: +These principles govern what to collect and what to skip. They apply at every stage. 1. **Long-form > snippets**: A 3000-word essay reveals more thinking structure than 50 tweets 2. **Controversy > consensus**: Disputed positions expose distinctive thinking better than universally praised ones @@ -25,182 +29,203 @@ These principles govern what to collect and what to skip: 4. **Firsthand > secondhand**: Their own words always outrank someone else's summary 5. **Craft discussion > biography**: How they talk about their process matters more than their life story 6. **Repeated patterns > one-off quotes**: A pattern that appears 5 times across contexts beats one viral line +7. **Failure discussion > success narrative**: How they talk about what went wrong reveals more than victory laps --- ## Source Quality Hierarchy -Prioritize sources in this order: +Prioritize sources in this order. The number is the source weight every note records. -1. **User-provided local materials** (transcripts, PDFs, screenshots — ground truth) +1. **User-provided local materials** (transcripts, PDFs, screenshots — ground truth, highest weight) 2. **First-person authored works** (books, essays, newsletters, blog posts) -3. **Long-form interviews and conversations** (podcasts, 30+ min interviews, fireside chats) -4. **Documented decisions and turning points** (case studies, public records, reported actions) -5. **Short-form first-person content** (social media posts, short Q&A, tweets/threads) +3. **Long-form interviews and conversations** (podcasts 30+ min, video interviews, fireside chats) +4. **Documented decisions and turning points** (case studies, public records, reported actions with attribution) +5. **Short-form first-person content** (social media posts, short Q&A, tweet threads) 6. **External analysis and criticism** (profiles, reviews, biographies by others) -7. **Secondhand summaries** (use only when nothing else is available) +7. **Secondhand summaries** (use only to locate primary sources, never as standalone evidence) -### Source Blacklist +### Source Blacklist — Permanently Excluded -These sources are permanently excluded — never cite them as evidence: +Never cite these as evidence. **Chinese context:** - Zhihu — unverifiable anonymous answers, heavy hearsay - WeChat official accounts — mostly repackaged secondhand content - Baidu Baike — unreliable, often outdated or promotional +- Sohu / NetEase / Tencent auto-generated news aggregation pages **General:** - Content farms and SEO-optimized summary sites - AI-generated biography pages -- Listicles ("10 lessons from X") unless they quote primary sources with links -- Wikipedia as a primary source (acceptable only as a pointer to find real sources) +- Listicles ("10 lessons from X") unless they directly quote and link primary sources +- Wikipedia as standalone evidence (acceptable only as a pointer to find real sources) +- Any source that is itself a summary of summaries ### Recommended Sources by Region **Chinese figures:** -- Bilibili original videos, especially long interviews -- Xiaoyuzhou podcasts +- Bilibili original long-form videos and interviews +- Xiaoyuzhou full podcast episodes - Authoritative media: 36Kr, LatePost, Caixin, GeekPark, Huxiu -- Official Weibo (verified account, direct posts only) +- Official Weibo (verified account, direct posts — not reposts or fan accounts) - Published books (via legitimate sources) +- Hundun Academy, Hupan University, and Dedao lecture recordings (when publicly available) **English figures:** -- YouTube long-form interviews (Lex Fridman, Tim Ferriss, etc.) -- Personal blogs and newsletters +- YouTube long-form interviews (Lex Fridman, Tim Ferriss, Joe Rogan, etc.) +- Personal blogs and newsletters (Substack, personal sites) - Published books and essays -- Podcast full episodes (not clips) -- Authoritative profiles: New Yorker, Atlantic, Bloomberg, Wired +- Podcast full episodes with timestamps +- Authoritative profiles: New Yorker, Atlantic, Bloomberg, Wired, NYT, FT +- Conference talks with full video/transcript (TED, Y Combinator, etc.) -### Tooling for non-subtitled content +### URL Grounding Rules -Long interviews and podcasts without subtitles are often the richest source of -Expression DNA and on-the-fly reasoning — do not skip them. Use: +These do **not** count as grounded sources: -- `tools/research/transcribe_audio.py --url "