@ericrisco/rsc 0.1.33 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/agent-eval/SKILL.md +1 -1
- package/skills/analyze/SKILL.md +1 -1
- package/skills/brand-voice/SKILL.md +1 -1
- package/skills/brand-voice/references/voice-guide-template.md +14 -0
- package/skills/building-agents/SKILL.md +2 -2
- package/skills/competitor-watch/SKILL.md +9 -4
- package/skills/competitor-watch/references/tracker-schema.md +19 -0
- package/skills/constitution/references/constitution-template.md +10 -0
- package/skills/content-engine/SKILL.md +1 -1
- package/skills/content-engine/references/brief-and-pipeline.md +15 -1
- package/skills/course-builder/references/grounding-and-scoping.md +23 -0
- package/skills/course-storytelling/references/learner-grounding.md +22 -2
- package/skills/decision-records/SKILL.md +14 -1
- package/skills/decision-records/references/templates.md +20 -0
- package/skills/design/references/brand-grounding.md +11 -1
- package/skills/harness/SKILL.md +3 -3
- package/skills/harness/references/daily-curation-automation.md +8 -6
- package/skills/harness/references/obsidian-scaffolding.md +16 -9
- package/skills/harness/references/wiki-article-template.md +31 -14
- package/skills/harness/references/wiki-index-template.md +7 -3
- package/skills/harness/references/wiki-protocol.md +120 -54
- package/skills/harness/references/wiki-worklog-template.md +5 -1
- package/skills/instagram-api/SKILL.md +8 -1
- package/skills/instagram-api/references/insights-metrics.md +8 -1
- package/skills/knowledge-ops/SKILL.md +9 -9
- package/skills/knowledge-ops/evals/cases.yaml +1 -1
- package/skills/knowledge-ops/references/gardening-playbook.md +18 -8
- package/skills/linkedin-api/SKILL.md +10 -1
- package/skills/linkedin-strategy/SKILL.md +12 -0
- package/skills/linkedin-strategy/references/wiki-records.md +27 -1
- package/skills/marketing/references/brand-grounding.md +22 -2
- package/skills/medium-strategy/SKILL.md +12 -0
- package/skills/medium-strategy/references/wiki-records.md +39 -1
- package/skills/medium-writing/SKILL.md +19 -0
- package/skills/plan/references/plan-template.md +12 -1
- package/skills/roast-me/tools/adapters/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/base.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/claude.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/codex.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/gemini.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/__pycache__/registry.cpython-312.pyc +0 -0
- package/skills/roast-me/tools/adapters/base.py +12 -0
- package/skills/roast-me/tools/adapters/codex.py +146 -53
- package/skills/roast-me/tools/adapters/gemini.py +102 -81
- package/skills/roast-me/tools/extract_prompts.py +13 -14
- package/skills/shortform-packaging/SKILL.md +7 -2
- package/skills/shortform-packaging/references/package-templates.md +13 -2
- package/skills/shortform-strategy/SKILL.md +30 -6
- package/skills/shortform-strategy/references/learning-loop-template.md +9 -1
- package/skills/specify/references/spec-template.md +12 -1
- package/skills/tiktok-api/SKILL.md +6 -2
- package/skills/tiktok-api/references/wiki-schema.md +38 -4
- package/skills/verify/SKILL.md +11 -1
- package/skills/youtube-api/SKILL.md +7 -2
- package/skills/youtube-api/references/wiki-schema.md +25 -4
- package/skills/youtube-packaging/SKILL.md +10 -2
- package/skills/youtube-packaging/references/description-and-chapters.md +70 -2
- package/skills/youtube-strategy/SKILL.md +9 -2
- package/skills/youtube-strategy/references/wiki-records.md +39 -6
- package/skills/youtube-strategy/scripts/verify.sh +4 -0
- package/skills/youtube-thumbnails/SKILL.md +10 -5
- package/skills/youtube-thumbnails/references/experiment-log-format.md +28 -2
|
@@ -1,50 +1,58 @@
|
|
|
1
1
|
"""Gemini CLI adapter (@google/gemini-cli).
|
|
2
2
|
|
|
3
|
-
Session files: ~/.gemini/tmp/<project_hash>/chats
|
|
4
|
-
Format: JSON
|
|
5
|
-
|
|
6
|
-
CONFIRMED (2026-06,
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Degradation: if ~/.gemini/ does not exist, discover() returns [].
|
|
3
|
+
Session files: ~/.gemini/tmp/<project_hash>/chats/session-*.json
|
|
4
|
+
Format: a SINGLE JSON object per file (not JSONL).
|
|
5
|
+
|
|
6
|
+
CONFIRMED (2026-06, against live ~/.gemini/tmp on macOS):
|
|
7
|
+
- Base path / layout: ~/.gemini/tmp/<project_hash>/chats/*.json
|
|
8
|
+
- File shape: {"sessionId", "projectHash", "startTime", "lastUpdated",
|
|
9
|
+
"messages": [ {"id", "timestamp", "type", "content", ...} ]}
|
|
10
|
+
- messages[].type is "user" or "gemini"; content is a plain string.
|
|
11
|
+
- gemini (assistant) messages also carry "model", "thoughts", "tokens".
|
|
12
|
+
- The format does NOT persist tool calls/errors as messages, so tool-error
|
|
13
|
+
detection is not possible here — followed_by_error stays False (honest);
|
|
14
|
+
correction detection (next user message) still works.
|
|
15
|
+
|
|
16
|
+
Degradation: if ~/.gemini/ does not exist, discover() returns []; a file that
|
|
17
|
+
is not the expected object shape yields no records (parse never raises).
|
|
20
18
|
"""
|
|
21
19
|
|
|
22
20
|
from __future__ import annotations
|
|
23
21
|
|
|
22
|
+
import datetime
|
|
24
23
|
import json
|
|
25
24
|
from pathlib import Path
|
|
26
25
|
from typing import Any
|
|
27
26
|
|
|
28
|
-
from .base import BaseAdapter
|
|
27
|
+
from .base import CORRECTION_RE, BaseAdapter
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _ts(value: Any) -> float | None:
|
|
31
|
+
if isinstance(value, (int, float)):
|
|
32
|
+
return float(value)
|
|
33
|
+
if isinstance(value, str):
|
|
34
|
+
try:
|
|
35
|
+
return datetime.datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp()
|
|
36
|
+
except (ValueError, AttributeError):
|
|
37
|
+
return None
|
|
38
|
+
return None
|
|
29
39
|
|
|
30
40
|
|
|
31
41
|
class GeminiAdapter(BaseAdapter):
|
|
32
42
|
RUNTIME_ID = "gemini"
|
|
33
43
|
|
|
34
|
-
# CONFIRMED: path from official gemini-cli session-management docs.
|
|
35
44
|
GEMINI_TMP_DIR = Path.home() / ".gemini" / "tmp"
|
|
36
45
|
|
|
37
46
|
def discover(self) -> list[Path]:
|
|
38
|
-
"""Find all *.
|
|
47
|
+
"""Find all session *.json chat files under ~/.gemini/tmp/<hash>/chats/."""
|
|
39
48
|
if not self.GEMINI_TMP_DIR.exists():
|
|
40
49
|
return []
|
|
41
50
|
files: list[Path] = []
|
|
42
51
|
try:
|
|
43
|
-
|
|
44
|
-
for jsonl in self.GEMINI_TMP_DIR.rglob("chats/*.jsonl"):
|
|
52
|
+
for jf in self.GEMINI_TMP_DIR.rglob("chats/*.json"):
|
|
45
53
|
try:
|
|
46
|
-
if
|
|
47
|
-
files.append(
|
|
54
|
+
if jf.is_file():
|
|
55
|
+
files.append(jf)
|
|
48
56
|
except OSError:
|
|
49
57
|
continue
|
|
50
58
|
except OSError:
|
|
@@ -52,70 +60,83 @@ class GeminiAdapter(BaseAdapter):
|
|
|
52
60
|
return sorted(files, key=lambda f: f.stat().st_mtime, reverse=True)
|
|
53
61
|
|
|
54
62
|
def parse(self, path: Path) -> list[dict[str, Any]]:
|
|
55
|
-
"""Parse one Gemini
|
|
56
|
-
|
|
57
|
-
STUBBED schema: expected MessageRecord with {role, content: [{text}], ...}.
|
|
58
|
-
Falls back silently if the schema differs.
|
|
59
|
-
"""
|
|
60
|
-
records: list[dict[str, Any]] = []
|
|
63
|
+
"""Parse one Gemini chat JSON file into user-prompt records with
|
|
64
|
+
correction context. Tool errors are not represented in this format."""
|
|
61
65
|
try:
|
|
62
|
-
|
|
63
|
-
except OSError:
|
|
66
|
+
obj = json.loads(path.read_text(encoding="utf-8", errors="replace"))
|
|
67
|
+
except (OSError, json.JSONDecodeError):
|
|
68
|
+
return []
|
|
69
|
+
if not isinstance(obj, dict):
|
|
64
70
|
return []
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
continue
|
|
70
|
-
try:
|
|
71
|
-
obj = json.loads(line)
|
|
72
|
-
except json.JSONDecodeError:
|
|
73
|
-
continue
|
|
72
|
+
messages = obj.get("messages")
|
|
73
|
+
if not isinstance(messages, list):
|
|
74
|
+
return []
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
# Normalise into an ordered timeline of {kind, text, model, ts}.
|
|
77
|
+
timeline: list[dict[str, Any]] = []
|
|
78
|
+
for m in messages:
|
|
79
|
+
if not isinstance(m, dict):
|
|
78
80
|
continue
|
|
79
|
-
|
|
80
|
-
content =
|
|
81
|
-
if isinstance(content,
|
|
82
|
-
parts = []
|
|
83
|
-
for block in content:
|
|
84
|
-
if isinstance(block, dict):
|
|
85
|
-
text = block.get("text", "")
|
|
86
|
-
if text:
|
|
87
|
-
parts.append(text)
|
|
88
|
-
text = "\n".join(parts)
|
|
89
|
-
elif isinstance(content, str):
|
|
90
|
-
text = content
|
|
91
|
-
else:
|
|
92
|
-
text = ""
|
|
93
|
-
|
|
94
|
-
if not text.strip():
|
|
81
|
+
mtype = m.get("type")
|
|
82
|
+
content = m.get("content", "")
|
|
83
|
+
if not isinstance(content, str) or not content.strip():
|
|
95
84
|
continue
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
ts
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
85
|
+
ts = _ts(m.get("timestamp"))
|
|
86
|
+
if mtype == "user":
|
|
87
|
+
timeline.append({"kind": "user", "text": content, "ts": ts})
|
|
88
|
+
elif mtype == "gemini":
|
|
89
|
+
model = m.get("model")
|
|
90
|
+
timeline.append({
|
|
91
|
+
"kind": "assistant",
|
|
92
|
+
"text": content,
|
|
93
|
+
"ts": ts,
|
|
94
|
+
"model": str(model) if model else "",
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
user_indices = [i for i, t in enumerate(timeline) if t["kind"] == "user"]
|
|
98
|
+
records: list[dict[str, Any]] = []
|
|
99
|
+
for n, idx in enumerate(user_indices):
|
|
100
|
+
turn = timeline[idx]
|
|
101
|
+
next_user = user_indices[n + 1] if n + 1 < len(user_indices) else len(timeline)
|
|
102
|
+
|
|
103
|
+
# Model that answered this prompt: the first assistant reply after it.
|
|
104
|
+
model = ""
|
|
105
|
+
context_after_assistant = ""
|
|
106
|
+
for j in range(idx + 1, next_user):
|
|
107
|
+
if timeline[j]["kind"] == "assistant":
|
|
108
|
+
model = timeline[j].get("model", "") or model
|
|
109
|
+
if not context_after_assistant:
|
|
110
|
+
context_after_assistant = timeline[j]["text"]
|
|
111
|
+
# context_before: last assistant text strictly before this prompt.
|
|
112
|
+
context_before = ""
|
|
113
|
+
for j in range(idx - 1, -1, -1):
|
|
114
|
+
if timeline[j]["kind"] == "assistant":
|
|
115
|
+
context_before = timeline[j]["text"]
|
|
116
|
+
break
|
|
117
|
+
|
|
118
|
+
followed_by_correction = False
|
|
119
|
+
correction_text = ""
|
|
120
|
+
if next_user < len(timeline):
|
|
121
|
+
nxt = timeline[next_user]["text"]
|
|
122
|
+
if CORRECTION_RE.search(nxt):
|
|
123
|
+
followed_by_correction = True
|
|
124
|
+
correction_text = nxt
|
|
125
|
+
|
|
126
|
+
records.append({
|
|
111
127
|
"runtime": self.RUNTIME_ID,
|
|
112
128
|
"session_file": str(path),
|
|
113
|
-
"prompt_text": text,
|
|
114
|
-
"timestamp": ts,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
record
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
"prompt_text": turn["text"],
|
|
130
|
+
"timestamp": turn.get("ts"),
|
|
131
|
+
"model": model,
|
|
132
|
+
"context_before": context_before,
|
|
133
|
+
# This format does not record tool errors — be honest, not invent.
|
|
134
|
+
"followed_by_error": False,
|
|
135
|
+
"error_was_recovered": False,
|
|
136
|
+
"followed_by_correction": followed_by_correction,
|
|
137
|
+
"correction_text": correction_text,
|
|
138
|
+
"error_tool": "",
|
|
139
|
+
"error_text": "",
|
|
140
|
+
})
|
|
120
141
|
|
|
121
142
|
return records
|
|
@@ -33,6 +33,7 @@ from typing import Any
|
|
|
33
33
|
# ---------------------------------------------------------------------------
|
|
34
34
|
_TOOLS_DIR = Path(__file__).parent
|
|
35
35
|
sys.path.insert(0, str(_TOOLS_DIR))
|
|
36
|
+
from adapters.base import CORRECTION_RE
|
|
36
37
|
from adapters.registry import get_adapters, list_runtime_ids
|
|
37
38
|
|
|
38
39
|
# ---------------------------------------------------------------------------
|
|
@@ -42,15 +43,11 @@ from adapters.registry import get_adapters, list_runtime_ids
|
|
|
42
43
|
MAX_PROMPTS = 300
|
|
43
44
|
PROMPT_TEXT_LIMIT = 1500
|
|
44
45
|
CORRECTION_TEXT_LIMIT = 500
|
|
46
|
+
ERROR_TEXT_LIMIT = 500
|
|
45
47
|
CONTEXT_BEFORE_LIMIT = 500
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
r"I said|I meant|I asked|that'?s not|please don'?t|why did you|"
|
|
50
|
-
r"you should have|that was wrong|incorrect|try again|redo|"
|
|
51
|
-
r"that broke|you broke|revert|undo)\b",
|
|
52
|
-
re.IGNORECASE,
|
|
53
|
-
)
|
|
49
|
+
# Single source of truth for the correction heuristic — shared with every adapter.
|
|
50
|
+
CORRECTION_PATTERNS = CORRECTION_RE
|
|
54
51
|
|
|
55
52
|
# Model tier classification (provider-neutral labels).
|
|
56
53
|
# Maps substrings found in model IDs to tier names.
|
|
@@ -144,13 +141,15 @@ def normalise_record(raw: dict[str, Any], position: int, total: int) -> dict[str
|
|
|
144
141
|
"has_xml_tags": has_xml_tags,
|
|
145
142
|
"has_file_paths": has_file_paths,
|
|
146
143
|
"has_code_blocks": has_code_blocks,
|
|
147
|
-
#
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
144
|
+
# Error/correction context: honored from the adapter when it does its own
|
|
145
|
+
# session-aware detection (codex, gemini); defaults to False for adapters
|
|
146
|
+
# that only surface raw prompts. Claude uses process_claude_sessions instead.
|
|
147
|
+
"followed_by_error": bool(raw.get("followed_by_error", False)),
|
|
148
|
+
"error_was_recovered": bool(raw.get("error_was_recovered", False)),
|
|
149
|
+
"followed_by_correction": bool(raw.get("followed_by_correction", False)),
|
|
150
|
+
"correction_text": truncate(raw.get("correction_text", ""), CORRECTION_TEXT_LIMIT),
|
|
151
|
+
"error_tool": raw.get("error_tool", "") or "",
|
|
152
|
+
"error_text": truncate(raw.get("error_text", ""), ERROR_TEXT_LIMIT),
|
|
154
153
|
"context_before": truncate(raw.get("context_before", ""), CONTEXT_BEFORE_LIMIT),
|
|
155
154
|
# Compute fields (best-effort from adapter)
|
|
156
155
|
"model": model_id,
|
|
@@ -63,8 +63,13 @@ stranger's voice that contradicts what already won.
|
|
|
63
63
|
4. **No script or no settled angle? Route OUT** (see Handoffs). Never invent here.
|
|
64
64
|
|
|
65
65
|
Persist the package under `02-DOCS/wiki/shortform/` and raw test results under
|
|
66
|
-
`02-DOCS/raw/shortform/`.
|
|
67
|
-
|
|
66
|
+
`02-DOCS/raw/shortform/`. The `shortform/` wiki tree is an **OKF v0.1 bundle** shared
|
|
67
|
+
with the `tiktok-api` and `shortform-strategy` siblings: each persisted package `.md`
|
|
68
|
+
leads with YAML frontmatter carrying a non-empty `type: shortform-package` (plus the
|
|
69
|
+
recommended `title`/`tags`/`timestamp`), and any cross-references use standard markdown
|
|
70
|
+
links, never `[[wikilinks]]`. The raw feedback log under `02-DOCS/raw/shortform/` is
|
|
71
|
+
`raw/`, not `wiki/` — no frontmatter required. The full frontmatter + pattern depth
|
|
72
|
+
lives in [references/package-templates.md](references/package-templates.md).
|
|
68
73
|
|
|
69
74
|
## The two-second decision
|
|
70
75
|
|
|
@@ -6,10 +6,21 @@ filled `02-DOCS` feedback-log schema.
|
|
|
6
6
|
|
|
7
7
|
## The package file format
|
|
8
8
|
|
|
9
|
-
Emit one
|
|
10
|
-
|
|
9
|
+
Emit one markdown file per short under `02-DOCS/wiki/shortform/`. The `shortform/`
|
|
10
|
+
wiki tree is an **OKF v0.1 bundle** shared with the `tiktok-api` and
|
|
11
|
+
`shortform-strategy` siblings, so the file leads with YAML frontmatter (a non-empty
|
|
12
|
+
`type` is the only OKF hard requirement; `title`/`tags`/`timestamp` are the recommended
|
|
13
|
+
surface). The labeled body below is unchanged — `scripts/verify.sh` lints exactly these
|
|
14
|
+
labels (English or ES/CA, one per line, content following) and ignores the frontmatter:
|
|
11
15
|
|
|
12
16
|
```text
|
|
17
|
+
---
|
|
18
|
+
type: shortform-package
|
|
19
|
+
title: faceless productivity TikTok — package
|
|
20
|
+
tags: [shortform, packaging, tiktok]
|
|
21
|
+
timestamp: 2026-06-02T12:00:00Z
|
|
22
|
+
platform: tiktok
|
|
23
|
+
---
|
|
13
24
|
Hook:
|
|
14
25
|
- I deleted my to-do app. Here's why.
|
|
15
26
|
- The app that runs my whole week (it's free)
|
|
@@ -113,9 +113,21 @@ Every decision must end up in the ledger so next month's call is grounded.
|
|
|
113
113
|
|
|
114
114
|
### Decision record format
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
`02-DOCS/wiki/shortform/decisions.md` is an OKF append-log: write the frontmatter
|
|
117
|
+
header once (a non-empty `type` is the only OKF hard requirement; `title`/`tags`/`timestamp`
|
|
118
|
+
are the recommended surface), then **prepend each new dated block newest-first** so the
|
|
119
|
+
latest call is on top. Use ISO 8601 dates; never edit a past block. Bump the header
|
|
120
|
+
`timestamp` on each append. Header + one block:
|
|
117
121
|
|
|
118
122
|
```markdown
|
|
123
|
+
---
|
|
124
|
+
type: shortform-decision-log
|
|
125
|
+
title: Shortform strategy decisions
|
|
126
|
+
tags: [shortform, strategy, decisions]
|
|
127
|
+
timestamp: 2026-06-02T10:00:00Z
|
|
128
|
+
---
|
|
129
|
+
# Shortform strategy decisions
|
|
130
|
+
|
|
119
131
|
## 2026-06-02 — Drop daily posting to 4 Reels/week
|
|
120
132
|
**Decision:** Cut from ~7 thin posts/week to 4 strong Reels/week, fixed Tue/Fri.
|
|
121
133
|
**Rationale:** Reach fell as volume rose; consistency > velocity past the ceiling.
|
|
@@ -140,10 +152,22 @@ Append every call to `02-DOCS/wiki/shortform/decisions.md` as a dated block:
|
|
|
140
152
|
|
|
141
153
|
## Persist it
|
|
142
154
|
|
|
143
|
-
Write the account's brain back so it compounds (mirrors the harness Karpathy-wiki convention):
|
|
155
|
+
Write the account's brain back so it compounds (mirrors the harness Karpathy-wiki convention). The `shortform/` wiki tree is an **OKF v0.1 bundle** shared with the `tiktok-api` and `shortform-packaging` siblings: every `.md` written here carries YAML frontmatter with a non-empty `type`, cross-references are standard markdown links (never `[[wikilinks]]`), and `decisions.md` follows the OKF newest-first append-log rule.
|
|
156
|
+
|
|
157
|
+
- `02-DOCS/wiki/shortform/strategy.md` — positioning, cadence/mix, KPI, length bands, current series. Lead with frontmatter; the body keeps the H1 + sections the linter checks:
|
|
158
|
+
|
|
159
|
+
```markdown
|
|
160
|
+
---
|
|
161
|
+
type: shortform-strategy
|
|
162
|
+
title: Shortform account strategy
|
|
163
|
+
tags: [shortform, strategy, positioning, cadence]
|
|
164
|
+
timestamp: 2026-06-02T10:00:00Z
|
|
165
|
+
---
|
|
166
|
+
# Shortform account strategy
|
|
167
|
+
...
|
|
168
|
+
```
|
|
144
169
|
|
|
145
|
-
- `02-DOCS/wiki/shortform/
|
|
146
|
-
- `02-DOCS/
|
|
147
|
-
- `02-DOCS/raw/shortform/metrics.csv` — the per-post ledger (schema in `references/learning-loop-template.md`).
|
|
170
|
+
- `02-DOCS/wiki/shortform/decisions.md` — the dated decision log, OKF append-log (`type: shortform-decision-log`, newest block on top; format above).
|
|
171
|
+
- `02-DOCS/raw/shortform/metrics.csv` — the per-post ledger (schema in `references/learning-loop-template.md`). This is `raw/`, not `wiki/` — a CSV, no frontmatter.
|
|
148
172
|
|
|
149
|
-
Verify a produced strategy doc with `scripts/verify.sh 02-DOCS/wiki/shortform/strategy.md` — a read-only structural lint (positioning, cadence, completion KPI, ride-or-skip rule, ≥1 dated decision, sends/saves logged).
|
|
173
|
+
Verify a produced strategy doc with `scripts/verify.sh 02-DOCS/wiki/shortform/strategy.md` — a read-only structural lint (positioning, cadence, completion KPI, ride-or-skip rule, ≥1 dated decision, sends/saves logged). The lint reads body content, so the added frontmatter does not affect it.
|
|
@@ -38,9 +38,17 @@ Run once a month, reading `02-DOCS/raw/shortform/metrics.csv`:
|
|
|
38
38
|
|
|
39
39
|
## Decision-record template
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
`02-DOCS/wiki/shortform/decisions.md` is an OKF v0.1 append-log: the frontmatter header (a non-empty `type` is the only OKF hard requirement; `title`/`tags`/`timestamp` are recommended) is written once, then each new block is **prepended newest-first** under the H1. Every block is dated (ISO 8601), rests on a named metric, and carries a revisit date so it is an experiment, not folklore. Past blocks are immutable; bump the header `timestamp` on each append.
|
|
42
42
|
|
|
43
43
|
```markdown
|
|
44
|
+
---
|
|
45
|
+
type: shortform-decision-log
|
|
46
|
+
title: Shortform strategy decisions
|
|
47
|
+
tags: [shortform, strategy, decisions]
|
|
48
|
+
timestamp: YYYY-MM-DDTHH:MM:SSZ
|
|
49
|
+
---
|
|
50
|
+
# Shortform strategy decisions
|
|
51
|
+
|
|
44
52
|
## YYYY-MM-DD — <short decision title>
|
|
45
53
|
**Decision:** <what changes, concretely — cadence/positioning/series/trend rule>
|
|
46
54
|
**Rationale:** <the one-line why>
|
|
@@ -6,10 +6,21 @@ endpoint, library, file path, or algorithm. If you can't phrase it without
|
|
|
6
6
|
naming the tech, it's a *point to clarify*, not a requirement.
|
|
7
7
|
|
|
8
8
|
```markdown
|
|
9
|
+
---
|
|
10
|
+
type: spec
|
|
11
|
+
title: Spec — <Feature name>
|
|
12
|
+
description: WHAT and WHY for <feature> — problem, goals, behaviour, acceptance criteria.
|
|
13
|
+
tags: [sdd, spec]
|
|
14
|
+
timestamp: YYYY-MM-DDTHH:MM:SSZ
|
|
15
|
+
topic: sdd
|
|
16
|
+
slug: <slug>
|
|
17
|
+
status: draft
|
|
18
|
+
---
|
|
19
|
+
|
|
9
20
|
# Spec — <Feature name>
|
|
10
21
|
|
|
11
22
|
> Slug: `<slug>` · Status: draft · Created: <YYYY-MM-DD>
|
|
12
|
-
> Inherits:
|
|
23
|
+
> Inherits: [constitution](../constitution.md)
|
|
13
24
|
|
|
14
25
|
## Problem & why
|
|
15
26
|
<The pain, who feels it, and the cost of leaving it unsolved. One short
|
|
@@ -251,10 +251,14 @@ A pull that prints to stdout and vanishes is wasted. **Every pull appends a date
|
|
|
251
251
|
|
|
252
252
|
Filenames carry the `tiktok-` prefix because the same `shortform/` wiki may also hold Instagram and YouTube pulls — namespacing keeps platforms from colliding.
|
|
253
253
|
|
|
254
|
-
Per-pull entry template:
|
|
254
|
+
Per-pull entry template. The frontmatter is **OKF v0.1 conformant** — a non-empty `type` is the only hard requirement; `title`/`tags`/`timestamp` are the recommended OKF surface; the domain `date`/`range`/`account`/`platform`/`source` keys the siblings parse are preserved additively (OKF tolerates extra keys). `date` is the reporting day; `timestamp` is the ISO 8601 write moment:
|
|
255
255
|
|
|
256
256
|
```markdown
|
|
257
257
|
---
|
|
258
|
+
type: shortform-metrics
|
|
259
|
+
title: TikTok account snapshot — 2026-06-02
|
|
260
|
+
tags: [tiktok, metrics, snapshot]
|
|
261
|
+
timestamp: 2026-06-02T09:00:00Z
|
|
258
262
|
date: 2026-06-02
|
|
259
263
|
range: 2026-05-26..2026-06-01
|
|
260
264
|
account: <open_id>
|
|
@@ -274,7 +278,7 @@ For You 71% · Personal profile 14% · Search 7%
|
|
|
274
278
|
completion +3.1pts after the tighter cold-open; FYP share up 5pts.
|
|
275
279
|
```
|
|
276
280
|
|
|
277
|
-
Rule: **append, never overwrite.** The feedback log *is* the value — overwriting yesterday's snapshot destroys the trend the siblings need, and erases the 24–48h settling you only see across pulls. Exact file tree, naming, and how siblings read the log: `references/wiki-schema.md`.
|
|
281
|
+
Rule: **append, never overwrite.** The feedback log *is* the value — overwriting yesterday's snapshot destroys the trend the siblings need, and erases the 24–48h settling you only see across pulls. The per-video log (`videos/tiktok-<id>.md`) is an OKF append-log: write its frontmatter header once, prepend each new dated block newest-first, never edit a past block. `index.md` is the OKF reserved file — no frontmatter, standard markdown links only. Exact file tree, frontmatter, naming, and how siblings read the log: `references/wiki-schema.md`.
|
|
278
282
|
|
|
279
283
|
## 6. Rate & failure math
|
|
280
284
|
|
|
@@ -24,8 +24,19 @@ from colliding and lets a sibling grep `tiktok-*` for just this account.
|
|
|
24
24
|
|
|
25
25
|
## Account snapshot entry (one file per pull)
|
|
26
26
|
|
|
27
|
+
Frontmatter is **OKF v0.1 conformant**: a non-empty `type` is the only hard
|
|
28
|
+
requirement; `title`/`tags`/`timestamp` are the recommended OKF surface. The
|
|
29
|
+
domain keys (`date`, `range`, `account`, `platform`, `source`, `provisional`) are
|
|
30
|
+
preserved verbatim — siblings parse them, and OKF tolerates arbitrary extra keys.
|
|
31
|
+
`date` is the snapshot's reporting day; `timestamp` is the ISO 8601 moment the file
|
|
32
|
+
was written. Keep both.
|
|
33
|
+
|
|
27
34
|
```markdown
|
|
28
35
|
---
|
|
36
|
+
type: shortform-metrics
|
|
37
|
+
title: TikTok account snapshot — 2026-06-02
|
|
38
|
+
tags: [tiktok, metrics, snapshot]
|
|
39
|
+
timestamp: 2026-06-02T09:00:00Z
|
|
29
40
|
date: 2026-06-02
|
|
30
41
|
range: 2026-05-26..2026-06-01
|
|
31
42
|
account: <open_id>
|
|
@@ -50,11 +61,23 @@ completion +3.1pts after the tighter cold-open; FYP share up 5pts.
|
|
|
50
61
|
|
|
51
62
|
Newest entry on top, so a glance shows the latest. Each pull **prepends** a block —
|
|
52
63
|
this is where the 24–48h settling becomes visible (the same video's
|
|
53
|
-
`full_video_watched_rate` will drift between pulls).
|
|
64
|
+
`full_video_watched_rate` will drift between pulls). This is an OKF append-log:
|
|
65
|
+
the frontmatter header is written once and stays immutable; new dated blocks are
|
|
66
|
+
prepended **above** older ones (newest-first), past blocks are never edited, and
|
|
67
|
+
dates are ISO 8601.
|
|
54
68
|
|
|
55
69
|
```markdown
|
|
70
|
+
---
|
|
71
|
+
type: shortform-record
|
|
72
|
+
title: tiktok-7399 — "cold open hook test"
|
|
73
|
+
tags: [tiktok, video-log]
|
|
74
|
+
timestamp: 2026-06-02T09:00:00Z
|
|
75
|
+
video_id: "7399..."
|
|
76
|
+
platform: tiktok
|
|
77
|
+
posted: 2026-05-28
|
|
78
|
+
duration: 21s
|
|
79
|
+
---
|
|
56
80
|
# tiktok-7399... — "cold open hook test"
|
|
57
|
-
posted: 2026-05-28 | duration: 21s
|
|
58
81
|
|
|
59
82
|
## 2026-06-02 (final-ish)
|
|
60
83
|
views 52,140 | completion 28.4% | avg_watched 6.1s | FYP 71%
|
|
@@ -63,13 +86,18 @@ views 52,140 | completion 28.4% | avg_watched 6.1s | FYP 71%
|
|
|
63
86
|
views 18,900 | completion 24.1% | avg_watched 5.3s | FYP 64%
|
|
64
87
|
```
|
|
65
88
|
|
|
89
|
+
On each prepend, bump the header `timestamp` to the new pull's ISO moment; never
|
|
90
|
+
edit a past dated block.
|
|
91
|
+
|
|
66
92
|
## index.md
|
|
67
93
|
|
|
68
|
-
A thin pointer, not a data dump
|
|
94
|
+
A thin pointer, not a data dump. As an OKF **reserved** file it carries **no
|
|
95
|
+
frontmatter**, and its cross-references are standard markdown links (never
|
|
96
|
+
wikilinks), so an OKF consumer can follow them straight to the snapshot files:
|
|
69
97
|
|
|
70
98
|
```markdown
|
|
71
99
|
# shortform wiki — index
|
|
72
|
-
- tiktok: latest snapshot → tiktok-account-2026-06-02.md
|
|
100
|
+
- tiktok: latest snapshot → [tiktok-account-2026-06-02.md](./tiktok-account-2026-06-02.md)
|
|
73
101
|
- open questions:
|
|
74
102
|
- is the cold-open change holding completion above 27%? (watch next 2 pulls)
|
|
75
103
|
```
|
|
@@ -83,3 +111,9 @@ A thin pointer, not a data dump:
|
|
|
83
111
|
- **Namespace by platform.** `tiktok-` prefix on every file so IG/YouTube pulls coexist.
|
|
84
112
|
- **Numbers only, no interpretation.** "What changed" is a factual delta line, not a
|
|
85
113
|
recommendation — what to *do* about it belongs to `shortform-strategy`.
|
|
114
|
+
- **OKF v0.1 conformant.** Every snapshot and per-video file carries YAML frontmatter
|
|
115
|
+
with a non-empty `type` (`shortform-metrics` / `shortform-record`) plus the OKF
|
|
116
|
+
`title`/`tags`/`timestamp` surface; domain keys like `date` are kept additively. The
|
|
117
|
+
reserved `index.md` carries no frontmatter. All cross-references are standard markdown
|
|
118
|
+
links — never `[[wikilinks]]` — so the `shortform/` tree is a portable OKF bundle the
|
|
119
|
+
siblings (and any OKF consumer) read without translation.
|
package/skills/verify/SKILL.md
CHANGED
|
@@ -113,9 +113,19 @@ A criterion you "reviewed by reading the code" is not verified. Reading is `revi
|
|
|
113
113
|
|
|
114
114
|
### 4 — RECORD
|
|
115
115
|
|
|
116
|
-
Write a dated verification record to `02-DOCS/wiki/sdd/verifications/<slug>-YYYY-MM-DD.md` so the project's living knowledge carries the proof, then index it in `02-DOCS/wiki/index.md` (the Knowledge map; root `CLAUDE.md` keeps only a short pointer) under the `sdd/` topic. Keep it short and factual:
|
|
116
|
+
Write a dated verification record to `02-DOCS/wiki/sdd/verifications/<slug>-YYYY-MM-DD.md` so the project's living knowledge carries the proof, then index it in `02-DOCS/wiki/index.md` (the Knowledge map; root `CLAUDE.md` keeps only a short pointer) under the `sdd/` topic. It is an OKF v0.1 wiki article: open it with YAML frontmatter carrying a non-empty `type:`. Keep it short and factual:
|
|
117
117
|
|
|
118
118
|
```markdown
|
|
119
|
+
---
|
|
120
|
+
type: verification
|
|
121
|
+
title: Verification — <slug> — YYYY-MM-DD
|
|
122
|
+
description: Evidence-backed verdict for <slug> — stack gate, task done-checks, acceptance criteria.
|
|
123
|
+
tags: [sdd, verification]
|
|
124
|
+
timestamp: YYYY-MM-DDTHH:MM:SSZ
|
|
125
|
+
topic: sdd
|
|
126
|
+
slug: <slug>
|
|
127
|
+
---
|
|
128
|
+
|
|
119
129
|
# Verification — <slug> — YYYY-MM-DD
|
|
120
130
|
|
|
121
131
|
## Stack gate
|
|
@@ -215,10 +215,15 @@ A pull that prints to stdout and vanishes is wasted. **Every pull appends a date
|
|
|
215
215
|
videos/<VIDEO_ID>.md # per-video running log, newest entry on top
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
Per-pull entry template:
|
|
218
|
+
Per-pull entry template (OKF v0.1: non-empty `type` + the domain keys siblings parse):
|
|
219
219
|
|
|
220
220
|
```markdown
|
|
221
221
|
---
|
|
222
|
+
type: youtube-metrics
|
|
223
|
+
title: Channel snapshot 2026-05-31
|
|
224
|
+
description: Channel KPIs, traffic and retention for 2026-05-01..2026-05-31.
|
|
225
|
+
tags: [youtube, channel-snapshot]
|
|
226
|
+
timestamp: 2026-05-31T00:00:00Z
|
|
222
227
|
date: 2026-05-31
|
|
223
228
|
range: 2026-05-01..2026-05-31
|
|
224
229
|
channel: MINE
|
|
@@ -237,7 +242,7 @@ Sharp drop 0.00→0.06 (intro), recovers, second dip ~0.55.
|
|
|
237
242
|
avg_view_pct +2.1pts; SUGGESTED share up 6pts after the Ep.13 packaging change.
|
|
238
243
|
```
|
|
239
244
|
|
|
240
|
-
Rule: **append, never overwrite.** The feedback log *is* the value — overwriting yesterday's snapshot destroys the trend the siblings need. Exact file tree, naming, and how siblings read the log: `references/wiki-schema.md`.
|
|
245
|
+
Rule: **append, never overwrite.** The feedback log *is* the value — overwriting yesterday's snapshot destroys the trend the siblings need. `02-DOCS/wiki/` is an OKF v0.1 bundle: keep the domain keys (`date`, `range`, `channel`, `source`) the siblings parse, add `type`/`timestamp` alongside them, use standard markdown links (never `[[wikilinks]]`), and leave the reserved `index.md` frontmatter-free. Exact file tree, naming, per-video log shape, and how siblings read the log: `references/wiki-schema.md`.
|
|
241
246
|
|
|
242
247
|
## 7. Quota & failure math
|
|
243
248
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
The point of this skill is not to print stats once; it is to grow a **feedback log** the strategy/packaging siblings can read. The log lives under `02-DOCS/wiki/youtube/` and every pull **appends** to it.
|
|
4
4
|
|
|
5
|
+
`02-DOCS/wiki/` is an **OKF v0.1** bundle, so every non-reserved `.md` file written here carries YAML frontmatter with a non-empty `type`. This skill writes `type: youtube-metrics` on its snapshots/logs and keeps the domain keys (`date`, `range`, `channel`, `source`) that the strategy/packaging siblings parse literally — `type` and `timestamp` are added *alongside* them, never replacing them. OKF allows arbitrary extra keys, so the shared keys stay stable. Links between wiki files are standard markdown links (`[text](./file.md)`), never Obsidian wikilinks.
|
|
6
|
+
|
|
5
7
|
## File tree
|
|
6
8
|
|
|
7
9
|
```text
|
|
@@ -24,6 +26,11 @@ Naming:
|
|
|
24
26
|
|
|
25
27
|
```markdown
|
|
26
28
|
---
|
|
29
|
+
type: youtube-metrics
|
|
30
|
+
title: Channel snapshot 2026-05-31
|
|
31
|
+
description: Channel KPIs and traffic for 2026-05-01..2026-05-31.
|
|
32
|
+
tags: [youtube, channel-snapshot]
|
|
33
|
+
timestamp: 2026-05-31T00:00:00Z
|
|
27
34
|
date: 2026-05-31
|
|
28
35
|
range: 2026-05-01..2026-05-31
|
|
29
36
|
channel: MINE
|
|
@@ -40,9 +47,20 @@ BROWSE 44% · SUGGESTED 31% · YT_SEARCH 12%
|
|
|
40
47
|
avg_view_pct +2.1pts; SUGGESTED share +6pts after the Ep.13 packaging change.
|
|
41
48
|
```
|
|
42
49
|
|
|
43
|
-
## Per-video
|
|
50
|
+
## Per-video log file — `videos/<VIDEO_ID>.md`
|
|
51
|
+
|
|
52
|
+
The file carries frontmatter once (at the top); each new dated block is **prepended directly under the frontmatter** (newest entry first — this is an append-log, past entries are immutable). On the first write, create the frontmatter; on every later pull, refresh `timestamp` and insert the new block above the previous newest one. Keep the domain key `channel` so siblings can parse it.
|
|
44
53
|
|
|
45
54
|
```markdown
|
|
55
|
+
---
|
|
56
|
+
type: youtube-metrics
|
|
57
|
+
title: Video log — dQw4w9WgXcQ
|
|
58
|
+
description: Per-pull KPI/traffic/retention log for video dQw4w9WgXcQ, newest entry first.
|
|
59
|
+
tags: [youtube, video-log]
|
|
60
|
+
timestamp: 2026-05-31T00:00:00Z
|
|
61
|
+
channel: MINE
|
|
62
|
+
source: youtube-analytics-api-v2
|
|
63
|
+
---
|
|
46
64
|
## 2026-05-31 (range 2026-05-01..2026-05-31)
|
|
47
65
|
views: 8,902 | avg_view_pct: 41.0% | ctr(studio): 6.2%
|
|
48
66
|
traffic: SUGGESTED 52% · BROWSE 23% · YT_SEARCH 9%
|
|
@@ -52,10 +70,12 @@ note: held suggested-traffic share week over week.
|
|
|
52
70
|
|
|
53
71
|
## index.md
|
|
54
72
|
|
|
73
|
+
OKF **reserved file**: a directory listing, so it carries **no frontmatter**. Point at the latest snapshot and tracked videos with standard markdown links (never wikilinks).
|
|
74
|
+
|
|
55
75
|
```markdown
|
|
56
76
|
# YouTube channel feedback log
|
|
57
|
-
latest snapshot: channel-2026-05-31.md
|
|
58
|
-
videos tracked: dQw4w9WgXcQ, abc123XYZ
|
|
77
|
+
latest snapshot: [channel-2026-05-31.md](./channel-2026-05-31.md)
|
|
78
|
+
videos tracked: [dQw4w9WgXcQ](./videos/dQw4w9WgXcQ.md), [abc123XYZ](./videos/abc123XYZ.md)
|
|
59
79
|
|
|
60
80
|
## Open questions for strategy/packaging
|
|
61
81
|
- Intro drop-off at 0.06 recurs across last 3 uploads — packaging or pacing?
|
|
@@ -67,4 +87,5 @@ videos tracked: dQw4w9WgXcQ, abc123XYZ
|
|
|
67
87
|
1. **Append, never overwrite.** A snapshot is a point in time; deleting it erases the trend. The history is the deliverable.
|
|
68
88
|
2. **One source of truth for derived numbers.** Store what the API returned. Mark Studio-sourced values explicitly (`ctr(studio)`) so a reader knows it was not API-derived.
|
|
69
89
|
3. **No interpretation in the log beyond a one-line "what changed."** Deeper reading ("we should change the hook") is the strategy/packaging sibling's job — this skill records, it does not prescribe.
|
|
70
|
-
4. **Siblings read this tree.** Keep frontmatter keys stable (`date`, `range`, `channel`, `source`) so a sibling can parse the latest snapshot without guessing.
|
|
90
|
+
4. **Siblings read this tree.** Keep the domain frontmatter keys stable (`date`, `range`, `channel`, `source`) so a sibling can parse the latest snapshot without guessing. The OKF keys (`type`, `title`, `description`, `tags`, `timestamp`) are added alongside them, never instead of them — all four youtube skills share this same key set and the same markdown-link style.
|
|
91
|
+
5. **OKF-conformant wiki content.** Every non-reserved `.md` here has a non-empty `type` (`youtube-metrics`). `index.md` is the lone reserved file (no frontmatter, a directory listing). Per-video logs are newest-first append-logs with ISO 8601 dates; past dated blocks are never edited. Cross-references use standard markdown links (`[text](./file.md)`), never `[[wikilinks]]`.
|