@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.
Files changed (65) hide show
  1. package/README.md +20 -0
  2. package/manifest.json +1 -1
  3. package/package.json +1 -1
  4. package/skills/agent-eval/SKILL.md +1 -1
  5. package/skills/analyze/SKILL.md +1 -1
  6. package/skills/brand-voice/SKILL.md +1 -1
  7. package/skills/brand-voice/references/voice-guide-template.md +14 -0
  8. package/skills/building-agents/SKILL.md +2 -2
  9. package/skills/competitor-watch/SKILL.md +9 -4
  10. package/skills/competitor-watch/references/tracker-schema.md +19 -0
  11. package/skills/constitution/references/constitution-template.md +10 -0
  12. package/skills/content-engine/SKILL.md +1 -1
  13. package/skills/content-engine/references/brief-and-pipeline.md +15 -1
  14. package/skills/course-builder/references/grounding-and-scoping.md +23 -0
  15. package/skills/course-storytelling/references/learner-grounding.md +22 -2
  16. package/skills/decision-records/SKILL.md +14 -1
  17. package/skills/decision-records/references/templates.md +20 -0
  18. package/skills/design/references/brand-grounding.md +11 -1
  19. package/skills/harness/SKILL.md +3 -3
  20. package/skills/harness/references/daily-curation-automation.md +8 -6
  21. package/skills/harness/references/obsidian-scaffolding.md +16 -9
  22. package/skills/harness/references/wiki-article-template.md +31 -14
  23. package/skills/harness/references/wiki-index-template.md +7 -3
  24. package/skills/harness/references/wiki-protocol.md +120 -54
  25. package/skills/harness/references/wiki-worklog-template.md +5 -1
  26. package/skills/instagram-api/SKILL.md +8 -1
  27. package/skills/instagram-api/references/insights-metrics.md +8 -1
  28. package/skills/knowledge-ops/SKILL.md +9 -9
  29. package/skills/knowledge-ops/evals/cases.yaml +1 -1
  30. package/skills/knowledge-ops/references/gardening-playbook.md +18 -8
  31. package/skills/linkedin-api/SKILL.md +10 -1
  32. package/skills/linkedin-strategy/SKILL.md +12 -0
  33. package/skills/linkedin-strategy/references/wiki-records.md +27 -1
  34. package/skills/marketing/references/brand-grounding.md +22 -2
  35. package/skills/medium-strategy/SKILL.md +12 -0
  36. package/skills/medium-strategy/references/wiki-records.md +39 -1
  37. package/skills/medium-writing/SKILL.md +19 -0
  38. package/skills/plan/references/plan-template.md +12 -1
  39. package/skills/roast-me/tools/adapters/__pycache__/__init__.cpython-312.pyc +0 -0
  40. package/skills/roast-me/tools/adapters/__pycache__/base.cpython-312.pyc +0 -0
  41. package/skills/roast-me/tools/adapters/__pycache__/claude.cpython-312.pyc +0 -0
  42. package/skills/roast-me/tools/adapters/__pycache__/codex.cpython-312.pyc +0 -0
  43. package/skills/roast-me/tools/adapters/__pycache__/gemini.cpython-312.pyc +0 -0
  44. package/skills/roast-me/tools/adapters/__pycache__/registry.cpython-312.pyc +0 -0
  45. package/skills/roast-me/tools/adapters/base.py +12 -0
  46. package/skills/roast-me/tools/adapters/codex.py +146 -53
  47. package/skills/roast-me/tools/adapters/gemini.py +102 -81
  48. package/skills/roast-me/tools/extract_prompts.py +13 -14
  49. package/skills/shortform-packaging/SKILL.md +7 -2
  50. package/skills/shortform-packaging/references/package-templates.md +13 -2
  51. package/skills/shortform-strategy/SKILL.md +30 -6
  52. package/skills/shortform-strategy/references/learning-loop-template.md +9 -1
  53. package/skills/specify/references/spec-template.md +12 -1
  54. package/skills/tiktok-api/SKILL.md +6 -2
  55. package/skills/tiktok-api/references/wiki-schema.md +38 -4
  56. package/skills/verify/SKILL.md +11 -1
  57. package/skills/youtube-api/SKILL.md +7 -2
  58. package/skills/youtube-api/references/wiki-schema.md +25 -4
  59. package/skills/youtube-packaging/SKILL.md +10 -2
  60. package/skills/youtube-packaging/references/description-and-chapters.md +70 -2
  61. package/skills/youtube-strategy/SKILL.md +9 -2
  62. package/skills/youtube-strategy/references/wiki-records.md +39 -6
  63. package/skills/youtube-strategy/scripts/verify.sh +4 -0
  64. package/skills/youtube-thumbnails/SKILL.md +10 -5
  65. 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/*.jsonl
4
- Format: JSON Lines. Each line is a MessageRecord.
5
-
6
- CONFIRMED (2026-06, via official Gemini CLI docs at google-gemini.github.io
7
- and github.com/google-gemini/gemini-cli session-management.md):
8
- - Base path: ~/.gemini/tmp/
9
- - Subdirectory: <project_hash>/chats/
10
- - File format: JSONL
11
- - MessageRecord fields include: sessionId, projectHash, model, role,
12
- content (array of {text: "..."}), and token usage fields.
13
-
14
- STUBBED (exact MessageRecord schema):
15
- The exact JSONL schema per line has not been verified against a live Gemini
16
- CLI installation. The role/content structure is inferred from the official
17
- session-management docs. If the schema differs, parse() returns [] gracefully.
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 *.jsonl chat files under ~/.gemini/tmp/<hash>/chats/."""
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
- # Pattern: ~/.gemini/tmp/<project_hash>/chats/*.jsonl
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 jsonl.is_file():
47
- files.append(jsonl)
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 CLI chat JSONL file.
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
- lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
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
- for line in lines:
67
- line = line.strip()
68
- if not line:
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
- # STUBBED: inferred from gemini-cli session-management docs.
76
- role = obj.get("role", "")
77
- if role not in ("user", "USER"):
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 = obj.get("content", [])
81
- if isinstance(content, list):
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
- # Gemini MessageRecord may carry token counts and model name.
98
- model = obj.get("model") or obj.get("modelVersion")
99
- ts = obj.get("timestamp") or obj.get("createTime")
100
- if isinstance(ts, str):
101
- try:
102
- import datetime
103
- dt = datetime.datetime.fromisoformat(ts.replace("Z", "+00:00"))
104
- ts = dt.timestamp()
105
- except (ValueError, AttributeError):
106
- ts = None
107
- elif not isinstance(ts, (int, float)):
108
- ts = None
109
-
110
- record: dict[str, Any] = {
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
- if model:
117
- record["model"] = str(model)
118
-
119
- records.append(record)
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
- CORRECTION_PATTERNS = re.compile(
48
- r"\b(no[,.]?\s|wrong|instead|actually|don'?t|shouldn'?t|stop|not that|"
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
- # These will be populated by post-processing when session context is available.
148
- "followed_by_error": False,
149
- "error_was_recovered": False,
150
- "followed_by_correction": False,
151
- "correction_text": "",
152
- "error_tool": "",
153
- "error_text": "",
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/`. Pattern depth lives in
67
- [references/package-templates.md](references/package-templates.md).
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 plain-text/markdown block per short. `scripts/verify.sh` lints exactly these
10
- labels (English or ES/CA, one per line, content following):
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
- Append every call to `02-DOCS/wiki/shortform/decisions.md` as a dated block:
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/strategy.md` — positioning, cadence/mix, KPI, length bands, current series.
146
- - `02-DOCS/wiki/shortform/decisions.md` — the dated decision log (format above).
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
- Append to `02-DOCS/wiki/shortform/decisions.md`. Every block is dated, rests on a named metric, and carries a revisit date so it is an experiment, not folklore.
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: 02-DOCS/wiki/sdd/constitution.md
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.
@@ -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 entry template (prepended to `videos/<id>.md`)
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]]`.