From 356391a7ecbaeea26a1cef2f6eea0ee172f60733 Mon Sep 17 00:00:00 2001 From: "Runneth (for Vamsi)" Date: Mon, 6 Jul 2026 02:38:18 -0400 Subject: [PATCH 01/25] feat(package): add context-kit --- package-index.json | 22 +++- .../context-kit/data/context-kit-state.json | 22 ++++ .../context-kit/apps/context-kit/package.json | 12 ++ .../apps/context-kit/src/pages/index.astro | 117 ++++++++++++++++++ .../brain/context-kit/context-kit-state.json | 22 ++++ .../brain/context-kit/guardrails.md | 16 +++ .../brain/context-kit/legal-compliance.md | 18 +++ .../brain/context-kit/source-of-truth.md | 16 +++ .../brain/templates/briefing-template.md | 11 ++ packages/context-kit/instructions/behavior.md | 39 ++++++ packages/context-kit/runneth-package.json | 19 +++ packages/context-kit/skills/SKILL.md | 59 +++++++++ 12 files changed, 371 insertions(+), 2 deletions(-) create mode 100644 packages/context-kit/apps/context-kit/data/context-kit-state.json create mode 100644 packages/context-kit/apps/context-kit/package.json create mode 100644 packages/context-kit/apps/context-kit/src/pages/index.astro create mode 100644 packages/context-kit/brain/context-kit/context-kit-state.json create mode 100644 packages/context-kit/brain/context-kit/guardrails.md create mode 100644 packages/context-kit/brain/context-kit/legal-compliance.md create mode 100644 packages/context-kit/brain/context-kit/source-of-truth.md create mode 100644 packages/context-kit/brain/templates/briefing-template.md create mode 100644 packages/context-kit/instructions/behavior.md create mode 100644 packages/context-kit/runneth-package.json create mode 100644 packages/context-kit/skills/SKILL.md diff --git a/package-index.json b/package-index.json index ddfb614d..528af26b 100644 --- a/package-index.json +++ b/package-index.json @@ -1,5 +1,23 @@ { "schemaVersion": 1, - "indexRevision": "initial-empty", - "packages": [] + "indexRevision": "context-kit-0.1.0", + "packages": [ + { + "id": "context-kit", + "name": "Context Kit", + "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", + "version": "0.1.0", + "categories": ["baseline"], + "packageManagerVersion": 1, + "source": { + "type": "github", + "owner": "Motion-Creative", + "repo": "runneth-apps", + "ref": "main", + "path": "packages/context-kit" + }, + "updatePolicy": "manual", + "uninstallPolicy": "allowed" + } + ] } diff --git a/packages/context-kit/apps/context-kit/data/context-kit-state.json b/packages/context-kit/apps/context-kit/data/context-kit-state.json new file mode 100644 index 00000000..48cb2bf7 --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/context-kit-state.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "levels": [ + { "id": "L1", "name": "Answers questions", "state": "passed" }, + { "id": "L2", "name": "On-brand outputs", "state": "current" }, + { "id": "L3", "name": "Runs with full context", "state": "locked" } + ], + "items": [ + { "id": "brand-context", "label": "Brand context", "bucket": "A", "status": "inferred", "why": "Keeps every output unmistakably on-brand." }, + { "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", "status": "inferred", "why": "Every readout leads with the metric you care about." }, + { "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", "status": "inferred", "why": "Separates a proven winner from early noise." }, + { "id": "competitors", "label": "Competitors", "bucket": "B", "status": "missing", "why": "Angles to attack the gaps rivals leave open." }, + { "id": "products", "label": "Products & SKUs", "bucket": "B", "status": "missing", "why": "Grounds every ad in real product facts." }, + { "id": "positioning", "label": "Positioning & personas", "bucket": "B", "status": "missing", "why": "Right audience, right angle." }, + { "id": "voice", "label": "Voice & tone", "bucket": "B", "status": "missing", "why": "Copy that sounds like you wrote it." }, + { "id": "voc", "label": "Voice-of-customer", "bucket": "B", "status": "missing", "why": "Real customer language for hooks." }, + { "id": "legal", "label": "Legal & compliance", "bucket": "C", "status": "missing", "why": "Clears review on the first pass." }, + { "id": "briefing-template", "label": "Briefing template", "bucket": "C", "status": "missing", "why": "Briefs come back in your exact format." }, + { "id": "source-of-truth", "label": "Source of truth", "bucket": "C", "status": "missing", "why": "Reports never fight your dashboard." }, + { "id": "guardrails", "label": "Guardrails", "bucket": "C", "status": "missing", "why": "Always/never rules for creative." } + ] +} diff --git a/packages/context-kit/apps/context-kit/package.json b/packages/context-kit/apps/context-kit/package.json new file mode 100644 index 00000000..ebbfd498 --- /dev/null +++ b/packages/context-kit/apps/context-kit/package.json @@ -0,0 +1,12 @@ +{ + "name": "onboarding", + "private": true, + "type": "module", + "scripts": { + "build": "astro build", + "dev": "astro dev" + }, + "dependencies": { + "astro": "^4.15.0" + } +} diff --git a/packages/context-kit/apps/context-kit/src/pages/index.astro b/packages/context-kit/apps/context-kit/src/pages/index.astro new file mode 100644 index 00000000..b938ff49 --- /dev/null +++ b/packages/context-kit/apps/context-kit/src/pages/index.astro @@ -0,0 +1,117 @@ +--- +import state from "../../data/context-kit-state.json"; +const items = state.items; +const total = items.length; +const confirmed = items.filter((i) => i.status === "confirmed").length; +const pct = Math.round((confirmed / total) * 100); +const buckets = [ + { key: "A", title: "From Motion — just confirm", sub: "Runneth already pulled these from your Motion setup." }, + { key: "B", title: "Runneth can research these", sub: "We'll draft it, you correct anything that's off." }, + { key: "C", title: "Only you can answer these", sub: "The few things no data source knows." }, +]; +const statusLabel = { confirmed: "Confirmed", inferred: "Confirm this", drafted: "Review draft", imported: "Imported", missing: "Not yet" }; +--- + + + + + + Runneth | Context Kit + + + + + +
+ Onboarding +

Your Context Kit

+

The more it knows about your brand, the better every output. Confirm what we pulled, react to what we drafted, add the few things only you know.

+ +
+ {state.levels.map((l) => ( +
+ {l.state === "passed" ? "✓" : l.state === "current" ? "You're here" : "🔒"} +
Level {l.id.replace("L", "")}
+
{l.name}
+
+ ))} +
+ +
+
{confirmed}/{total}
+
+
{confirmed === total ? "Full context unlocked." : `${total - confirmed} items left to unlock full context.`}
+
Confirm the blue ones, react to drafts, fill the rest. Say "build my Context Kit" in chat.
+
+
+
+ + {buckets.map((b) => ( +
+
{b.title}
+
{b.sub}
+
+ {items.filter((i) => i.bucket === b.key).map((i) => ( +
+
{i.label}
+
{i.why}
+
+ {statusLabel[i.status]} + {i.status === "confirmed" ? "Update" : i.status === "inferred" ? "Review & confirm" : "Add / drop file"} +
+
+ ))} +
+
+ ))} + +

This board mirrors your brain. Runneth updates it as you fill things in through chat. It doesn't store anything itself.

+
+ + diff --git a/packages/context-kit/brain/context-kit/context-kit-state.json b/packages/context-kit/brain/context-kit/context-kit-state.json new file mode 100644 index 00000000..a2ce66a5 --- /dev/null +++ b/packages/context-kit/brain/context-kit/context-kit-state.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "levels": [ + { "id": "L1", "name": "Answers questions", "state": "passed" }, + { "id": "L2", "name": "On-brand outputs", "state": "current" }, + { "id": "L3", "name": "Runs with full context", "state": "locked" } + ], + "items": [ + { "id": "brand-context", "label": "Brand context", "bucket": "A", "status": "inferred", "source": "motion brand-context", "path": "workspace-config/brand-context", "why": "Keeps every output unmistakably on-brand." }, + { "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", "status": "inferred", "source": "motion workspace-goal", "path": "workspace-config/workspace-goal", "why": "Every readout leads with the metric you care about." }, + { "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", "status": "inferred", "source": "motion spend-threshold", "path": "workspace-config/spend-threshold", "why": "Separates a proven winner from early noise." }, + { "id": "competitors", "label": "Competitors", "bucket": "B", "status": "missing", "source": "inspo + competitor-analysis", "path": "brand-audit//competitor-analysis.md", "why": "Angles to attack the gaps rivals leave open." }, + { "id": "products", "label": "Products & SKUs", "bucket": "B", "status": "missing", "source": "product-catalog", "path": "brand-audit//product-catalog.md", "why": "Grounds every ad in real product facts." }, + { "id": "positioning", "label": "Positioning & personas", "bucket": "B", "status": "missing", "source": "brand-audit strategy", "path": "brand-audit//strategy.md", "why": "Right audience, right angle, not spray-and-pray." }, + { "id": "voice", "label": "Voice & tone", "bucket": "B", "status": "missing", "source": "brand-audit", "path": "brand-audit//brand-context.md", "why": "Copy that sounds like you wrote it." }, + { "id": "voc", "label": "Voice-of-customer", "bucket": "B", "status": "missing", "source": "review-audit", "path": "brand-audit//review-audit.md", "why": "Real customer language for hooks." }, + { "id": "legal", "label": "Legal & compliance", "bucket": "C", "status": "missing", "source": "customer", "path": "onboarding/legal-compliance.md", "why": "Clears review on the first pass." }, + { "id": "briefing-template", "label": "Briefing template", "bucket": "C", "status": "missing", "source": "customer", "path": "templates/briefing-template.md", "why": "Briefs come back in your exact format." }, + { "id": "source-of-truth", "label": "Source of truth", "bucket": "C", "status": "missing", "source": "customer", "path": "onboarding/source-of-truth.md", "why": "Reports never fight your dashboard." }, + { "id": "guardrails", "label": "Guardrails", "bucket": "C", "status": "missing", "source": "customer", "path": "onboarding/guardrails.md", "why": "Always/never rules for creative." } + ] +} diff --git a/packages/context-kit/brain/context-kit/guardrails.md b/packages/context-kit/brain/context-kit/guardrails.md new file mode 100644 index 00000000..bcbd0bad --- /dev/null +++ b/packages/context-kit/brain/context-kit/guardrails.md @@ -0,0 +1,16 @@ +# Guardrails + +> Status: not yet filled. Runneth honors this before generating customer-facing output. Fill it +> through the Context Kit skill ("build my Context Kit") or tell Runneth here. + +## What belongs here +The always/never rules for how Runneth should and should not create for your brand. + +## Thought starters +- Anything Runneth should always do in creative? (a signature format, a required CTA, a mascot) +- Anything Runneth should never do? (a tone, a competitor mention, a discount level, an emoji style) +- Approval workflow: who signs off before something ships, and on what? +- Any internal targets or constraints beyond your Motion goal? + +## Your answers +_(empty)_ diff --git a/packages/context-kit/brain/context-kit/legal-compliance.md b/packages/context-kit/brain/context-kit/legal-compliance.md new file mode 100644 index 00000000..05e886d2 --- /dev/null +++ b/packages/context-kit/brain/context-kit/legal-compliance.md @@ -0,0 +1,18 @@ +# Legal & compliance + +> Status: not yet filled. Runneth reads this before any creative work. Fill it through the +> Context Kit skill ("build my Context Kit") or just tell Runneth the answers here. + +## What belongs here +The claims, words, and framings your brand is allowed and not allowed to make in ads and copy, +so creative clears review on the first pass. + +## Thought starters +- Any claims you legally cannot make? (e.g. "clinically proven", "#1", "cures", "guaranteed") +- Required disclaimers or fine print on specific claims? +- Regulated categories that apply to you? (health, finance, beauty, supplements, kids) +- Words or comparisons legal has flagged before? +- Anything you must always attribute or cite? + +## Your answers +_(empty)_ diff --git a/packages/context-kit/brain/context-kit/source-of-truth.md b/packages/context-kit/brain/context-kit/source-of-truth.md new file mode 100644 index 00000000..67557c41 --- /dev/null +++ b/packages/context-kit/brain/context-kit/source-of-truth.md @@ -0,0 +1,16 @@ +# Source of truth + +> Status: not yet filled. Runneth honors this before any performance or reporting answer. Fill it +> through the Context Kit skill ("build my Context Kit") or tell Runneth here. + +## What belongs here +Which numbers Runneth should trust when sources disagree, so reports never fight your dashboard. + +## Thought starters +- When the ad platform and your attribution tool disagree, which wins? (e.g. Triple Whale, Northbeam, GA4, platform-reported) +- What is your primary success metric, and over what attribution window? +- Any metric you explicitly do not trust or want ignored? +- Which dashboard is the one your team reviews in meetings? + +## Your answers +_(empty)_ diff --git a/packages/context-kit/brain/templates/briefing-template.md b/packages/context-kit/brain/templates/briefing-template.md new file mode 100644 index 00000000..eb346694 --- /dev/null +++ b/packages/context-kit/brain/templates/briefing-template.md @@ -0,0 +1,11 @@ +# Briefing template + +> Status: not yet filled. This is the exact format Runneth returns creative briefs in. Drop your +> current brief template during onboarding and Runneth will match it, or edit this file directly. + +## How to fill +Best path: drop your existing brief doc during "build my Context Kit" and Runneth saves it here +as the exact-match template. Otherwise, paste your preferred structure below. + +## Your template +_(empty. until filled, Runneth uses its default briefing structure.)_ diff --git a/packages/context-kit/instructions/behavior.md b/packages/context-kit/instructions/behavior.md new file mode 100644 index 00000000..6491a590 --- /dev/null +++ b/packages/context-kit/instructions/behavior.md @@ -0,0 +1,39 @@ +# Context Kit package instructions + +This package installs the Context Kit: the institutional knowledge that makes every Runneth answer +sharper. It seeds structured-but-empty scaffolds, a status index, the Context Kit board app, and the +Context Kit skill. These instructions are loaded as prompt context so Runneth knows the surface +exists and how to use it. + +## What Runneth should know from moment one + +- There is a Context Kit board app at `agent_apps/context-kit`. It is a read-only mirror of brain + completeness. Build it with `app build context-kit` before first use (package sync stages files + but does not build apps). +- State lives at `/agent/brain/context-kit/context-kit-state.json`. It is the source of truth for + what is confirmed, inferred, drafted, imported, or missing. +- The write-in scaffolds live at `/agent/brain/context-kit/` (legal-compliance.md, source-of-truth.md, + guardrails.md) and `/agent/brain/templates/briefing-template.md`. + +## Read-before-work rules (retrieval awareness) + +- Before creative work (hooks, concepts, briefs, scripts, ad copy), read + `/agent/brain/context-kit/legal-compliance.md` and `/agent/brain/templates/briefing-template.md` + if present and filled. +- Before any performance/reporting answer, honor `/agent/brain/context-kit/source-of-truth.md` + when it names which numbers to trust. +- Before generating any customer-facing output, honor `/agent/brain/context-kit/guardrails.md`. +- Bucket B context (competitors, products, positioning, voice, voice-of-customer) lives in the + existing `/agent/brain/brand-audit//` bundle and is already covered by the + creative-strategy read-before-work rules. + +## Rules + +- Never write Context Kit content into `user.md`. Fill happens as deliberate in-conversation brain + writes through the Context Kit skill. +- Prefer importing what already exists. If Google Drive or Notion is connected (or can be), offer to + find and pull the customer's existing brand, legal, brief, or positioning docs before asking them + to type anything. +- The scaffolds are create-if-absent. Never overwrite a file a customer has already filled. +- As each item is filled with real content, add or refresh its `/agent/INDEX.md` entry. Do not + blind-overwrite `INDEX.md` from a package sync. diff --git a/packages/context-kit/runneth-package.json b/packages/context-kit/runneth-package.json new file mode 100644 index 00000000..d3e4b7c8 --- /dev/null +++ b/packages/context-kit/runneth-package.json @@ -0,0 +1,19 @@ +{ + "schemaVersion": 1, + "id": "context-kit", + "name": "Context Kit", + "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", + "version": "0.1.0", + "updatePolicy": "manual", + "uninstallPolicy": "allowed", + "resources": [ + { "id": "context-kit-behavior", "type": "package_instruction", "sourcePath": "instructions/behavior.md" }, + { "id": "context-kit-state", "type": "file", "sourcePath": "brain/context-kit/context-kit-state.json", "target": { "root": "agent_brain", "path": "context-kit/context-kit-state.json" }, "executable": false }, + { "id": "legal-compliance", "type": "file", "sourcePath": "brain/context-kit/legal-compliance.md", "target": { "root": "agent_brain", "path": "context-kit/legal-compliance.md" }, "executable": false }, + { "id": "source-of-truth", "type": "file", "sourcePath": "brain/context-kit/source-of-truth.md", "target": { "root": "agent_brain", "path": "context-kit/source-of-truth.md" }, "executable": false }, + { "id": "guardrails", "type": "file", "sourcePath": "brain/context-kit/guardrails.md", "target": { "root": "agent_brain", "path": "context-kit/guardrails.md" }, "executable": false }, + { "id": "briefing-template", "type": "file", "sourcePath": "brain/templates/briefing-template.md", "target": { "root": "agent_brain", "path": "templates/briefing-template.md" }, "executable": false }, + { "id": "context-kit-skill", "type": "directory", "sourcePath": "skills", "target": { "root": "agent_skills", "path": "context-kit" }, "executablePaths": [] }, + { "id": "context-kit-app", "type": "directory", "sourcePath": "apps/context-kit", "target": { "root": "agent_apps", "path": "context-kit" }, "executablePaths": [] } + ] +} diff --git a/packages/context-kit/skills/SKILL.md b/packages/context-kit/skills/SKILL.md new file mode 100644 index 00000000..cbdd0952 --- /dev/null +++ b/packages/context-kit/skills/SKILL.md @@ -0,0 +1,59 @@ +--- +name: context-kit +description: Builds a customer's Context Kit, the institutional knowledge that makes every Runneth answer sharper. Reads the Context Kit state index, confirms what Motion already knows, imports what already lives in Google Drive or Notion, drafts what Runneth can research, and collects what only the customer knows, filling each into the brain and moving the completeness meter. Triggers on "build my context kit", "set up my context kit", "context kit", "build my brain", "sharpen Runneth", "what makes my answers better", "what do you still need from me", "what does Runneth know about us". +--- + +# Context Kit skill + +Turn a fresh brain into a filled one, without the manual sales/CS back-and-forth. The Context Kit +board app is the mirror; this skill is the doer. Never write to `user.md`. Fill happens as deliberate +in-conversation brain writes. Always prefer importing what already exists over asking the customer to +retype it. + +## Step 0 — Load state +1. Read `/agent/brain/context-kit/context-kit-state.json`. +2. Run `motion brand-context --data-query "summary"`, `motion workspace-goal`, `motion spend-threshold` + to hydrate the three Bucket A items with live values. +3. Check which context sources are connected (Google Drive, Notion) so import is offered when available. +4. Compute completeness and resolve the workspace slug for brand-audit paths. + +## Step 1 — Open the board +- If the app is not built yet, run `app build context-kit`, then `app list` for the verified URL. +- Hand the URL back. Never invent it. + +## Step 2 — Bucket A: confirm (fast) +For brand-context, kpis-goal, spend-threshold: show the live value plainly and ask for a one-tap +confirm. On confirm, set the item `confirmed`. Do not re-collect what Motion already holds. + +## Step 3 — Import mode (try this before drafting or asking, for every remaining item) +Before researching or asking a customer to type, ask: "Do you already have this written down in +Google Drive or Notion?" +- If Drive or Notion is connected, search it for the relevant doc (brand guidelines, legal/claims, + brief template, positioning, competitor list), show what you found, confirm it's the right one, + and import it into the correct brain path. Mark the item `imported` then `confirmed`. +- If not connected, offer the native connect flow first. Only fall through to draft/write-in if they + decline or don't have it. +- Lean on the existing context-sweep / integration skills to do the search + pull rather than + reinventing retrieval. + +## Step 4 — Bucket B: draft, then confirm +For competitors, products, positioning, voice, voice-of-customer not covered by an import: run the +matching skill (product-catalog, competitor-analysis, brand-audit strategy/review passes, Inspo +followed brands + boards for competitors). Show the draft, invite corrections, write into the +existing `/agent/brain/brand-audit//` bundle, mark `confirmed`. No parallel files. + +## Step 5 — Bucket C: collect (only what import/research can't cover) +For legal, briefing-template, source-of-truth, guardrails still unfilled: pick the lightest mode. +File drop when they have the doc, thought starters when stuck (scaffolds carry these), one quick +prompt when short. Write into the scaffold file, replace the `_(empty)_` block, mark `confirmed`. + +## Step 6 — Keep the map correct +- After each item is filled with real content, add or refresh its `/agent/INDEX.md` entry. +- Update `context-kit-state.json` after every change so the board stays honest. +- Re-run `app build context-kit` (or refresh its data copy) so the meter reflects new state. + +## Rules +- Never touch `user.md`. +- Never overwrite an already-filled file (create-if-absent, edit-in-place only on confirm). +- Plain, non-technical language with the customer. No file paths or JSON in chat. +- One item at a time; celebrate the Level 3 unlock when the meter completes. From fc6768f329b314909585bb425a22a90aee18be8c Mon Sep 17 00:00:00 2001 From: "Runneth (for Vamsi)" Date: Mon, 6 Jul 2026 02:41:05 -0400 Subject: [PATCH 02/25] fix(context-kit): add package.json manifest for the VM installer --- packages/context-kit/package.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/context-kit/package.json diff --git a/packages/context-kit/package.json b/packages/context-kit/package.json new file mode 100644 index 00000000..515869a6 --- /dev/null +++ b/packages/context-kit/package.json @@ -0,0 +1,20 @@ +{ + "schemaVersion": 1, + "id": "context-kit", + "name": "Context Kit", + "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", + "version": "0.1.0", + "installPolicy": "manual", + "updatePolicy": "manual", + "uninstallPolicy": "allowed", + "resources": [ + { "id": "context-kit-behavior", "type": "package_instruction", "sourcePath": "instructions/behavior.md" }, + { "id": "context-kit-state", "type": "file", "sourcePath": "brain/context-kit/context-kit-state.json", "target": { "root": "agent_brain", "path": "context-kit/context-kit-state.json" }, "executable": false }, + { "id": "legal-compliance", "type": "file", "sourcePath": "brain/context-kit/legal-compliance.md", "target": { "root": "agent_brain", "path": "context-kit/legal-compliance.md" }, "executable": false }, + { "id": "source-of-truth", "type": "file", "sourcePath": "brain/context-kit/source-of-truth.md", "target": { "root": "agent_brain", "path": "context-kit/source-of-truth.md" }, "executable": false }, + { "id": "guardrails", "type": "file", "sourcePath": "brain/context-kit/guardrails.md", "target": { "root": "agent_brain", "path": "context-kit/guardrails.md" }, "executable": false }, + { "id": "briefing-template", "type": "file", "sourcePath": "brain/templates/briefing-template.md", "target": { "root": "agent_brain", "path": "templates/briefing-template.md" }, "executable": false }, + { "id": "context-kit-skill", "type": "directory", "sourcePath": "skills", "target": { "root": "agent_skills", "path": "context-kit" }, "executablePaths": [] }, + { "id": "context-kit-app", "type": "directory", "sourcePath": "apps/context-kit", "target": { "root": "agent_apps", "path": "context-kit" }, "executablePaths": [] } + ] +} From 3ed7a9128b5cf76735032e8f3445fe7fe31e0653 Mon Sep 17 00:00:00 2001 From: "Runneth (for Vamsi)" Date: Mon, 6 Jul 2026 03:56:44 -0400 Subject: [PATCH 03/25] =?UTF-8?q?feat(context-kit):=20v0.2.0=20=E2=80=94?= =?UTF-8?q?=20bundle=20buildeth=20v3=20+=20astro=20base,=20runtime-fetch?= =?UTF-8?q?=20board=20with=20click-to-expand,=20is:global=20styles,=20data?= =?UTF-8?q?/=20scaffolds,=20richness=20+=20brand-context=20scoping=20(stag?= =?UTF-8?q?ing=20feedback)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-index.json | 4 +- packages/context-kit/README.md | 24 +++ .../apps/context-kit/astro.config.mjs | 6 + .../apps/context-kit/buildeth.app.json | 8 + .../apps/context-kit/data/competitors.md | 3 + .../context-kit/data/context-kit-state.json | 16 +- .../apps/context-kit/data/positioning.md | 3 + .../apps/context-kit/data/products.md | 3 + .../context-kit/apps/context-kit/data/voc.md | 3 + .../apps/context-kit/data/voice.md | 3 + .../apps/context-kit/src/pages/index.astro | 178 +++++++++++------- .../brain/context-kit/context-kit-state.json | 24 +-- packages/context-kit/instructions/behavior.md | 48 +++-- packages/context-kit/package.json | 2 +- packages/context-kit/runneth-package.json | 2 +- packages/context-kit/skills/SKILL.md | 74 ++++---- 16 files changed, 252 insertions(+), 149 deletions(-) create mode 100644 packages/context-kit/README.md create mode 100644 packages/context-kit/apps/context-kit/astro.config.mjs create mode 100644 packages/context-kit/apps/context-kit/buildeth.app.json create mode 100644 packages/context-kit/apps/context-kit/data/competitors.md create mode 100644 packages/context-kit/apps/context-kit/data/positioning.md create mode 100644 packages/context-kit/apps/context-kit/data/products.md create mode 100644 packages/context-kit/apps/context-kit/data/voc.md create mode 100644 packages/context-kit/apps/context-kit/data/voice.md diff --git a/package-index.json b/package-index.json index 528af26b..9006ee55 100644 --- a/package-index.json +++ b/package-index.json @@ -1,12 +1,12 @@ { "schemaVersion": 1, - "indexRevision": "context-kit-0.1.0", + "indexRevision": "context-kit-0.2.0", "packages": [ { "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.1.0", + "version": "0.2.0", "categories": ["baseline"], "packageManagerVersion": 1, "source": { diff --git a/packages/context-kit/README.md b/packages/context-kit/README.md new file mode 100644 index 00000000..9304fe47 --- /dev/null +++ b/packages/context-kit/README.md @@ -0,0 +1,24 @@ +# Context Kit package + +Builds the institutional knowledge that makes every Runneth answer sharper: brain scaffolds, a board app +(read-only mirror of completeness), and the "build my Context Kit" skill. + +## App build gotchas (learned in staging — read before editing the app) + +- **`buildeth.app.json` must be v3** with `conversationId`, `workspaceId`, `oauthEnabled: true`, + `data: { "dir": "data" }`, `static: { "dist": "dist", "index": "index.html" }`. It ships as a template + with `__CONVERSATION_ID__` / `__WORKSPACE_ID__` tokens the skill substitutes before `app build`. +- **`astro.config.mjs` must set `base: "/context-kit"`** (and `trailingSlash: "never"`). Without a base, + Astro emits absolute `/_astro/...` asset URLs and `app build` rejects the static output. +- **Use `
- Onboarding + Context Kit

Your Context Kit

-

The more it knows about your brand, the better every output. Confirm what we pulled, react to what we drafted, add the few things only you know.

- -
- {state.levels.map((l) => ( -
- {l.state === "passed" ? "✓" : l.state === "current" ? "You're here" : "🔒"} -
Level {l.id.replace("L", "")}
-
{l.name}
-
- ))} -
- -
-
{confirmed}/{total}
-
-
{confirmed === total ? "Full context unlocked." : `${total - confirmed} items left to unlock full context.`}
-
Confirm the blue ones, react to drafts, fill the rest. Say "build my Context Kit" in chat.
-
-
-
- - {buckets.map((b) => ( -
-
{b.title}
-
{b.sub}
-
- {items.filter((i) => i.bucket === b.key).map((i) => ( -
-
{i.label}
-
{i.why}
-
- {statusLabel[i.status]} - {i.status === "confirmed" ? "Update" : i.status === "inferred" ? "Review & confirm" : "Add / drop file"} -
-
- ))} -
-
- ))} - -

This board mirrors your brain. Runneth updates it as you fill things in through chat. It doesn't store anything itself.

+

The more Runneth knows about your brand, the sharper every answer. Confirm what we pulled, review what we drafted, add the few things only you know. Click any card to see what's saved.

+
+

This board mirrors your brain in real time. Runneth updates it as you fill things in through chat. It reads state on every load, so a refresh always shows the latest.

+ + diff --git a/packages/context-kit/brain/context-kit/context-kit-state.json b/packages/context-kit/brain/context-kit/context-kit-state.json index a2ce66a5..cd4798e5 100644 --- a/packages/context-kit/brain/context-kit/context-kit-state.json +++ b/packages/context-kit/brain/context-kit/context-kit-state.json @@ -6,17 +6,17 @@ { "id": "L3", "name": "Runs with full context", "state": "locked" } ], "items": [ - { "id": "brand-context", "label": "Brand context", "bucket": "A", "status": "inferred", "source": "motion brand-context", "path": "workspace-config/brand-context", "why": "Keeps every output unmistakably on-brand." }, - { "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", "status": "inferred", "source": "motion workspace-goal", "path": "workspace-config/workspace-goal", "why": "Every readout leads with the metric you care about." }, - { "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", "status": "inferred", "source": "motion spend-threshold", "path": "workspace-config/spend-threshold", "why": "Separates a proven winner from early noise." }, - { "id": "competitors", "label": "Competitors", "bucket": "B", "status": "missing", "source": "inspo + competitor-analysis", "path": "brand-audit//competitor-analysis.md", "why": "Angles to attack the gaps rivals leave open." }, - { "id": "products", "label": "Products & SKUs", "bucket": "B", "status": "missing", "source": "product-catalog", "path": "brand-audit//product-catalog.md", "why": "Grounds every ad in real product facts." }, - { "id": "positioning", "label": "Positioning & personas", "bucket": "B", "status": "missing", "source": "brand-audit strategy", "path": "brand-audit//strategy.md", "why": "Right audience, right angle, not spray-and-pray." }, - { "id": "voice", "label": "Voice & tone", "bucket": "B", "status": "missing", "source": "brand-audit", "path": "brand-audit//brand-context.md", "why": "Copy that sounds like you wrote it." }, - { "id": "voc", "label": "Voice-of-customer", "bucket": "B", "status": "missing", "source": "review-audit", "path": "brand-audit//review-audit.md", "why": "Real customer language for hooks." }, - { "id": "legal", "label": "Legal & compliance", "bucket": "C", "status": "missing", "source": "customer", "path": "onboarding/legal-compliance.md", "why": "Clears review on the first pass." }, - { "id": "briefing-template", "label": "Briefing template", "bucket": "C", "status": "missing", "source": "customer", "path": "templates/briefing-template.md", "why": "Briefs come back in your exact format." }, - { "id": "source-of-truth", "label": "Source of truth", "bucket": "C", "status": "missing", "source": "customer", "path": "onboarding/source-of-truth.md", "why": "Reports never fight your dashboard." }, - { "id": "guardrails", "label": "Guardrails", "bucket": "C", "status": "missing", "source": "customer", "path": "onboarding/guardrails.md", "why": "Always/never rules for creative." } + { "id": "brand-context", "label": "Brand context", "bucket": "A", "status": "inferred", "why": "Keeps every output unmistakably on-brand.", "preview": "" }, + { "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", "status": "inferred", "why": "Every readout leads with the metric you care about.", "preview": "" }, + { "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", "status": "inferred", "why": "Separates a proven winner from early noise.", "preview": "" }, + { "id": "competitors", "label": "Competitors", "bucket": "B", "status": "missing", "why": "Angles to attack the gaps rivals leave open.", "file": "competitors.md" }, + { "id": "products", "label": "Products & SKUs", "bucket": "B", "status": "missing", "why": "Grounds every ad in real product facts.", "file": "products.md" }, + { "id": "positioning", "label": "Positioning & personas", "bucket": "B", "status": "missing", "why": "Right audience, right angle.", "file": "positioning.md" }, + { "id": "voice", "label": "Voice & tone", "bucket": "B", "status": "missing", "why": "Copy that sounds like you wrote it.", "file": "voice.md" }, + { "id": "voc", "label": "Voice-of-customer", "bucket": "B", "status": "missing", "why": "Real customer language for hooks.", "file": "voc.md" }, + { "id": "legal", "label": "Legal & compliance", "bucket": "C", "status": "missing", "why": "Clears review on the first pass." }, + { "id": "briefing-template", "label": "Briefing template", "bucket": "C", "status": "missing", "why": "Briefs come back in your exact format." }, + { "id": "source-of-truth", "label": "Source of truth", "bucket": "C", "status": "missing", "why": "Reports never fight your dashboard." }, + { "id": "guardrails", "label": "Guardrails", "bucket": "C", "status": "missing", "why": "Always/never rules for creative." } ] } diff --git a/packages/context-kit/instructions/behavior.md b/packages/context-kit/instructions/behavior.md index 6491a590..5493fe8e 100644 --- a/packages/context-kit/instructions/behavior.md +++ b/packages/context-kit/instructions/behavior.md @@ -2,38 +2,34 @@ This package installs the Context Kit: the institutional knowledge that makes every Runneth answer sharper. It seeds structured-but-empty scaffolds, a status index, the Context Kit board app, and the -Context Kit skill. These instructions are loaded as prompt context so Runneth knows the surface -exists and how to use it. +Context Kit skill. These instructions are loaded as prompt context so Runneth knows the surface exists. ## What Runneth should know from moment one -- There is a Context Kit board app at `agent_apps/context-kit`. It is a read-only mirror of brain - completeness. Build it with `app build context-kit` before first use (package sync stages files - but does not build apps). -- State lives at `/agent/brain/context-kit/context-kit-state.json`. It is the source of truth for - what is confirmed, inferred, drafted, imported, or missing. -- The write-in scaffolds live at `/agent/brain/context-kit/` (legal-compliance.md, source-of-truth.md, - guardrails.md) and `/agent/brain/templates/briefing-template.md`. +- There is a Context Kit board app at `agent_apps/context-kit`. Package sync stages files but does NOT + build apps. To build it: fill the bundled `buildeth.app.json` (replace `__CONVERSATION_ID__` and + `__WORKSPACE_ID__` with the current context), then `app build context-kit`. `astro.config.mjs` (base + `/context-kit`) ships with the package. +- The board is client-rendered: it fetches `data/context-kit-state.json` and the Bucket B `data/*.md` + files at runtime. Update those data files to change what the board shows. A rebuild is only needed for + app source changes, not content/state changes. +- State lives at `/agent/brain/context-kit/context-kit-state.json` (source of truth) and is mirrored to + `/agent/apps/context-kit/data/context-kit-state.json` (what the board reads). +- Write-in scaffolds: `/agent/brain/context-kit/` (legal-compliance.md, source-of-truth.md, guardrails.md) + and `/agent/brain/templates/briefing-template.md`. ## Read-before-work rules (retrieval awareness) -- Before creative work (hooks, concepts, briefs, scripts, ad copy), read - `/agent/brain/context-kit/legal-compliance.md` and `/agent/brain/templates/briefing-template.md` - if present and filled. -- Before any performance/reporting answer, honor `/agent/brain/context-kit/source-of-truth.md` - when it names which numbers to trust. -- Before generating any customer-facing output, honor `/agent/brain/context-kit/guardrails.md`. -- Bucket B context (competitors, products, positioning, voice, voice-of-customer) lives in the - existing `/agent/brain/brand-audit//` bundle and is already covered by the - creative-strategy read-before-work rules. +- Before creative work, read `/agent/brain/context-kit/legal-compliance.md` and + `/agent/brain/templates/briefing-template.md` if filled. +- Before performance/reporting, honor `/agent/brain/context-kit/source-of-truth.md`. +- Before customer-facing output, honor `/agent/brain/context-kit/guardrails.md`. +- Bucket B context lives in `/agent/brain/brand-audit//` and is covered by the creative-strategy + read-before-work rules. ## Rules -- Never write Context Kit content into `user.md`. Fill happens as deliberate in-conversation brain - writes through the Context Kit skill. -- Prefer importing what already exists. If Google Drive or Notion is connected (or can be), offer to - find and pull the customer's existing brand, legal, brief, or positioning docs before asking them - to type anything. -- The scaffolds are create-if-absent. Never overwrite a file a customer has already filled. -- As each item is filled with real content, add or refresh its `/agent/INDEX.md` entry. Do not - blind-overwrite `INDEX.md` from a package sync. +- Never write Context Kit content into `user.md`. Fill happens as deliberate in-conversation brain writes. +- Prefer importing existing docs from Google Drive / Notion before asking the customer to type. +- Scaffolds are create-if-absent. Never overwrite a file a customer has already filled. +- As each item is filled, refresh its `/agent/INDEX.md` entry. Do not blind-overwrite `INDEX.md` from sync. diff --git a/packages/context-kit/package.json b/packages/context-kit/package.json index 515869a6..7206fc37 100644 --- a/packages/context-kit/package.json +++ b/packages/context-kit/package.json @@ -3,7 +3,7 @@ "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.1.0", + "version": "0.2.0", "installPolicy": "manual", "updatePolicy": "manual", "uninstallPolicy": "allowed", diff --git a/packages/context-kit/runneth-package.json b/packages/context-kit/runneth-package.json index d3e4b7c8..a7518f2b 100644 --- a/packages/context-kit/runneth-package.json +++ b/packages/context-kit/runneth-package.json @@ -3,7 +3,7 @@ "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.1.0", + "version": "0.2.0", "updatePolicy": "manual", "uninstallPolicy": "allowed", "resources": [ diff --git a/packages/context-kit/skills/SKILL.md b/packages/context-kit/skills/SKILL.md index cbdd0952..64c606cd 100644 --- a/packages/context-kit/skills/SKILL.md +++ b/packages/context-kit/skills/SKILL.md @@ -15,42 +15,50 @@ retype it. 2. Run `motion brand-context --data-query "summary"`, `motion workspace-goal`, `motion spend-threshold` to hydrate the three Bucket A items with live values. 3. Check which context sources are connected (Google Drive, Notion) so import is offered when available. -4. Compute completeness and resolve the workspace slug for brand-audit paths. - -## Step 1 — Open the board -- If the app is not built yet, run `app build context-kit`, then `app list` for the verified URL. -- Hand the URL back. Never invent it. - -## Step 2 — Bucket A: confirm (fast) -For brand-context, kpis-goal, spend-threshold: show the live value plainly and ask for a one-tap -confirm. On confirm, set the item `confirmed`. Do not re-collect what Motion already holds. - -## Step 3 — Import mode (try this before drafting or asking, for every remaining item) -Before researching or asking a customer to type, ask: "Do you already have this written down in -Google Drive or Notion?" -- If Drive or Notion is connected, search it for the relevant doc (brand guidelines, legal/claims, - brief template, positioning, competitor list), show what you found, confirm it's the right one, - and import it into the correct brain path. Mark the item `imported` then `confirmed`. -- If not connected, offer the native connect flow first. Only fall through to draft/write-in if they - decline or don't have it. -- Lean on the existing context-sweep / integration skills to do the search + pull rather than - reinventing retrieval. - -## Step 4 — Bucket B: draft, then confirm -For competitors, products, positioning, voice, voice-of-customer not covered by an import: run the -matching skill (product-catalog, competitor-analysis, brand-audit strategy/review passes, Inspo -followed brands + boards for competitors). Show the draft, invite corrections, write into the -existing `/agent/brain/brand-audit//` bundle, mark `confirmed`. No parallel files. - -## Step 5 — Bucket C: collect (only what import/research can't cover) -For legal, briefing-template, source-of-truth, guardrails still unfilled: pick the lightest mode. -File drop when they have the doc, thought starters when stuck (scaffolds carry these), one quick -prompt when short. Write into the scaffold file, replace the `_(empty)_` block, mark `confirmed`. +4. Resolve the workspace slug for brand-audit paths. + +## Step 1 — Build and open the board (first run only) +Package sync stages files but does NOT build the app. On first run: +1. Fill the bundled manifest: in `/agent/apps/context-kit/buildeth.app.json`, replace `__CONVERSATION_ID__` + with the current conversation id and `__WORKSPACE_ID__` with the current workspace id. The rest of the + v3 manifest (oauthEnabled, data dir, static dist/index) and `astro.config.mjs` (base `/context-kit`) + ship in the package, so no trial-and-error is needed. +2. Run `app build context-kit`, then `app list` for the verified URL. Hand the URL back. Never invent it. +3. After the first build, the board reads `data/` at runtime (client-side fetch), so later state/content + updates show on refresh with NO rebuild. Only rebuild if you change the app source itself. + +## Step 2 — Bucket A: confirm (fast) + write the full working doc +For brand-context, kpis-goal, spend-threshold: show the live value plainly and ask a one-tap confirm. +On confirm: set the item `confirmed`, set its `preview` string in the state index (short human summary +for the card), and write the full working document. `brand-context.md` (#10) is the *foundation only*: +brand name, story, positioning statement, product description, proof points, and a 2-sentence tone/audience +summary. Do NOT concatenate the Bucket B detail into it. Do not re-collect what Motion already holds. + +## Step 3 — Import mode (try before drafting or asking, for every remaining item) +Ask: "Do you already have this in Google Drive or Notion?" If connected, search for the relevant doc, +confirm it, import into the correct brain path, set `imported` then `confirmed`. If not connected, offer +the native connect flow first. Only fall through to draft/write-in if they decline or don't have it. Lean +on existing context-sweep / integration skills for the search + pull. + +## Step 4 — Bucket B: draft rich, then confirm, then mirror to the board +For competitors, products, positioning, voice, voice-of-customer: run the matching skill (product-catalog, +competitor-analysis, brand-audit strategy/review passes, Inspo followed brands + boards for competitors). +Write FULL working documents into `/agent/brain/brand-audit//` (#9): competitor tone/audience/ +positioning breakdowns, complete product feature lists, full personas with motivations and objections, all +voice-of-customer quotes and patterns. Thin summaries fail here. Then: +- set status `drafted` (customer reviews) then `confirmed` on accept. +- MIRROR each file into `/agent/apps/context-kit/data/.md` (competitors.md, products.md, + positioning.md, voice.md, voc.md) so the board's click-to-expand can fetch it at runtime. + +## Step 5 — Bucket C: collect with depth +For legal, briefing-template, source-of-truth, guardrails: file drop when they have the doc, thought +starters when stuck, one prompt when short. Prompt for the COMPLETE rule set, not a one-liner (#9). Write +into the scaffold file, fully populating the `## Your answers` section. Mark `confirmed`. ## Step 6 — Keep the map correct - After each item is filled with real content, add or refresh its `/agent/INDEX.md` entry. -- Update `context-kit-state.json` after every change so the board stays honest. -- Re-run `app build context-kit` (or refresh its data copy) so the meter reflects new state. +- Update `/agent/brain/context-kit/context-kit-state.json` AND the app's `data/context-kit-state.json` + after every change (the board reads the app copy). No rebuild needed for data-only changes. ## Rules - Never touch `user.md`. From c543b41a4a51a76daaf313ca6969d2034d9e0cfb Mon Sep 17 00:00:00 2001 From: "Runneth (for Vamsi)" Date: Mon, 6 Jul 2026 08:32:50 -0400 Subject: [PATCH 04/25] =?UTF-8?q?feat(context-kit):=20v0.3.0=20=E2=80=94?= =?UTF-8?q?=20Motion-drafted=20brand-context=20+=20Bucket=20B,=20single=20?= =?UTF-8?q?/brain/context-kit=20folder,=20dataFile=20on=20all=20items,=20e?= =?UTF-8?q?mpty-state=20copy,=20Drive/Notion=20header=20nudge,=20help=20ba?= =?UTF-8?q?nner,=20app=20name=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-index.json | 4 +- .../apps/context-kit/data/brand-context.md | 3 + .../context-kit/data/briefing-template.md | 3 + .../context-kit/data/context-kit-state.json | 24 ++-- .../apps/context-kit/data/guardrails.md | 3 + .../apps/context-kit/data/kpis-goal.md | 3 + .../apps/context-kit/data/legal-compliance.md | 3 + .../apps/context-kit/data/source-of-truth.md | 3 + .../apps/context-kit/data/spend-threshold.md | 3 + .../context-kit/apps/context-kit/package.json | 2 +- .../apps/context-kit/src/pages/index.astro | 52 ++++++--- .../briefing-template.md | 0 .../brain/context-kit/context-kit-state.json | 24 ++-- packages/context-kit/package.json | 85 ++++++++++++-- packages/context-kit/runneth-package.json | 85 ++++++++++++-- packages/context-kit/skills/SKILL.md | 109 ++++++++++-------- 16 files changed, 297 insertions(+), 109 deletions(-) create mode 100644 packages/context-kit/apps/context-kit/data/brand-context.md create mode 100644 packages/context-kit/apps/context-kit/data/briefing-template.md create mode 100644 packages/context-kit/apps/context-kit/data/guardrails.md create mode 100644 packages/context-kit/apps/context-kit/data/kpis-goal.md create mode 100644 packages/context-kit/apps/context-kit/data/legal-compliance.md create mode 100644 packages/context-kit/apps/context-kit/data/source-of-truth.md create mode 100644 packages/context-kit/apps/context-kit/data/spend-threshold.md rename packages/context-kit/brain/{templates => context-kit}/briefing-template.md (100%) diff --git a/package-index.json b/package-index.json index 9006ee55..466b3ffc 100644 --- a/package-index.json +++ b/package-index.json @@ -1,12 +1,12 @@ { "schemaVersion": 1, - "indexRevision": "context-kit-0.2.0", + "indexRevision": "context-kit-0.3.0", "packages": [ { "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.2.0", + "version": "0.3.0", "categories": ["baseline"], "packageManagerVersion": 1, "source": { diff --git a/packages/context-kit/apps/context-kit/data/brand-context.md b/packages/context-kit/apps/context-kit/data/brand-context.md new file mode 100644 index 00000000..787d4330 --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/brand-context.md @@ -0,0 +1,3 @@ +# brand-context + +_Not filled in yet. Runneth writes this to /agent/brain/context-kit/brand-context.md and mirrors it here when the Context Kit skill fills it._ diff --git a/packages/context-kit/apps/context-kit/data/briefing-template.md b/packages/context-kit/apps/context-kit/data/briefing-template.md new file mode 100644 index 00000000..7439c1fd --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/briefing-template.md @@ -0,0 +1,3 @@ +# briefing-template + +_Not filled in yet. Runneth writes this to /agent/brain/context-kit/briefing-template.md and mirrors it here when the Context Kit skill fills it._ diff --git a/packages/context-kit/apps/context-kit/data/context-kit-state.json b/packages/context-kit/apps/context-kit/data/context-kit-state.json index cd4798e5..ffcbe397 100644 --- a/packages/context-kit/apps/context-kit/data/context-kit-state.json +++ b/packages/context-kit/apps/context-kit/data/context-kit-state.json @@ -6,17 +6,17 @@ { "id": "L3", "name": "Runs with full context", "state": "locked" } ], "items": [ - { "id": "brand-context", "label": "Brand context", "bucket": "A", "status": "inferred", "why": "Keeps every output unmistakably on-brand.", "preview": "" }, - { "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", "status": "inferred", "why": "Every readout leads with the metric you care about.", "preview": "" }, - { "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", "status": "inferred", "why": "Separates a proven winner from early noise.", "preview": "" }, - { "id": "competitors", "label": "Competitors", "bucket": "B", "status": "missing", "why": "Angles to attack the gaps rivals leave open.", "file": "competitors.md" }, - { "id": "products", "label": "Products & SKUs", "bucket": "B", "status": "missing", "why": "Grounds every ad in real product facts.", "file": "products.md" }, - { "id": "positioning", "label": "Positioning & personas", "bucket": "B", "status": "missing", "why": "Right audience, right angle.", "file": "positioning.md" }, - { "id": "voice", "label": "Voice & tone", "bucket": "B", "status": "missing", "why": "Copy that sounds like you wrote it.", "file": "voice.md" }, - { "id": "voc", "label": "Voice-of-customer", "bucket": "B", "status": "missing", "why": "Real customer language for hooks.", "file": "voc.md" }, - { "id": "legal", "label": "Legal & compliance", "bucket": "C", "status": "missing", "why": "Clears review on the first pass." }, - { "id": "briefing-template", "label": "Briefing template", "bucket": "C", "status": "missing", "why": "Briefs come back in your exact format." }, - { "id": "source-of-truth", "label": "Source of truth", "bucket": "C", "status": "missing", "why": "Reports never fight your dashboard." }, - { "id": "guardrails", "label": "Guardrails", "bucket": "C", "status": "missing", "why": "Always/never rules for creative." } + { "id": "brand-context", "label": "Brand context", "bucket": "A", "status": "inferred", "why": "Keeps every output unmistakably on-brand.", "preview": "", "dataFile": "brand-context.md" }, + { "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", "status": "inferred", "why": "Every readout leads with the metric you care about.", "preview": "", "dataFile": "kpis-goal.md" }, + { "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", "status": "inferred", "why": "Separates a proven winner from early noise.", "preview": "", "dataFile": "spend-threshold.md" }, + { "id": "competitors", "label": "Competitors", "bucket": "B", "status": "missing", "why": "Angles to attack the gaps rivals leave open.", "dataFile": "competitors.md" }, + { "id": "products", "label": "Products & SKUs", "bucket": "B", "status": "missing", "why": "Grounds every ad in real product facts.", "dataFile": "products.md" }, + { "id": "positioning", "label": "Positioning & personas", "bucket": "B", "status": "missing", "why": "Right audience, right angle.", "dataFile": "positioning.md" }, + { "id": "voice", "label": "Voice & tone", "bucket": "B", "status": "missing", "why": "Copy that sounds like you wrote it.", "dataFile": "voice.md" }, + { "id": "voc", "label": "Voice-of-customer", "bucket": "B", "status": "missing", "why": "Real customer language for hooks.", "dataFile": "voc.md" }, + { "id": "legal", "label": "Legal & compliance", "bucket": "C", "status": "missing", "why": "Clears review on the first pass.", "dataFile": "legal-compliance.md" }, + { "id": "briefing-template", "label": "Briefing template", "bucket": "C", "status": "missing", "why": "Briefs come back in your exact format.", "dataFile": "briefing-template.md" }, + { "id": "source-of-truth", "label": "Source of truth", "bucket": "C", "status": "missing", "why": "Reports never fight your dashboard.", "dataFile": "source-of-truth.md" }, + { "id": "guardrails", "label": "Guardrails", "bucket": "C", "status": "missing", "why": "Always/never rules for creative.", "dataFile": "guardrails.md" } ] } diff --git a/packages/context-kit/apps/context-kit/data/guardrails.md b/packages/context-kit/apps/context-kit/data/guardrails.md new file mode 100644 index 00000000..3d9f5cb1 --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/guardrails.md @@ -0,0 +1,3 @@ +# guardrails + +_Not filled in yet. Runneth writes this to /agent/brain/context-kit/guardrails.md and mirrors it here when the Context Kit skill fills it._ diff --git a/packages/context-kit/apps/context-kit/data/kpis-goal.md b/packages/context-kit/apps/context-kit/data/kpis-goal.md new file mode 100644 index 00000000..6209f0aa --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/kpis-goal.md @@ -0,0 +1,3 @@ +# kpis-goal + +_Not filled in yet. Runneth writes this to /agent/brain/context-kit/kpis-goal.md and mirrors it here when the Context Kit skill fills it._ diff --git a/packages/context-kit/apps/context-kit/data/legal-compliance.md b/packages/context-kit/apps/context-kit/data/legal-compliance.md new file mode 100644 index 00000000..b535b698 --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/legal-compliance.md @@ -0,0 +1,3 @@ +# legal-compliance + +_Not filled in yet. Runneth writes this to /agent/brain/context-kit/legal-compliance.md and mirrors it here when the Context Kit skill fills it._ diff --git a/packages/context-kit/apps/context-kit/data/source-of-truth.md b/packages/context-kit/apps/context-kit/data/source-of-truth.md new file mode 100644 index 00000000..b3ae8a5c --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/source-of-truth.md @@ -0,0 +1,3 @@ +# source-of-truth + +_Not filled in yet. Runneth writes this to /agent/brain/context-kit/source-of-truth.md and mirrors it here when the Context Kit skill fills it._ diff --git a/packages/context-kit/apps/context-kit/data/spend-threshold.md b/packages/context-kit/apps/context-kit/data/spend-threshold.md new file mode 100644 index 00000000..963511af --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/spend-threshold.md @@ -0,0 +1,3 @@ +# spend-threshold + +_Not filled in yet. Runneth writes this to /agent/brain/context-kit/spend-threshold.md and mirrors it here when the Context Kit skill fills it._ diff --git a/packages/context-kit/apps/context-kit/package.json b/packages/context-kit/apps/context-kit/package.json index ebbfd498..86d75990 100644 --- a/packages/context-kit/apps/context-kit/package.json +++ b/packages/context-kit/apps/context-kit/package.json @@ -1,5 +1,5 @@ { - "name": "onboarding", + "name": "context-kit", "private": true, "type": "module", "scripts": { diff --git a/packages/context-kit/apps/context-kit/src/pages/index.astro b/packages/context-kit/apps/context-kit/src/pages/index.astro index 440b1104..ec08948b 100644 --- a/packages/context-kit/apps/context-kit/src/pages/index.astro +++ b/packages/context-kit/apps/context-kit/src/pages/index.astro @@ -17,6 +17,8 @@ .kicker{display:inline-block;font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;background:var(--lime);padding:5px 10px;border-radius:4px;margin-bottom:16px} h1{font-size:clamp(30px,4.5vw,44px);font-weight:800;letter-spacing:-.025em;margin-bottom:10px} .lede{font-size:16px;color:var(--muted);max-width:640px} + .integration-nudge{font-size:13.5px;color:var(--muted);margin-top:12px;max-width:640px} + .integration-nudge strong{color:var(--ink)} .levels{display:flex;gap:10px;margin:30px 0 22px} @media(max-width:640px){.levels{flex-direction:column}} .lvl{flex:1;border:1px solid var(--border);border-radius:var(--r);padding:14px 16px;background:var(--gray-0);position:relative} @@ -60,7 +62,8 @@ .card-panel h1,.card-panel h2,.card-panel h3{font-size:14px;margin:10px 0 4px} .card-panel p{margin:6px 0} .card-panel .empty{color:var(--muted);font-style:italic} - .note{margin-top:26px;font-size:13px;color:var(--muted);border-top:1px solid var(--border);padding-top:16px} + .help-banner{display:flex;align-items:flex-start;gap:12px;margin-top:32px;padding:16px 20px;border:1px solid var(--border);border-radius:var(--r);background:var(--surface);font-size:14px;color:var(--ink);line-height:1.55} + .help-banner .icon{font-size:18px;flex-shrink:0;margin-top:1px} @@ -68,8 +71,12 @@ Context Kit

Your Context Kit

The more Runneth knows about your brand, the sharper every answer. Confirm what we pulled, review what we drafted, add the few things only you know. Click any card to see what's saved.

+

📄 Already have brand docs in Google Drive or Notion? Connect them in chat and Runneth can pull from them directly.

-

This board mirrors your brain in real time. Runneth updates it as you fill things in through chat. It reads state on every load, so a refresh always shows the latest.

+
+ 💡 + This board mirrors your brain. Click any card to review what Runneth has saved. Tell Runneth in chat to update anything, or ask it to pull up any file anytime. Refresh after a chat update to see the latest. +
diff --git a/packages/context-kit/brain/templates/briefing-template.md b/packages/context-kit/brain/context-kit/briefing-template.md similarity index 100% rename from packages/context-kit/brain/templates/briefing-template.md rename to packages/context-kit/brain/context-kit/briefing-template.md diff --git a/packages/context-kit/brain/context-kit/context-kit-state.json b/packages/context-kit/brain/context-kit/context-kit-state.json index cd4798e5..ffcbe397 100644 --- a/packages/context-kit/brain/context-kit/context-kit-state.json +++ b/packages/context-kit/brain/context-kit/context-kit-state.json @@ -6,17 +6,17 @@ { "id": "L3", "name": "Runs with full context", "state": "locked" } ], "items": [ - { "id": "brand-context", "label": "Brand context", "bucket": "A", "status": "inferred", "why": "Keeps every output unmistakably on-brand.", "preview": "" }, - { "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", "status": "inferred", "why": "Every readout leads with the metric you care about.", "preview": "" }, - { "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", "status": "inferred", "why": "Separates a proven winner from early noise.", "preview": "" }, - { "id": "competitors", "label": "Competitors", "bucket": "B", "status": "missing", "why": "Angles to attack the gaps rivals leave open.", "file": "competitors.md" }, - { "id": "products", "label": "Products & SKUs", "bucket": "B", "status": "missing", "why": "Grounds every ad in real product facts.", "file": "products.md" }, - { "id": "positioning", "label": "Positioning & personas", "bucket": "B", "status": "missing", "why": "Right audience, right angle.", "file": "positioning.md" }, - { "id": "voice", "label": "Voice & tone", "bucket": "B", "status": "missing", "why": "Copy that sounds like you wrote it.", "file": "voice.md" }, - { "id": "voc", "label": "Voice-of-customer", "bucket": "B", "status": "missing", "why": "Real customer language for hooks.", "file": "voc.md" }, - { "id": "legal", "label": "Legal & compliance", "bucket": "C", "status": "missing", "why": "Clears review on the first pass." }, - { "id": "briefing-template", "label": "Briefing template", "bucket": "C", "status": "missing", "why": "Briefs come back in your exact format." }, - { "id": "source-of-truth", "label": "Source of truth", "bucket": "C", "status": "missing", "why": "Reports never fight your dashboard." }, - { "id": "guardrails", "label": "Guardrails", "bucket": "C", "status": "missing", "why": "Always/never rules for creative." } + { "id": "brand-context", "label": "Brand context", "bucket": "A", "status": "inferred", "why": "Keeps every output unmistakably on-brand.", "preview": "", "dataFile": "brand-context.md" }, + { "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", "status": "inferred", "why": "Every readout leads with the metric you care about.", "preview": "", "dataFile": "kpis-goal.md" }, + { "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", "status": "inferred", "why": "Separates a proven winner from early noise.", "preview": "", "dataFile": "spend-threshold.md" }, + { "id": "competitors", "label": "Competitors", "bucket": "B", "status": "missing", "why": "Angles to attack the gaps rivals leave open.", "dataFile": "competitors.md" }, + { "id": "products", "label": "Products & SKUs", "bucket": "B", "status": "missing", "why": "Grounds every ad in real product facts.", "dataFile": "products.md" }, + { "id": "positioning", "label": "Positioning & personas", "bucket": "B", "status": "missing", "why": "Right audience, right angle.", "dataFile": "positioning.md" }, + { "id": "voice", "label": "Voice & tone", "bucket": "B", "status": "missing", "why": "Copy that sounds like you wrote it.", "dataFile": "voice.md" }, + { "id": "voc", "label": "Voice-of-customer", "bucket": "B", "status": "missing", "why": "Real customer language for hooks.", "dataFile": "voc.md" }, + { "id": "legal", "label": "Legal & compliance", "bucket": "C", "status": "missing", "why": "Clears review on the first pass.", "dataFile": "legal-compliance.md" }, + { "id": "briefing-template", "label": "Briefing template", "bucket": "C", "status": "missing", "why": "Briefs come back in your exact format.", "dataFile": "briefing-template.md" }, + { "id": "source-of-truth", "label": "Source of truth", "bucket": "C", "status": "missing", "why": "Reports never fight your dashboard.", "dataFile": "source-of-truth.md" }, + { "id": "guardrails", "label": "Guardrails", "bucket": "C", "status": "missing", "why": "Always/never rules for creative.", "dataFile": "guardrails.md" } ] } diff --git a/packages/context-kit/package.json b/packages/context-kit/package.json index 7206fc37..26457f17 100644 --- a/packages/context-kit/package.json +++ b/packages/context-kit/package.json @@ -3,18 +3,85 @@ "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.2.0", + "version": "0.3.0", "installPolicy": "manual", "updatePolicy": "manual", "uninstallPolicy": "allowed", "resources": [ - { "id": "context-kit-behavior", "type": "package_instruction", "sourcePath": "instructions/behavior.md" }, - { "id": "context-kit-state", "type": "file", "sourcePath": "brain/context-kit/context-kit-state.json", "target": { "root": "agent_brain", "path": "context-kit/context-kit-state.json" }, "executable": false }, - { "id": "legal-compliance", "type": "file", "sourcePath": "brain/context-kit/legal-compliance.md", "target": { "root": "agent_brain", "path": "context-kit/legal-compliance.md" }, "executable": false }, - { "id": "source-of-truth", "type": "file", "sourcePath": "brain/context-kit/source-of-truth.md", "target": { "root": "agent_brain", "path": "context-kit/source-of-truth.md" }, "executable": false }, - { "id": "guardrails", "type": "file", "sourcePath": "brain/context-kit/guardrails.md", "target": { "root": "agent_brain", "path": "context-kit/guardrails.md" }, "executable": false }, - { "id": "briefing-template", "type": "file", "sourcePath": "brain/templates/briefing-template.md", "target": { "root": "agent_brain", "path": "templates/briefing-template.md" }, "executable": false }, - { "id": "context-kit-skill", "type": "directory", "sourcePath": "skills", "target": { "root": "agent_skills", "path": "context-kit" }, "executablePaths": [] }, - { "id": "context-kit-app", "type": "directory", "sourcePath": "apps/context-kit", "target": { "root": "agent_apps", "path": "context-kit" }, "executablePaths": [] } + { + "id": "context-kit-behavior", + "type": "package_instruction", + "sourcePath": "instructions/behavior.md" + }, + { + "id": "context-kit-state", + "type": "file", + "sourcePath": "brain/context-kit/context-kit-state.json", + "target": { + "root": "agent_brain", + "path": "context-kit/context-kit-state.json" + }, + "executable": false + }, + { + "id": "legal-compliance", + "type": "file", + "sourcePath": "brain/context-kit/legal-compliance.md", + "target": { + "root": "agent_brain", + "path": "context-kit/legal-compliance.md" + }, + "executable": false + }, + { + "id": "source-of-truth", + "type": "file", + "sourcePath": "brain/context-kit/source-of-truth.md", + "target": { + "root": "agent_brain", + "path": "context-kit/source-of-truth.md" + }, + "executable": false + }, + { + "id": "guardrails", + "type": "file", + "sourcePath": "brain/context-kit/guardrails.md", + "target": { + "root": "agent_brain", + "path": "context-kit/guardrails.md" + }, + "executable": false + }, + { + "id": "briefing-template", + "type": "file", + "sourcePath": "brain/context-kit/briefing-template.md", + "target": { + "root": "agent_brain", + "path": "context-kit/briefing-template.md" + }, + "executable": false + }, + { + "id": "context-kit-skill", + "type": "directory", + "sourcePath": "skills", + "target": { + "root": "agent_skills", + "path": "context-kit" + }, + "executablePaths": [] + }, + { + "id": "context-kit-app", + "type": "directory", + "sourcePath": "apps/context-kit", + "target": { + "root": "agent_apps", + "path": "context-kit" + }, + "executablePaths": [] + } ] } diff --git a/packages/context-kit/runneth-package.json b/packages/context-kit/runneth-package.json index a7518f2b..ccd54ea8 100644 --- a/packages/context-kit/runneth-package.json +++ b/packages/context-kit/runneth-package.json @@ -3,17 +3,84 @@ "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.2.0", + "version": "0.3.0", "updatePolicy": "manual", "uninstallPolicy": "allowed", "resources": [ - { "id": "context-kit-behavior", "type": "package_instruction", "sourcePath": "instructions/behavior.md" }, - { "id": "context-kit-state", "type": "file", "sourcePath": "brain/context-kit/context-kit-state.json", "target": { "root": "agent_brain", "path": "context-kit/context-kit-state.json" }, "executable": false }, - { "id": "legal-compliance", "type": "file", "sourcePath": "brain/context-kit/legal-compliance.md", "target": { "root": "agent_brain", "path": "context-kit/legal-compliance.md" }, "executable": false }, - { "id": "source-of-truth", "type": "file", "sourcePath": "brain/context-kit/source-of-truth.md", "target": { "root": "agent_brain", "path": "context-kit/source-of-truth.md" }, "executable": false }, - { "id": "guardrails", "type": "file", "sourcePath": "brain/context-kit/guardrails.md", "target": { "root": "agent_brain", "path": "context-kit/guardrails.md" }, "executable": false }, - { "id": "briefing-template", "type": "file", "sourcePath": "brain/templates/briefing-template.md", "target": { "root": "agent_brain", "path": "templates/briefing-template.md" }, "executable": false }, - { "id": "context-kit-skill", "type": "directory", "sourcePath": "skills", "target": { "root": "agent_skills", "path": "context-kit" }, "executablePaths": [] }, - { "id": "context-kit-app", "type": "directory", "sourcePath": "apps/context-kit", "target": { "root": "agent_apps", "path": "context-kit" }, "executablePaths": [] } + { + "id": "context-kit-behavior", + "type": "package_instruction", + "sourcePath": "instructions/behavior.md" + }, + { + "id": "context-kit-state", + "type": "file", + "sourcePath": "brain/context-kit/context-kit-state.json", + "target": { + "root": "agent_brain", + "path": "context-kit/context-kit-state.json" + }, + "executable": false + }, + { + "id": "legal-compliance", + "type": "file", + "sourcePath": "brain/context-kit/legal-compliance.md", + "target": { + "root": "agent_brain", + "path": "context-kit/legal-compliance.md" + }, + "executable": false + }, + { + "id": "source-of-truth", + "type": "file", + "sourcePath": "brain/context-kit/source-of-truth.md", + "target": { + "root": "agent_brain", + "path": "context-kit/source-of-truth.md" + }, + "executable": false + }, + { + "id": "guardrails", + "type": "file", + "sourcePath": "brain/context-kit/guardrails.md", + "target": { + "root": "agent_brain", + "path": "context-kit/guardrails.md" + }, + "executable": false + }, + { + "id": "briefing-template", + "type": "file", + "sourcePath": "brain/context-kit/briefing-template.md", + "target": { + "root": "agent_brain", + "path": "context-kit/briefing-template.md" + }, + "executable": false + }, + { + "id": "context-kit-skill", + "type": "directory", + "sourcePath": "skills", + "target": { + "root": "agent_skills", + "path": "context-kit" + }, + "executablePaths": [] + }, + { + "id": "context-kit-app", + "type": "directory", + "sourcePath": "apps/context-kit", + "target": { + "root": "agent_apps", + "path": "context-kit" + }, + "executablePaths": [] + } ] } diff --git a/packages/context-kit/skills/SKILL.md b/packages/context-kit/skills/SKILL.md index 64c606cd..aacce681 100644 --- a/packages/context-kit/skills/SKILL.md +++ b/packages/context-kit/skills/SKILL.md @@ -1,67 +1,86 @@ --- name: context-kit -description: Builds a customer's Context Kit, the institutional knowledge that makes every Runneth answer sharper. Reads the Context Kit state index, confirms what Motion already knows, imports what already lives in Google Drive or Notion, drafts what Runneth can research, and collects what only the customer knows, filling each into the brain and moving the completeness meter. Triggers on "build my context kit", "set up my context kit", "context kit", "build my brain", "sharpen Runneth", "what makes my answers better", "what do you still need from me", "what does Runneth know about us". +description: Builds a customer's Context Kit, the institutional knowledge that makes every Runneth answer sharper. Reads the Context Kit state index, confirms what Motion knows, drafts brand-context and every Bucket B item from Motion creative data before asking anything, imports what already lives in Google Drive or Notion, and collects what only the customer knows, filling each into the brain and moving the completeness meter. Triggers on "build my context kit", "set up my context kit", "context kit", "build my brain", "sharpen Runneth", "what makes my answers better", "what do you still need from me", "what does Runneth know about us". --- # Context Kit skill -Turn a fresh brain into a filled one, without the manual sales/CS back-and-forth. The Context Kit -board app is the mirror; this skill is the doer. Never write to `user.md`. Fill happens as deliberate -in-conversation brain writes. Always prefer importing what already exists over asking the customer to -retype it. +Turn a fresh brain into a filled one, without the manual sales/CS back-and-forth. The board app is the +mirror; this skill is the doer. Draft from Motion data first, then offer import, then collect. Never write +to `user.md`. Fill happens as deliberate in-conversation brain writes. + +## Single source of truth +Every one of the 12 files lives in `/agent/brain/context-kit/.md` and is mirrored to +`/agent/apps/context-kit/data/.md` so the board can fetch it. Do NOT scatter files into +`brand-audit/` or `templates/`. Item ids: brand-context, kpis-goal, spend-threshold, competitors, +products, positioning, voice, voc, legal (file legal-compliance.md), briefing-template, source-of-truth, +guardrails. ## Step 0 — Load state 1. Read `/agent/brain/context-kit/context-kit-state.json`. -2. Run `motion brand-context --data-query "summary"`, `motion workspace-goal`, `motion spend-threshold` - to hydrate the three Bucket A items with live values. -3. Check which context sources are connected (Google Drive, Notion) so import is offered when available. -4. Resolve the workspace slug for brand-audit paths. +2. Run `motion brand-context --data-query "summary"`, `motion workspace-goal`, `motion spend-threshold`. +3. Note which context sources are connected (Google Drive, Notion). ## Step 1 — Build and open the board (first run only) -Package sync stages files but does NOT build the app. On first run: -1. Fill the bundled manifest: in `/agent/apps/context-kit/buildeth.app.json`, replace `__CONVERSATION_ID__` - with the current conversation id and `__WORKSPACE_ID__` with the current workspace id. The rest of the - v3 manifest (oauthEnabled, data dir, static dist/index) and `astro.config.mjs` (base `/context-kit`) - ship in the package, so no trial-and-error is needed. -2. Run `app build context-kit`, then `app list` for the verified URL. Hand the URL back. Never invent it. -3. After the first build, the board reads `data/` at runtime (client-side fetch), so later state/content - updates show on refresh with NO rebuild. Only rebuild if you change the app source itself. +Sync stages files but does not build apps. Fill `/agent/apps/context-kit/buildeth.app.json` (replace +`__CONVERSATION_ID__` and `__WORKSPACE_ID__` with current context), run `app build context-kit`, then +`app list` for the URL. `astro.config.mjs` ships in the package. After first build the board reads `data/` +at runtime, so later content/state changes need NO rebuild. + +## Step 2 — Bucket A: confirm, and auto-draft brand-context +- **kpis-goal, spend-threshold:** show the live Motion value, one-tap confirm, write the full doc, set the + `preview` string in state, mark `confirmed`. +- **brand-context (auto-draft, never ask from scratch):** + 1. If `motion brand-context` returns content, show it and ask to confirm. + 2. If empty, say: "I don't have brand context saved yet, I'll draft it from your top-performing ad creative now." + 3. Pull top spend creative: `motion meta insights --date-range last_30d --sort topSpend --include-metrics` + (+ TikTok if connected). Read hooks, primary text, headlines, summaries. + 4. Draft `brand-context.md` as the FOUNDATION only: brand name, origin story, positioning statement, + product description, proof points, a 2-sentence tone summary, a 2-sentence audience summary. Do not + duplicate the full voice/VoC/competitor detail (those are their own files). + 5. Present the draft, ask to confirm, then write `/agent/brain/context-kit/brand-context.md`, save to + workspace config, mirror to `data/brand-context.md`, mark `confirmed`. -## Step 2 — Bucket A: confirm (fast) + write the full working doc -For brand-context, kpis-goal, spend-threshold: show the live value plainly and ask a one-tap confirm. -On confirm: set the item `confirmed`, set its `preview` string in the state index (short human summary -for the card), and write the full working document. `brand-context.md` (#10) is the *foundation only*: -brand name, story, positioning statement, product description, proof points, and a 2-sentence tone/audience -summary. Do NOT concatenate the Bucket B detail into it. Do not re-collect what Motion already holds. +## Step 3 — Proactive import offer +After state load, if any Bucket B/C item is missing AND Google Drive or Notion is connected, proactively +say: "I can see you have [Drive/Notion] connected, want me to search there first before drafting from +scratch?" If they say yes, search, confirm the doc, import into `/agent/brain/context-kit/.md`, mirror +to `data/`, mark `imported`. Lean on existing context-sweep / integration skills for the pull. -## Step 3 — Import mode (try before drafting or asking, for every remaining item) -Ask: "Do you already have this in Google Drive or Notion?" If connected, search for the relevant doc, -confirm it, import into the correct brain path, set `imported` then `confirmed`. If not connected, offer -the native connect flow first. Only fall through to draft/write-in if they decline or don't have it. Lean -on existing context-sweep / integration skills for the search + pull. +## Step 4 — Bucket B: draft from Motion creative data first, then correct +For each item, attempt a Motion-backed draft BEFORE import or manual collection. A rough draft the user can +react to beats a blank prompt. Write full working docs to `/agent/brain/context-kit/.md`, mirror to +`data/.md`, set `drafted` (then `confirmed` on accept), and tell the user +"I drafted [item] from your Motion data, click the card to review or tell me what to change." -## Step 4 — Bucket B: draft rich, then confirm, then mirror to the board -For competitors, products, positioning, voice, voice-of-customer: run the matching skill (product-catalog, -competitor-analysis, brand-audit strategy/review passes, Inspo followed brands + boards for competitors). -Write FULL working documents into `/agent/brain/brand-audit//` (#9): competitor tone/audience/ -positioning breakdowns, complete product feature lists, full personas with motivations and objections, all -voice-of-customer quotes and patterns. Thin summaries fail here. Then: -- set status `drafted` (customer reviews) then `confirmed` on accept. -- MIRROR each file into `/agent/apps/context-kit/data/.md` (competitors.md, products.md, - positioning.md, voice.md, voc.md) so the board's click-to-expand can fetch it at runtime. +- **competitors:** `motion inspo brands` (followed brands), then `motion inspo unique-creatives --brand-id + --sort-by impressionRank` per brand. Summarize who they are, what angles they run, what they + emphasize. Ask the user to add anyone missing. +- **products:** top spend creative via `motion meta insights` with summaries + glossary tags. Compile the + product list from repeated hero claims / SKU callouts / bundles, note claims per product. Ask the user to + add pricing, SKU codes, and anything not in copy. +- **positioning:** `motion meta insights --include-glossary`. Read intended-audience, messaging-angle, and + hook-tactic tag distribution; combine with hook language to draft positioning + personas. Flag assumptions. +- **voice:** hooks, primary text, and summaries of the top 20-30 by spend. Synthesize 4-6 named voice + characteristics, each with a "sounds like / doesn't sound like" pair. Should feel recognizable to a brand writer. +- **voc:** top spend VIDEO creative with `--include-transcript`. Extract the most repeated real customer + phrases, emotional beats, outcome language; supplement with static hooks. Group by problem language, + outcome language, objections, social proof. +- If a pull returns too little (no followed brands, no transcripts), say what was missing and fall through to + import or manual for that item only. Never silently skip. ## Step 5 — Bucket C: collect with depth -For legal, briefing-template, source-of-truth, guardrails: file drop when they have the doc, thought -starters when stuck, one prompt when short. Prompt for the COMPLETE rule set, not a one-liner (#9). Write -into the scaffold file, fully populating the `## Your answers` section. Mark `confirmed`. +legal, briefing-template, source-of-truth, guardrails: file drop when they have it, thought starters when +stuck, one prompt when short. Prompt for the COMPLETE rule set, not a one-liner. Write to +`/agent/brain/context-kit/.md` (legal -> legal-compliance.md), mirror to `data/`, mark `confirmed`. ## Step 6 — Keep the map correct -- After each item is filled with real content, add or refresh its `/agent/INDEX.md` entry. -- Update `/agent/brain/context-kit/context-kit-state.json` AND the app's `data/context-kit-state.json` - after every change (the board reads the app copy). No rebuild needed for data-only changes. +- Refresh `/agent/INDEX.md` entries pointing to `/agent/brain/context-kit/` as the canonical home for all + Context Kit files. No separate entries for scattered originals. +- After every change update BOTH `/agent/brain/context-kit/context-kit-state.json` and the app's + `data/context-kit-state.json` (the board reads the app copy). No rebuild needed for data-only changes. ## Rules -- Never touch `user.md`. -- Never overwrite an already-filled file (create-if-absent, edit-in-place only on confirm). +- Never touch `user.md`. Never overwrite an already-filled file (create-if-absent, edit-in-place on confirm). - Plain, non-technical language with the customer. No file paths or JSON in chat. - One item at a time; celebrate the Level 3 unlock when the meter completes. From a92c989d4df2823a7d65b595848fd56a5f4f692a Mon Sep 17 00:00:00 2001 From: "Runneth (for Vamsi)" Date: Mon, 6 Jul 2026 09:38:11 -0400 Subject: [PATCH 05/25] =?UTF-8?q?feat(context-kit):=20v0.4.0=20=E2=80=94?= =?UTF-8?q?=20reshape=20voc=20as=20real=20customer-language=20research=20(?= =?UTF-8?q?review-audit=20+=20brand-relevant-keywords=20+=20web=20+=20Levi?= =?UTF-8?q?nger=20lived-context/trigger-moments),=20split=20brand=20voice?= =?UTF-8?q?=20from=20customer=20voice,=20add=20reviews-platform=20connect?= =?UTF-8?q?=20prompts=20+=20per-card=20enrich=20nudge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-index.json | 4 +- .../context-kit/data/context-kit-state.json | 130 ++++++++++++++++-- .../context-kit/apps/context-kit/data/voc.md | 4 +- .../apps/context-kit/src/pages/index.astro | 21 +-- .../brain/context-kit/context-kit-state.json | 130 ++++++++++++++++-- packages/context-kit/package.json | 2 +- packages/context-kit/runneth-package.json | 2 +- packages/context-kit/skills/SKILL.md | 86 ++++++------ 8 files changed, 295 insertions(+), 84 deletions(-) diff --git a/package-index.json b/package-index.json index 466b3ffc..ce081490 100644 --- a/package-index.json +++ b/package-index.json @@ -1,12 +1,12 @@ { "schemaVersion": 1, - "indexRevision": "context-kit-0.3.0", + "indexRevision": "context-kit-0.4.0", "packages": [ { "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.3.0", + "version": "0.4.0", "categories": ["baseline"], "packageManagerVersion": 1, "source": { diff --git a/packages/context-kit/apps/context-kit/data/context-kit-state.json b/packages/context-kit/apps/context-kit/data/context-kit-state.json index ffcbe397..f2078a47 100644 --- a/packages/context-kit/apps/context-kit/data/context-kit-state.json +++ b/packages/context-kit/apps/context-kit/data/context-kit-state.json @@ -1,22 +1,122 @@ { "version": 1, "levels": [ - { "id": "L1", "name": "Answers questions", "state": "passed" }, - { "id": "L2", "name": "On-brand outputs", "state": "current" }, - { "id": "L3", "name": "Runs with full context", "state": "locked" } + { + "id": "L1", + "name": "Answers questions", + "state": "passed" + }, + { + "id": "L2", + "name": "On-brand outputs", + "state": "current" + }, + { + "id": "L3", + "name": "Runs with full context", + "state": "locked" + } ], "items": [ - { "id": "brand-context", "label": "Brand context", "bucket": "A", "status": "inferred", "why": "Keeps every output unmistakably on-brand.", "preview": "", "dataFile": "brand-context.md" }, - { "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", "status": "inferred", "why": "Every readout leads with the metric you care about.", "preview": "", "dataFile": "kpis-goal.md" }, - { "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", "status": "inferred", "why": "Separates a proven winner from early noise.", "preview": "", "dataFile": "spend-threshold.md" }, - { "id": "competitors", "label": "Competitors", "bucket": "B", "status": "missing", "why": "Angles to attack the gaps rivals leave open.", "dataFile": "competitors.md" }, - { "id": "products", "label": "Products & SKUs", "bucket": "B", "status": "missing", "why": "Grounds every ad in real product facts.", "dataFile": "products.md" }, - { "id": "positioning", "label": "Positioning & personas", "bucket": "B", "status": "missing", "why": "Right audience, right angle.", "dataFile": "positioning.md" }, - { "id": "voice", "label": "Voice & tone", "bucket": "B", "status": "missing", "why": "Copy that sounds like you wrote it.", "dataFile": "voice.md" }, - { "id": "voc", "label": "Voice-of-customer", "bucket": "B", "status": "missing", "why": "Real customer language for hooks.", "dataFile": "voc.md" }, - { "id": "legal", "label": "Legal & compliance", "bucket": "C", "status": "missing", "why": "Clears review on the first pass.", "dataFile": "legal-compliance.md" }, - { "id": "briefing-template", "label": "Briefing template", "bucket": "C", "status": "missing", "why": "Briefs come back in your exact format.", "dataFile": "briefing-template.md" }, - { "id": "source-of-truth", "label": "Source of truth", "bucket": "C", "status": "missing", "why": "Reports never fight your dashboard.", "dataFile": "source-of-truth.md" }, - { "id": "guardrails", "label": "Guardrails", "bucket": "C", "status": "missing", "why": "Always/never rules for creative.", "dataFile": "guardrails.md" } + { + "id": "brand-context", + "label": "Brand context", + "bucket": "A", + "status": "inferred", + "why": "Keeps every output unmistakably on-brand.", + "preview": "", + "dataFile": "brand-context.md" + }, + { + "id": "kpis-goal", + "label": "KPIs & goal", + "bucket": "A", + "status": "inferred", + "why": "Every readout leads with the metric you care about.", + "preview": "", + "dataFile": "kpis-goal.md" + }, + { + "id": "spend-threshold", + "label": "Spend threshold", + "bucket": "A", + "status": "inferred", + "why": "Separates a proven winner from early noise.", + "preview": "", + "dataFile": "spend-threshold.md" + }, + { + "id": "competitors", + "label": "Competitors", + "bucket": "B", + "status": "missing", + "why": "Angles to attack the gaps rivals leave open.", + "dataFile": "competitors.md" + }, + { + "id": "products", + "label": "Products & SKUs", + "bucket": "B", + "status": "missing", + "why": "Grounds every ad in real product facts.", + "dataFile": "products.md" + }, + { + "id": "positioning", + "label": "Positioning & personas", + "bucket": "B", + "status": "missing", + "why": "Right audience, right angle.", + "dataFile": "positioning.md" + }, + { + "id": "voice", + "label": "Voice & tone", + "bucket": "B", + "status": "missing", + "why": "Copy that sounds like you wrote it.", + "dataFile": "voice.md" + }, + { + "id": "voc", + "label": "Voice-of-customer", + "bucket": "B", + "status": "missing", + "why": "How your customers actually talk about the problem.", + "dataFile": "voc.md", + "enrich": "Enrich this by connecting your reviews platform (Yotpo, Okendo, Trustpilot) in chat. Real reviews are the richest source of customer language." + }, + { + "id": "legal", + "label": "Legal & compliance", + "bucket": "C", + "status": "missing", + "why": "Clears review on the first pass.", + "dataFile": "legal-compliance.md" + }, + { + "id": "briefing-template", + "label": "Briefing template", + "bucket": "C", + "status": "missing", + "why": "Briefs come back in your exact format.", + "dataFile": "briefing-template.md" + }, + { + "id": "source-of-truth", + "label": "Source of truth", + "bucket": "C", + "status": "missing", + "why": "Reports never fight your dashboard.", + "dataFile": "source-of-truth.md" + }, + { + "id": "guardrails", + "label": "Guardrails", + "bucket": "C", + "status": "missing", + "why": "Always/never rules for creative.", + "dataFile": "guardrails.md" + } ] } diff --git a/packages/context-kit/apps/context-kit/data/voc.md b/packages/context-kit/apps/context-kit/data/voc.md index ea505eda..4bcc1b8e 100644 --- a/packages/context-kit/apps/context-kit/data/voc.md +++ b/packages/context-kit/apps/context-kit/data/voc.md @@ -1,3 +1,3 @@ -# voc +# Voice-of-customer -_Not filled in yet. Runneth mirrors this from /agent/brain/brand-audit// when the Context Kit skill drafts it._ +_Not filled in yet. This is how your customers actually talk about the problem, drawn from real reviews, Reddit, and social language, not your ad scripts. Connect your reviews platform in chat to make it much richer. Runneth writes this to /agent/brain/context-kit/voc.md and mirrors it here._ diff --git a/packages/context-kit/apps/context-kit/src/pages/index.astro b/packages/context-kit/apps/context-kit/src/pages/index.astro index ec08948b..416a0efe 100644 --- a/packages/context-kit/apps/context-kit/src/pages/index.astro +++ b/packages/context-kit/apps/context-kit/src/pages/index.astro @@ -62,6 +62,8 @@ .card-panel h1,.card-panel h2,.card-panel h3{font-size:14px;margin:10px 0 4px} .card-panel p{margin:6px 0} .card-panel .empty{color:var(--muted);font-style:italic} + .card-panel .enrich{display:block;margin-top:10px;padding:9px 12px;border:1px dashed var(--sky);border-radius:8px;background:#eef7fb;color:#0b4a59;font-size:12.5px} + .card-panel .enrich strong{color:#0b4a59} .help-banner{display:flex;align-items:flex-start;gap:12px;margin-top:32px;padding:16px 20px;border:1px solid var(--border);border-radius:var(--r);background:var(--surface);font-size:14px;color:var(--ink);line-height:1.55} .help-banner .icon{font-size:18px;flex-shrink:0;margin-top:1px} @@ -103,25 +105,28 @@ if(item.bucket==="B") return "Runneth hasn't researched this yet. Say \"build my Context Kit\" in chat and Runneth will draft it for you."; return "Only you can answer this one. Say \"fill in my "+esc(item.label)+"\" in chat and Runneth will walk you through it."; } + function enrichHtml(item){ + return item.enrich ? "
\u2728 Tip: "+esc(item.enrich)+"
" : ""; + } async function loadPanel(item, panel){ const filled = ["confirmed","imported","drafted"].includes(item.status); const file = item.dataFile || (item.id+".md"); + let content; if(filled){ try{ const r = await fetch(BASE+"/data/"+file,{cache:"no-store"}); if(!r.ok) throw 0; const t = await r.text(); - panel.innerHTML = "

"+mdToHtml(t)+"

"; + content = "

"+mdToHtml(t)+"

"; }catch(e){ - panel.innerHTML = "

"+ (item.bucket==="B" ? "This file hasn't been saved yet. Say \"build my Context Kit\" in chat and Runneth will fill it in." : emptyMsg(item)) +"

"; + content = "

"+ (item.bucket==="B" ? "This file hasn't been saved yet. Say \"build my Context Kit\" in chat and Runneth will fill it in." : emptyMsg(item)) +"

"; } - return; - } - if(item.bucket==="A" && item.preview){ - panel.innerHTML = "

"+esc(item.preview)+"

"+emptyMsg(item)+"

"; - return; + } else if(item.bucket==="A" && item.preview){ + content = "

"+esc(item.preview)+"

"+emptyMsg(item)+"

"; + } else { + content = "

"+emptyMsg(item)+"

"; } - panel.innerHTML = "

"+emptyMsg(item)+"

"; + panel.innerHTML = content + enrichHtml(item); } function render(state){ const items = state.items || []; diff --git a/packages/context-kit/brain/context-kit/context-kit-state.json b/packages/context-kit/brain/context-kit/context-kit-state.json index ffcbe397..f2078a47 100644 --- a/packages/context-kit/brain/context-kit/context-kit-state.json +++ b/packages/context-kit/brain/context-kit/context-kit-state.json @@ -1,22 +1,122 @@ { "version": 1, "levels": [ - { "id": "L1", "name": "Answers questions", "state": "passed" }, - { "id": "L2", "name": "On-brand outputs", "state": "current" }, - { "id": "L3", "name": "Runs with full context", "state": "locked" } + { + "id": "L1", + "name": "Answers questions", + "state": "passed" + }, + { + "id": "L2", + "name": "On-brand outputs", + "state": "current" + }, + { + "id": "L3", + "name": "Runs with full context", + "state": "locked" + } ], "items": [ - { "id": "brand-context", "label": "Brand context", "bucket": "A", "status": "inferred", "why": "Keeps every output unmistakably on-brand.", "preview": "", "dataFile": "brand-context.md" }, - { "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", "status": "inferred", "why": "Every readout leads with the metric you care about.", "preview": "", "dataFile": "kpis-goal.md" }, - { "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", "status": "inferred", "why": "Separates a proven winner from early noise.", "preview": "", "dataFile": "spend-threshold.md" }, - { "id": "competitors", "label": "Competitors", "bucket": "B", "status": "missing", "why": "Angles to attack the gaps rivals leave open.", "dataFile": "competitors.md" }, - { "id": "products", "label": "Products & SKUs", "bucket": "B", "status": "missing", "why": "Grounds every ad in real product facts.", "dataFile": "products.md" }, - { "id": "positioning", "label": "Positioning & personas", "bucket": "B", "status": "missing", "why": "Right audience, right angle.", "dataFile": "positioning.md" }, - { "id": "voice", "label": "Voice & tone", "bucket": "B", "status": "missing", "why": "Copy that sounds like you wrote it.", "dataFile": "voice.md" }, - { "id": "voc", "label": "Voice-of-customer", "bucket": "B", "status": "missing", "why": "Real customer language for hooks.", "dataFile": "voc.md" }, - { "id": "legal", "label": "Legal & compliance", "bucket": "C", "status": "missing", "why": "Clears review on the first pass.", "dataFile": "legal-compliance.md" }, - { "id": "briefing-template", "label": "Briefing template", "bucket": "C", "status": "missing", "why": "Briefs come back in your exact format.", "dataFile": "briefing-template.md" }, - { "id": "source-of-truth", "label": "Source of truth", "bucket": "C", "status": "missing", "why": "Reports never fight your dashboard.", "dataFile": "source-of-truth.md" }, - { "id": "guardrails", "label": "Guardrails", "bucket": "C", "status": "missing", "why": "Always/never rules for creative.", "dataFile": "guardrails.md" } + { + "id": "brand-context", + "label": "Brand context", + "bucket": "A", + "status": "inferred", + "why": "Keeps every output unmistakably on-brand.", + "preview": "", + "dataFile": "brand-context.md" + }, + { + "id": "kpis-goal", + "label": "KPIs & goal", + "bucket": "A", + "status": "inferred", + "why": "Every readout leads with the metric you care about.", + "preview": "", + "dataFile": "kpis-goal.md" + }, + { + "id": "spend-threshold", + "label": "Spend threshold", + "bucket": "A", + "status": "inferred", + "why": "Separates a proven winner from early noise.", + "preview": "", + "dataFile": "spend-threshold.md" + }, + { + "id": "competitors", + "label": "Competitors", + "bucket": "B", + "status": "missing", + "why": "Angles to attack the gaps rivals leave open.", + "dataFile": "competitors.md" + }, + { + "id": "products", + "label": "Products & SKUs", + "bucket": "B", + "status": "missing", + "why": "Grounds every ad in real product facts.", + "dataFile": "products.md" + }, + { + "id": "positioning", + "label": "Positioning & personas", + "bucket": "B", + "status": "missing", + "why": "Right audience, right angle.", + "dataFile": "positioning.md" + }, + { + "id": "voice", + "label": "Voice & tone", + "bucket": "B", + "status": "missing", + "why": "Copy that sounds like you wrote it.", + "dataFile": "voice.md" + }, + { + "id": "voc", + "label": "Voice-of-customer", + "bucket": "B", + "status": "missing", + "why": "How your customers actually talk about the problem.", + "dataFile": "voc.md", + "enrich": "Enrich this by connecting your reviews platform (Yotpo, Okendo, Trustpilot) in chat. Real reviews are the richest source of customer language." + }, + { + "id": "legal", + "label": "Legal & compliance", + "bucket": "C", + "status": "missing", + "why": "Clears review on the first pass.", + "dataFile": "legal-compliance.md" + }, + { + "id": "briefing-template", + "label": "Briefing template", + "bucket": "C", + "status": "missing", + "why": "Briefs come back in your exact format.", + "dataFile": "briefing-template.md" + }, + { + "id": "source-of-truth", + "label": "Source of truth", + "bucket": "C", + "status": "missing", + "why": "Reports never fight your dashboard.", + "dataFile": "source-of-truth.md" + }, + { + "id": "guardrails", + "label": "Guardrails", + "bucket": "C", + "status": "missing", + "why": "Always/never rules for creative.", + "dataFile": "guardrails.md" + } ] } diff --git a/packages/context-kit/package.json b/packages/context-kit/package.json index 26457f17..decb779f 100644 --- a/packages/context-kit/package.json +++ b/packages/context-kit/package.json @@ -3,7 +3,7 @@ "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.3.0", + "version": "0.4.0", "installPolicy": "manual", "updatePolicy": "manual", "uninstallPolicy": "allowed", diff --git a/packages/context-kit/runneth-package.json b/packages/context-kit/runneth-package.json index ccd54ea8..8ca60dec 100644 --- a/packages/context-kit/runneth-package.json +++ b/packages/context-kit/runneth-package.json @@ -3,7 +3,7 @@ "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.3.0", + "version": "0.4.0", "updatePolicy": "manual", "uninstallPolicy": "allowed", "resources": [ diff --git a/packages/context-kit/skills/SKILL.md b/packages/context-kit/skills/SKILL.md index aacce681..5aba8a50 100644 --- a/packages/context-kit/skills/SKILL.md +++ b/packages/context-kit/skills/SKILL.md @@ -1,6 +1,6 @@ --- name: context-kit -description: Builds a customer's Context Kit, the institutional knowledge that makes every Runneth answer sharper. Reads the Context Kit state index, confirms what Motion knows, drafts brand-context and every Bucket B item from Motion creative data before asking anything, imports what already lives in Google Drive or Notion, and collects what only the customer knows, filling each into the brain and moving the completeness meter. Triggers on "build my context kit", "set up my context kit", "context kit", "build my brain", "sharpen Runneth", "what makes my answers better", "what do you still need from me", "what does Runneth know about us". +description: Builds a customer's Context Kit, the institutional knowledge that makes every Runneth answer sharper. Reads the Context Kit state index, confirms what Motion knows, drafts brand-context and every Bucket B item from Motion creative data before asking anything, imports what already lives in Google Drive or Notion, researches real customer language for voice-of-customer, and collects what only the customer knows, filling each into the brain and moving the completeness meter. Triggers on "build my context kit", "set up my context kit", "context kit", "build my brain", "sharpen Runneth", "what makes my answers better", "what do you still need from me", "what does Runneth know about us". --- # Context Kit skill @@ -19,7 +19,7 @@ guardrails. ## Step 0 — Load state 1. Read `/agent/brain/context-kit/context-kit-state.json`. 2. Run `motion brand-context --data-query "summary"`, `motion workspace-goal`, `motion spend-threshold`. -3. Note which context sources are connected (Google Drive, Notion). +3. Note which context sources are connected (Google Drive, Notion, and any reviews platform). ## Step 1 — Build and open the board (first run only) Sync stages files but does not build apps. Fill `/agent/apps/context-kit/buildeth.app.json` (replace @@ -30,55 +30,61 @@ at runtime, so later content/state changes need NO rebuild. ## Step 2 — Bucket A: confirm, and auto-draft brand-context - **kpis-goal, spend-threshold:** show the live Motion value, one-tap confirm, write the full doc, set the `preview` string in state, mark `confirmed`. -- **brand-context (auto-draft, never ask from scratch):** - 1. If `motion brand-context` returns content, show it and ask to confirm. - 2. If empty, say: "I don't have brand context saved yet, I'll draft it from your top-performing ad creative now." - 3. Pull top spend creative: `motion meta insights --date-range last_30d --sort topSpend --include-metrics` - (+ TikTok if connected). Read hooks, primary text, headlines, summaries. - 4. Draft `brand-context.md` as the FOUNDATION only: brand name, origin story, positioning statement, - product description, proof points, a 2-sentence tone summary, a 2-sentence audience summary. Do not - duplicate the full voice/VoC/competitor detail (those are their own files). - 5. Present the draft, ask to confirm, then write `/agent/brain/context-kit/brand-context.md`, save to - workspace config, mirror to `data/brand-context.md`, mark `confirmed`. +- **brand-context (auto-draft, never ask from scratch):** if `motion brand-context` returns content, show it + and confirm; if empty, say you'll draft it from top-performing creative, pull + `motion meta insights --date-range last_30d --sort topSpend --include-metrics` (+ TikTok if connected), + and draft the FOUNDATION only (brand name, origin story, positioning statement, product description, proof + points, 2-sentence tone summary, 2-sentence audience summary). Present, confirm, write, save to workspace + config, mirror to `data/`, mark `confirmed`. Do not duplicate the voice/voc/competitor detail here. -## Step 3 — Proactive import offer -After state load, if any Bucket B/C item is missing AND Google Drive or Notion is connected, proactively -say: "I can see you have [Drive/Notion] connected, want me to search there first before drafting from -scratch?" If they say yes, search, confirm the doc, import into `/agent/brain/context-kit/.md`, mirror -to `data/`, mark `imported`. Lean on existing context-sweep / integration skills for the pull. +## Step 3 — Proactive import + connect offers +After state load, if any Bucket B/C item is missing: +- If Google Drive or Notion is connected: "I can see you have [Drive/Notion] connected, want me to search + there first before drafting from scratch?" +- If NO reviews platform is connected: proactively suggest it, because it powers the best voice-of-customer: + "Connecting your reviews platform (Yotpo, Okendo, Trustpilot, Amazon) gives me your real customer language, + the single richest input for hooks and angles. Want to connect it?" +Import confirmed docs into `/agent/brain/context-kit/.md`, mirror to `data/`, mark `imported`. Lean on +existing context-sweep / integration skills for the pull. -## Step 4 — Bucket B: draft from Motion creative data first, then correct -For each item, attempt a Motion-backed draft BEFORE import or manual collection. A rough draft the user can -react to beats a blank prompt. Write full working docs to `/agent/brain/context-kit/.md`, mirror to -`data/.md`, set `drafted` (then `confirmed` on accept), and tell the user -"I drafted [item] from your Motion data, click the card to review or tell me what to change." +## Step 4 — Bucket B: draft from real data first, then correct +For each item, attempt a data-backed draft BEFORE asking. Write full working docs to +`/agent/brain/context-kit/.md`, mirror to `data/.md`, set `drafted` (then `confirmed` on accept), and +tell the user "I drafted [item], click the card to review or tell me what to change." - **competitors:** `motion inspo brands` (followed brands), then `motion inspo unique-creatives --brand-id - --sort-by impressionRank` per brand. Summarize who they are, what angles they run, what they - emphasize. Ask the user to add anyone missing. -- **products:** top spend creative via `motion meta insights` with summaries + glossary tags. Compile the - product list from repeated hero claims / SKU callouts / bundles, note claims per product. Ask the user to - add pricing, SKU codes, and anything not in copy. + --sort-by impressionRank` per brand. Who they are, what angles they run, what they emphasize. +- **products:** top spend creative via `motion meta insights` with summaries + glossary tags. Product list + from repeated hero claims / SKU callouts / bundles. Ask user for pricing/SKU codes not in copy. - **positioning:** `motion meta insights --include-glossary`. Read intended-audience, messaging-angle, and - hook-tactic tag distribution; combine with hook language to draft positioning + personas. Flag assumptions. -- **voice:** hooks, primary text, and summaries of the top 20-30 by spend. Synthesize 4-6 named voice - characteristics, each with a "sounds like / doesn't sound like" pair. Should feel recognizable to a brand writer. -- **voc:** top spend VIDEO creative with `--include-transcript`. Extract the most repeated real customer - phrases, emotional beats, outcome language; supplement with static hooks. Group by problem language, - outcome language, objections, social proof. -- If a pull returns too little (no followed brands, no transcripts), say what was missing and fall through to - import or manual for that item only. Never silently skip. + hook-tactic tag distribution + hook language to draft positioning + personas. Flag assumptions. +- **voice (the BRAND's voice, not the customer's):** hooks, primary text, and summaries of the top 20-30 by + spend. Synthesize 4-6 named voice characteristics, each with a "sounds like / doesn't sound like" pair. + This is how the brand should write. +- **voc (voice-of-customer = how the CUSTOMER sounds, NOT the brand):** real, unfiltered customer language. + Ad transcripts are the WEAKEST source (they are the brand's scripted version of the customer), so do not + rely on them. Draft it this way: + 1. If a reviews platform is connected, run the `review-audit` skill over the real reviews. + 2. Generate customer-language search terms with the `brand-relevant-keywords` skill (search the customer's + problem, not the brand name), then web-research Reddit, review sites, and social comments for real quotes. + 3. Use Motion video transcripts (`--include-transcript`) only as a supplement, labeled as ad-mediated + (best for UGC/testimonial phrasing). + Layer in the lived-context frame (Sarah Levinger): which generation, what shaped their trust, and 5-10 + trigger moments (specific everyday situations where the problem fires) with the emotion attached to each. + Output a swipe file in 7 categories, near-verbatim: pain points, emotional language, desire statements, + before/after arcs, objections, competitor complaints, trigger events. If no reviews platform is connected, + say so and suggest connecting one, then proceed with web research and transcripts. +- If a pull returns too little, say what was missing and fall through to import or manual for that item only. ## Step 5 — Bucket C: collect with depth legal, briefing-template, source-of-truth, guardrails: file drop when they have it, thought starters when -stuck, one prompt when short. Prompt for the COMPLETE rule set, not a one-liner. Write to -`/agent/brain/context-kit/.md` (legal -> legal-compliance.md), mirror to `data/`, mark `confirmed`. +stuck, one prompt when short. Prompt for the COMPLETE rule set. Write to `/agent/brain/context-kit/.md` +(legal -> legal-compliance.md), mirror to `data/`, mark `confirmed`. ## Step 6 — Keep the map correct -- Refresh `/agent/INDEX.md` entries pointing to `/agent/brain/context-kit/` as the canonical home for all - Context Kit files. No separate entries for scattered originals. +- Refresh `/agent/INDEX.md` entries pointing to `/agent/brain/context-kit/` as the canonical home. - After every change update BOTH `/agent/brain/context-kit/context-kit-state.json` and the app's - `data/context-kit-state.json` (the board reads the app copy). No rebuild needed for data-only changes. + `data/context-kit-state.json`. No rebuild needed for data-only changes. ## Rules - Never touch `user.md`. Never overwrite an already-filled file (create-if-absent, edit-in-place on confirm). From 2f3e45923c8edbb6f55069bf888c08cd41f821e6 Mon Sep 17 00:00:00 2001 From: "Runneth (for Vamsi)" Date: Mon, 6 Jul 2026 12:15:09 -0400 Subject: [PATCH 06/25] =?UTF-8?q?feat(context-kit):=20v0.5.0=20=E2=80=94?= =?UTF-8?q?=20glossary-tag=20spine=20+=20explicit=20fallback=20chain=20(dr?= =?UTF-8?q?afted=20vs=20inferred=20+=20sourceNote),=20board=20chrome=20red?= =?UTF-8?q?esign=20(brand-name=20title,=20compact=20nudge,=20numbered=20pa?= =?UTF-8?q?th=20+=20pips,=20butter=20color,=20unified=20View=20CTA),=20ful?= =?UTF-8?q?l=20markdown=20renderer,=20thoughtStarters=20+=20more=20enrich?= =?UTF-8?q?=20tips,=20bucket=20icons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-index.json | 4 +- .../context-kit/data/context-kit-state.json | 131 ++---------- .../apps/context-kit/src/pages/index.astro | 192 +++++++++++------- .../brain/context-kit/context-kit-state.json | 131 ++---------- packages/context-kit/package.json | 2 +- packages/context-kit/runneth-package.json | 2 +- packages/context-kit/skills/SKILL.md | 119 ++++++----- 7 files changed, 220 insertions(+), 361 deletions(-) diff --git a/package-index.json b/package-index.json index ce081490..286c212c 100644 --- a/package-index.json +++ b/package-index.json @@ -1,12 +1,12 @@ { "schemaVersion": 1, - "indexRevision": "context-kit-0.4.0", + "indexRevision": "context-kit-0.5.0", "packages": [ { "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.4.0", + "version": "0.5.0", "categories": ["baseline"], "packageManagerVersion": 1, "source": { diff --git a/packages/context-kit/apps/context-kit/data/context-kit-state.json b/packages/context-kit/apps/context-kit/data/context-kit-state.json index f2078a47..d9471b1d 100644 --- a/packages/context-kit/apps/context-kit/data/context-kit-state.json +++ b/packages/context-kit/apps/context-kit/data/context-kit-state.json @@ -1,122 +1,23 @@ { "version": 1, + "brandName": "", "levels": [ - { - "id": "L1", - "name": "Answers questions", - "state": "passed" - }, - { - "id": "L2", - "name": "On-brand outputs", - "state": "current" - }, - { - "id": "L3", - "name": "Runs with full context", - "state": "locked" - } + { "id": "L1", "name": "Answers questions", "state": "passed" }, + { "id": "L2", "name": "On-brand outputs", "state": "current" }, + { "id": "L3", "name": "Runs with full context", "state": "locked" } ], "items": [ - { - "id": "brand-context", - "label": "Brand context", - "bucket": "A", - "status": "inferred", - "why": "Keeps every output unmistakably on-brand.", - "preview": "", - "dataFile": "brand-context.md" - }, - { - "id": "kpis-goal", - "label": "KPIs & goal", - "bucket": "A", - "status": "inferred", - "why": "Every readout leads with the metric you care about.", - "preview": "", - "dataFile": "kpis-goal.md" - }, - { - "id": "spend-threshold", - "label": "Spend threshold", - "bucket": "A", - "status": "inferred", - "why": "Separates a proven winner from early noise.", - "preview": "", - "dataFile": "spend-threshold.md" - }, - { - "id": "competitors", - "label": "Competitors", - "bucket": "B", - "status": "missing", - "why": "Angles to attack the gaps rivals leave open.", - "dataFile": "competitors.md" - }, - { - "id": "products", - "label": "Products & SKUs", - "bucket": "B", - "status": "missing", - "why": "Grounds every ad in real product facts.", - "dataFile": "products.md" - }, - { - "id": "positioning", - "label": "Positioning & personas", - "bucket": "B", - "status": "missing", - "why": "Right audience, right angle.", - "dataFile": "positioning.md" - }, - { - "id": "voice", - "label": "Voice & tone", - "bucket": "B", - "status": "missing", - "why": "Copy that sounds like you wrote it.", - "dataFile": "voice.md" - }, - { - "id": "voc", - "label": "Voice-of-customer", - "bucket": "B", - "status": "missing", - "why": "How your customers actually talk about the problem.", - "dataFile": "voc.md", - "enrich": "Enrich this by connecting your reviews platform (Yotpo, Okendo, Trustpilot) in chat. Real reviews are the richest source of customer language." - }, - { - "id": "legal", - "label": "Legal & compliance", - "bucket": "C", - "status": "missing", - "why": "Clears review on the first pass.", - "dataFile": "legal-compliance.md" - }, - { - "id": "briefing-template", - "label": "Briefing template", - "bucket": "C", - "status": "missing", - "why": "Briefs come back in your exact format.", - "dataFile": "briefing-template.md" - }, - { - "id": "source-of-truth", - "label": "Source of truth", - "bucket": "C", - "status": "missing", - "why": "Reports never fight your dashboard.", - "dataFile": "source-of-truth.md" - }, - { - "id": "guardrails", - "label": "Guardrails", - "bucket": "C", - "status": "missing", - "why": "Always/never rules for creative.", - "dataFile": "guardrails.md" - } + { "id": "brand-context", "label": "Brand context", "bucket": "A", "status": "inferred", "why": "Keeps every output unmistakably on-brand.", "preview": "", "dataFile": "brand-context.md" }, + { "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", "status": "inferred", "why": "Every readout leads with the metric you care about.", "preview": "", "dataFile": "kpis-goal.md" }, + { "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", "status": "inferred", "why": "Separates a proven winner from early noise.", "preview": "", "dataFile": "spend-threshold.md" }, + { "id": "competitors", "label": "Competitors", "bucket": "B", "status": "missing", "why": "Angles to attack the gaps rivals leave open.", "dataFile": "competitors.md", "enrich": "Follow brands in Motion Inspo so Runneth can research their live ads and creative angles automatically." }, + { "id": "products", "label": "Products & SKUs", "bucket": "B", "status": "missing", "why": "Grounds every ad in real product facts.", "dataFile": "products.md", "enrich": "Connect your Shopify store for live product data, pricing, and bestseller rankings." }, + { "id": "positioning", "label": "Positioning & personas", "bucket": "B", "status": "missing", "why": "Right audience, right angle.", "dataFile": "positioning.md" }, + { "id": "voice", "label": "Voice & tone", "bucket": "B", "status": "missing", "why": "Copy that sounds like you wrote it.", "dataFile": "voice.md" }, + { "id": "voc", "label": "Voice-of-customer", "bucket": "B", "status": "missing", "why": "How your customers actually talk about the problem.", "dataFile": "voc.md", "enrich": "Connect your reviews platform (Yotpo, Okendo, Trustpilot) for real customer language, the richest VoC source available." }, + { "id": "legal", "label": "Legal & compliance", "bucket": "C", "status": "missing", "why": "Clears review on the first pass.", "dataFile": "legal-compliance.md", "thoughtStarters": ["Any claims you legally can't make? (clinically proven, #1, cures, guaranteed)", "Required disclaimers or fine print on specific claims?", "Regulated categories that apply? (health, finance, beauty, supplements, kids)", "Words or comparisons legal has flagged before?"] }, + { "id": "briefing-template", "label": "Briefing template", "bucket": "C", "status": "missing", "why": "Briefs come back in your exact format.", "dataFile": "briefing-template.md", "thoughtStarters": ["Do you have an existing brief format you want matched? Drop it in chat.", "What sections does every brief need? (hook, angle, format, CTA)", "Who reads the brief, and what do they need from it?"] }, + { "id": "source-of-truth", "label": "Source of truth", "bucket": "C", "status": "missing", "why": "Reports never fight your dashboard.", "dataFile": "source-of-truth.md", "enrich": "Connect your attribution platform (Northbeam, Triple Whale, or similar) so Runneth always reports from your preferred source.", "thoughtStarters": ["When the ad platform and your attribution tool disagree, which wins?", "What's your primary success metric and attribution window?", "Any metric you explicitly don't trust or want ignored?", "Which dashboard does your team review in meetings?"] }, + { "id": "guardrails", "label": "Guardrails", "bucket": "C", "status": "missing", "why": "Always/never rules for creative.", "dataFile": "guardrails.md", "thoughtStarters": ["What should Runneth NEVER suggest in a creative concept?", "Any visual styles or creative directions that are always off-limits?", "Non-negotiables for claims, tone, or offers?", "Any past ad types or messages to avoid repeating?"] } ] } diff --git a/packages/context-kit/apps/context-kit/src/pages/index.astro b/packages/context-kit/apps/context-kit/src/pages/index.astro index 416a0efe..9bdc7363 100644 --- a/packages/context-kit/apps/context-kit/src/pages/index.astro +++ b/packages/context-kit/apps/context-kit/src/pages/index.astro @@ -10,43 +10,48 @@
- Context Kit -

Your Context Kit

-

The more Runneth knows about your brand, the sharper every answer. Confirm what we pulled, review what we drafted, add the few things only you know. Click any card to see what's saved.

-

📄 Already have brand docs in Google Drive or Notion? Connect them in chat and Runneth can pull from them directly.

+

Context Kit

+

The more Runneth knows about your brand, the sharper every answer. Confirm what we pulled, review what we drafted, add the few things only you know.

+
+
🔗
+
+
Import from Google Drive or Notion
+
Connect them in chat and Runneth can pull your brand docs directly.
+
+
💡 @@ -83,30 +106,52 @@ diff --git a/packages/context-kit/brain/context-kit/context-kit-state.json b/packages/context-kit/brain/context-kit/context-kit-state.json index 79ce2941..18d2c3d5 100644 --- a/packages/context-kit/brain/context-kit/context-kit-state.json +++ b/packages/context-kit/brain/context-kit/context-kit-state.json @@ -144,6 +144,62 @@ "Non-negotiables for claims, tone, or offers?", "Any past ad types or messages to avoid repeating?" ] + }, + { + "id": "integration-ad-platform", + "label": "Ad platform", + "bucket": "D", + "status": "missing", + "why": "How you want Runneth to read your ad data.", + "dataFile": "integrations/ad-platform.md", + "enrich": "Connect your ad platform (Meta, TikTok) so Runneth can draft this from your live account.", + "thoughtStarters": [ + "Which metric should Runneth grade on?", + "Any spend floor before a result counts?", + "Campaigns that are always-on and shouldn't be flagged as new?" + ] + }, + { + "id": "integration-asset-library", + "label": "Asset library", + "bucket": "D", + "status": "missing", + "why": "Which assets Runneth can safely use.", + "dataFile": "integrations/asset-library.md", + "enrich": "Connect your asset library (Google Drive, a DAM) so Runneth pulls only approved, brand-safe assets.", + "thoughtStarters": [ + "Which folder holds cleared, approved assets?", + "What's off-limits (WIP, legal review)?", + "How do you mark a final, shippable version?" + ] + }, + { + "id": "integration-data-warehouse", + "label": "Data warehouse", + "bucket": "D", + "status": "missing", + "why": "How Runneth should read your warehouse.", + "dataFile": "integrations/data-warehouse.md", + "enrich": "Connect your warehouse (Snowflake, BigQuery) for blended reporting Runneth can reconcile against.", + "thoughtStarters": [ + "Which table is the source of truth for revenue?", + "Net or gross of fees?", + "What should Runneth NOT pull from here?" + ] + }, + { + "id": "integration-reviews", + "label": "Reviews platform", + "bucket": "D", + "status": "missing", + "why": "How Runneth should use your reviews.", + "dataFile": "integrations/reviews.md", + "enrich": "Connect your reviews platform (Yotpo, Okendo, Trustpilot) so Runneth mines real customer language.", + "thoughtStarters": [ + "Which products' reviews matter most?", + "Any themes you already know customers repeat?", + "Anything to exclude (spam, off-topic)?" + ] } ] } diff --git a/packages/context-kit/instructions/behavior.md b/packages/context-kit/instructions/behavior.md index 803d220e..c5155638 100644 --- a/packages/context-kit/instructions/behavior.md +++ b/packages/context-kit/instructions/behavior.md @@ -49,3 +49,17 @@ reads), not only when the Context Kit skill runs. entry. Never write to `user.md`. 3. Keep it to one offer per turn. This is a helpful nudge, not nagging. The goal is that Runneth visibly gets smarter about this brand the more it is used. + + +## Bucket A import contract (Auto-filled tab) + +The three Auto-filled items (brand-context, kpis-goal, spend-threshold) come from Motion but the customer can correct them. Store each as two sections so a correction survives a re-sync: + +- `## Latest Import From Motion` - the most recent value pulled from Motion. +- `## Runneth Instructions` - the customer's corrections and rules. + +When the two conflict, follow `Runneth Instructions` unless the customer explicitly chooses the Motion value, and say it plainly: "Motion currently says X, but your instructions say Y. I am using Y." A refresh updates only `Latest Import From Motion` and preserves `Runneth Instructions`. + +## Integration source guides (Your tools tab) + +Bucket D holds one guide per connected source: how THIS customer wants Runneth to use that tool, not what the tool can do. Files live at `/agent/brain/context-kit/integrations/.md`, mirrored to the board's `data/integrations/.md`. Create or fill a guide when the customer connects that source or gives rules for it; do not invent vendor-specific guides otherwise. Each guide covers: what it applies to, what to use it for, what to avoid, the customer's rules, and any open question. Before performance, reporting, or asset work that uses a connected source, read its guide first. Bucket D is tracked separately from the completeness meter, which measures brand knowledge only. diff --git a/packages/context-kit/package.json b/packages/context-kit/package.json index 17f357bf..72234d72 100644 --- a/packages/context-kit/package.json +++ b/packages/context-kit/package.json @@ -3,7 +3,7 @@ "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.8.1", + "version": "0.9.0", "installPolicy": "manual", "updatePolicy": "manual", "uninstallPolicy": "allowed", diff --git a/packages/context-kit/runneth-package.json b/packages/context-kit/runneth-package.json index 47f47c1c..f27afec6 100644 --- a/packages/context-kit/runneth-package.json +++ b/packages/context-kit/runneth-package.json @@ -3,7 +3,7 @@ "id": "context-kit", "name": "Context Kit", "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.8.1", + "version": "0.9.0", "updatePolicy": "manual", "uninstallPolicy": "allowed", "resources": [ diff --git a/packages/context-kit/skills/SKILL.md b/packages/context-kit/skills/SKILL.md index fc3dc8a7..239c8302 100644 --- a/packages/context-kit/skills/SKILL.md +++ b/packages/context-kit/skills/SKILL.md @@ -98,3 +98,13 @@ thoughtStarters when stuck, one prompt when short. Prompt for the COMPLETE rule - Plain, non-technical language with the customer. No file paths or JSON in chat. - One item at a time; celebrate the Level 3 unlock when the meter completes. - The self-improvement loop is ALWAYS ON (see the package instruction): on any creative-strategy turn, if a context-kit file was thin and you inferred, say so and offer once to capture the detail, then save it to the right file. Keep it to one in-chat offer, no persistent queue. + + +## Bucket A: two-section import contract +When writing brand-context, kpis-goal, and spend-threshold, use the durable import format so a Motion re-sync never wipes a customer correction: +- `## Latest Import From Motion` holds the current Motion value. +- `## Runneth Instructions` holds the customer's corrections and rules. +On conflict, follow `Runneth Instructions` and say so plainly. A refresh updates only the import section and preserves instructions. Confirming still moves the item to `confirmed`. + +## Bucket D: integration source guides (Your tools) +Bucket D is one card per connected source, capturing how the customer wants Runneth to USE that tool (not what it can do). Create-when-connected: when a customer connects a source or gives rules for it, write `/agent/brain/context-kit/integrations/.md`, mirror to `data/integrations/.md`, and set the item `confirmed` (or `imported` if pulled from an existing doc). Each guide answers: what it applies to (account/workspace/folder/table/brand), what to use it for, what to avoid, the customer's rules, and any open question. Leave the shipped generic scaffolds (ad-platform, asset-library, data-warehouse, reviews) `missing` with their thoughtStarters until the customer connects that source. Bucket D is excluded from the completeness meter. diff --git a/scripts/validate-runneth-package-index.mjs b/scripts/validate-runneth-package-index.mjs index eef87b71..48bf8c3b 100644 --- a/scripts/validate-runneth-package-index.mjs +++ b/scripts/validate-runneth-package-index.mjs @@ -152,8 +152,14 @@ const assertPackageManifest = (manifest, label) => { `${label}.uninstallPolicy: invalid`, ) assert.ok(Array.isArray(manifest.resources), `${label}.resources: must be array`) + const resourceIds = new Set() manifest.resources.forEach((resource, index) => { assertPackageResource(resource, `${label}.resources[${index}]`) + assert.ok( + !resourceIds.has(resource.id), + `${label}.resources: duplicate resource id ${resource.id}`, + ) + resourceIds.add(resource.id) }) } From cdb611e2313cfeddf152463198addfb3fe2a5af8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 20 Jul 2026 10:41:34 +0000 Subject: [PATCH 12/25] feat(context-kit): v1.0.0 brand knowledge refresh Co-authored-by: vamsi --- package-index.json | 6 +- .../brain/context-kit/business-team.md | 16 ++ .../brain/context-kit/landing-pages.md | 16 ++ .../brain/context-kit/media-buying.md | 20 ++ packages/context-kit/instructions/behavior.md | 88 +++--- packages/context-kit/package.json | 67 +++-- packages/context-kit/runneth-package.json | 44 ++- packages/context-kit/skills/SKILL.md | 189 ++++++------ .../workflows/context-kit-refresh.ts | 272 ++++++++++++++++++ 9 files changed, 553 insertions(+), 165 deletions(-) create mode 100755 packages/context-kit/brain/context-kit/business-team.md create mode 100755 packages/context-kit/brain/context-kit/landing-pages.md create mode 100755 packages/context-kit/brain/context-kit/media-buying.md create mode 100755 packages/context-kit/workflows/context-kit-refresh.ts diff --git a/package-index.json b/package-index.json index 27ac24c5..91508910 100644 --- a/package-index.json +++ b/package-index.json @@ -1,12 +1,12 @@ { "schemaVersion": 1, - "indexRevision": "context-kit-0.9.0", + "indexRevision": "context-kit-1.0.0", "packages": [ { "id": "context-kit", "name": "Context Kit", - "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.9.0", + "description": "Builds the brand knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill. Install first.", + "version": "1.0.0", "categories": ["baseline"], "packageManagerVersion": 1, "source": { diff --git a/packages/context-kit/brain/context-kit/business-team.md b/packages/context-kit/brain/context-kit/business-team.md new file mode 100755 index 00000000..ce94a8a1 --- /dev/null +++ b/packages/context-kit/brain/context-kit/business-team.md @@ -0,0 +1,16 @@ +# Business & Team + +## Runneth Instructions + +_Fill this in so Runneth knows who is using it and how decisions get made._ + +--- + +**Thought starters:** +- Brand name and website URL. +- Who uses Runneth on the team, and what are their roles? +- Who makes the final call on creative concepts before they go into production? +- Who approves creative before launch? +- What is the typical launch cadence — how many new concepts per week/month? +- How are briefs delivered to creators or the production team (Notion, email, Slack)? +- Are there any external agencies or freelancers involved in creative production? diff --git a/packages/context-kit/brain/context-kit/landing-pages.md b/packages/context-kit/brain/context-kit/landing-pages.md new file mode 100755 index 00000000..783f2f04 --- /dev/null +++ b/packages/context-kit/brain/context-kit/landing-pages.md @@ -0,0 +1,16 @@ +# Landing Pages + +## Runneth Instructions + +_Fill this in so Runneth knows where ads send traffic and what is being optimized for._ + +--- + +**Thought starters:** +- What are the main landing page URLs you drive traffic to? +- What is each page optimized for (trial signup, demo booking, purchase, lead form)? +- Have you run any landing page tests? What did you learn? +- Which landing pages correlate with your strongest-performing creative? +- Are there any pages that consistently underperform despite strong creative? +- Do different audiences or funnels go to different pages? +- Are there pages you want Runneth to avoid recommending traffic to? diff --git a/packages/context-kit/brain/context-kit/media-buying.md b/packages/context-kit/brain/context-kit/media-buying.md new file mode 100755 index 00000000..785c09b1 --- /dev/null +++ b/packages/context-kit/brain/context-kit/media-buying.md @@ -0,0 +1,20 @@ +# Media Buying Model + +## Latest Import From Motion + +_Not yet populated. The skill will attempt to infer from campaign structure on first run._ + +## Runneth Instructions + +_Fill this in so Runneth knows how budget decisions are made._ + +--- + +**Thought starters:** +- What is the bid strategy (lowest cost, cost cap, target ROAS)? +- How is budget structured — CBO, ABO, or mixed? +- What is the testing budget per concept? +- What spend threshold does a creative need to reach before you make a call on it? +- What does the campaign architecture look like — how many campaigns, how are ad sets organized? +- How do you handle audience targeting — broad, interest, retargeting? +- How often does budget get redistributed between campaigns? diff --git a/packages/context-kit/instructions/behavior.md b/packages/context-kit/instructions/behavior.md index c5155638..42b9106a 100644 --- a/packages/context-kit/instructions/behavior.md +++ b/packages/context-kit/instructions/behavior.md @@ -1,65 +1,55 @@ # Context Kit package instructions -This package installs the Context Kit: the institutional knowledge that makes every Runneth answer -sharper. It seeds structured-but-empty scaffolds, a status index, the Context Kit board app, and the -Context Kit skill. These instructions are loaded as prompt context so Runneth knows the surface exists. +This package installs the Context Kit: brand knowledge that makes every Runneth answer sharper. It seeds structured-but-empty scaffolds, a status index, the Context Kit board app, and the Context Kit skill. -## What Runneth should know from moment one +Naming conventions, per-campaign KPI maps, and the Motion query contract are in the **ad-naming** companion package. Install Context Kit first, then ad-naming. -- There is a Context Kit board app at `agent_apps/context-kit`. Package sync stages files but does NOT - build apps. To build it: fill the bundled `buildeth.app.json` (replace `__CONVERSATION_ID__` and - `__WORKSPACE_ID__` with the current context), then `app build context-kit`. `astro.config.mjs` (base - `/context-kit`) ships with the package. -- The board is client-rendered: it fetches `data/context-kit-state.json` and the Bucket B `data/*.md` - files at runtime. Update those data files to change what the board shows. A rebuild is only needed for - app source changes, not content/state changes. -- State lives at `/agent/brain/context-kit/context-kit-state.json` (source of truth) and is mirrored to - `/agent/apps/context-kit/data/context-kit-state.json` (what the board reads). -- Write-in scaffolds: `/agent/brain/context-kit/` (legal-compliance.md, source-of-truth.md, guardrails.md) - and `/agent/brain/templates/briefing-template.md`. - -## Read-before-work rules (retrieval awareness) - -- Before creative work, read `/agent/brain/context-kit/legal-compliance.md` and - `/agent/brain/templates/briefing-template.md` if filled. -- Before performance/reporting, honor `/agent/brain/context-kit/source-of-truth.md`. -- Before customer-facing output, honor `/agent/brain/context-kit/guardrails.md`. -- Bucket B context lives in `/agent/brain/brand-audit//` and is covered by the creative-strategy - read-before-work rules. +## Knoweth lane model -## Rules +Context Kit registers three lanes on first run. The skill handles registration — do NOT use `user.md` guards. -- Never write Context Kit content into `user.md`. Fill happens as deliberate in-conversation brain writes. -- Prefer importing existing docs from Google Drive / Notion before asking the customer to type. -- Scaffolds are create-if-absent. Never overwrite a file a customer has already filled. -- As each item is filled, refresh its `/agent/INDEX.md` entry. Do not blind-overwrite `INDEX.md` from sync. +| Lane | What it injects | When it fires | +|---|---|---| +| `context-kit-core` | State, guardrails, legal, source-of-truth, briefing-template | Always | +| `context-kit-brand` | Brand-context, voice, voc, positioning, products, competitors | Creative, briefing, concept turns | +| `context-kit-performance` | KPIs-goal, spend-threshold | Performance and reporting turns | -## Self-improvement loop (always on, not just during onboarding) +If **ad-naming** is installed, it registers its own `ad-naming` lane covering the naming decoder, KPI map, and query contract. If **creative-corpus** is installed, it registers its own `creative-corpus` lane. Neither is owned or registered by this package. + +## What Runneth should know from moment one -This fires on EVERY creative-strategy turn (hooks, briefs, concepts, scripts, ad copy, performance -reads), not only when the Context Kit skill runs. +- Board app at `agent_apps/context-kit`. Package sync stages files but does NOT build apps. Build: fill `buildeth.app.json`, then `app build context-kit`. +- Board is client-rendered: fetches `data/context-kit-state.json` and `data/*.md` at runtime. Rebuild only needed for source changes, not content. +- State: `/agent/brain/context-kit/context-kit-state.json` (source of truth), mirrored to `data/context-kit-state.json`. +- Lanes: registered by the skill on first run. Check `lanesRegistered` in state. +- Refresh workflow: registered by the skill on first run. Check `refreshWorkflowId` in state. -1. Before answering, read the relevant `/agent/brain/context-kit/` files (per the read-before-work rules). -2. If a needed file was empty, thin, or you had to infer to answer well, then AFTER the answer: - - Say plainly what was missing and that you inferred it (don't hide it). - - Make ONE specific, low-friction offer to capture it, e.g. "I inferred your tone because your Voice - page is light. Want me to save your take so I nail it next time?" - - On yes: write the detail to the correct `/agent/brain/context-kit/.md`, mirror to the board's - `data/.md`, update `context-kit-state.json` (status + preview), and refresh the `/agent/INDEX.md` - entry. Never write to `user.md`. -3. Keep it to one offer per turn. This is a helpful nudge, not nagging. The goal is that Runneth visibly - gets smarter about this brand the more it is used. +## Self-improvement loop (always on) +Fires on every creative-strategy turn. -## Bucket A import contract (Auto-filled tab) +1. The relevant lane files are already injected. Use them. +2. If a needed file was empty or thin, AFTER the answer: + - Say plainly what was missing. + - Make ONE specific offer to capture it. + - On yes: write the file, mirror to `data/`, update state, refresh INDEX. +3. Keep it to one offer per turn. -The three Auto-filled items (brand-context, kpis-goal, spend-threshold) come from Motion but the customer can correct them. Store each as two sections so a correction survives a re-sync: +## Bucket A import contract -- `## Latest Import From Motion` - the most recent value pulled from Motion. -- `## Runneth Instructions` - the customer's corrections and rules. +Each Auto-filled item uses two sections: +- `## Latest Import From Motion` — most recent value from Motion. +- `## Runneth Instructions` — customer corrections and rules. -When the two conflict, follow `Runneth Instructions` unless the customer explicitly chooses the Motion value, and say it plainly: "Motion currently says X, but your instructions say Y. I am using Y." A refresh updates only `Latest Import From Motion` and preserves `Runneth Instructions`. +On conflict, follow `Runneth Instructions`. A refresh updates only `Latest Import From Motion`. -## Integration source guides (Your tools tab) +## Integration source guides (Your tools tab, Bucket D) + +Files at `/agent/brain/context-kit/integrations/.md`, mirrored to `data/integrations/.md`. The ad-platform guide (the account-specific Motion query contract) is provided by ad-naming when installed — do not create it here. Other guides use thoughtStarters until the customer connects that source. + +## Rules -Bucket D holds one guide per connected source: how THIS customer wants Runneth to use that tool, not what the tool can do. Files live at `/agent/brain/context-kit/integrations/.md`, mirrored to the board's `data/integrations/.md`. Create or fill a guide when the customer connects that source or gives rules for it; do not invent vendor-specific guides otherwise. Each guide covers: what it applies to, what to use it for, what to avoid, the customer's rules, and any open question. Before performance, reporting, or asset work that uses a connected source, read its guide first. Bucket D is tracked separately from the completeness meter, which measures brand knowledge only. +- Never write into `user.md`. +- Prefer importing from Drive/Notion before asking the customer to type. +- Scaffolds are create-if-absent. Never overwrite a file the customer has filled. +- Refresh INDEX.md as each item is filled. diff --git a/packages/context-kit/package.json b/packages/context-kit/package.json index 72234d72..dc07ff64 100644 --- a/packages/context-kit/package.json +++ b/packages/context-kit/package.json @@ -2,8 +2,8 @@ "schemaVersion": 1, "id": "context-kit", "name": "Context Kit", - "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.9.0", + "description": "Builds the brand knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill. Install first, then ad-naming for naming conventions and query contract, then creative-corpus for the full creative library.", + "version": "1.0.0", "installPolicy": "manual", "updatePolicy": "manual", "uninstallPolicy": "allowed", @@ -17,70 +17,77 @@ "id": "context-kit-state", "type": "file", "sourcePath": "brain/context-kit/context-kit-state.json", - "target": { - "root": "agent_brain", - "path": "context-kit/context-kit-state.json" - }, + "target": { "root": "agent_brain", "path": "context-kit/context-kit-state.json" }, "executable": false }, { "id": "legal-compliance", "type": "file", "sourcePath": "brain/context-kit/legal-compliance.md", - "target": { - "root": "agent_brain", - "path": "context-kit/legal-compliance.md" - }, + "target": { "root": "agent_brain", "path": "context-kit/legal-compliance.md" }, "executable": false }, { "id": "source-of-truth", "type": "file", "sourcePath": "brain/context-kit/source-of-truth.md", - "target": { - "root": "agent_brain", - "path": "context-kit/source-of-truth.md" - }, + "target": { "root": "agent_brain", "path": "context-kit/source-of-truth.md" }, "executable": false }, { "id": "guardrails", "type": "file", "sourcePath": "brain/context-kit/guardrails.md", - "target": { - "root": "agent_brain", - "path": "context-kit/guardrails.md" - }, + "target": { "root": "agent_brain", "path": "context-kit/guardrails.md" }, "executable": false }, { "id": "briefing-template", "type": "file", "sourcePath": "brain/context-kit/briefing-template.md", - "target": { - "root": "agent_brain", - "path": "context-kit/briefing-template.md" - }, + "target": { "root": "agent_brain", "path": "context-kit/briefing-template.md" }, + "executable": false + }, + { + "id": "media-buying-scaffold", + "type": "file", + "sourcePath": "brain/context-kit/media-buying.md", + "target": { "root": "agent_brain", "path": "context-kit/media-buying.md" }, + "executable": false + }, + { + "id": "business-team-scaffold", + "type": "file", + "sourcePath": "brain/context-kit/business-team.md", + "target": { "root": "agent_brain", "path": "context-kit/business-team.md" }, + "executable": false + }, + { + "id": "landing-pages-scaffold", + "type": "file", + "sourcePath": "brain/context-kit/landing-pages.md", + "target": { "root": "agent_brain", "path": "context-kit/landing-pages.md" }, + "executable": false + }, + { + "id": "context-kit-refresh-workflow", + "type": "file", + "sourcePath": "workflows/context-kit-refresh.ts", + "target": { "root": "agent_brain", "path": "context-kit/workflows/context-kit-refresh.ts" }, "executable": false }, { "id": "context-kit-skill", "type": "directory", "sourcePath": "skills", - "target": { - "root": "agent_skills", - "path": "context-kit" - }, + "target": { "root": "agent_skills", "path": "context-kit" }, "executablePaths": [] }, { "id": "context-kit-app", "type": "directory", "sourcePath": "apps/context-kit", - "target": { - "root": "agent_apps", - "path": "context-kit" - }, + "target": { "root": "agent_apps", "path": "context-kit" }, "executablePaths": [] } ] diff --git a/packages/context-kit/runneth-package.json b/packages/context-kit/runneth-package.json index f27afec6..bf56f11b 100644 --- a/packages/context-kit/runneth-package.json +++ b/packages/context-kit/runneth-package.json @@ -2,8 +2,8 @@ "schemaVersion": 1, "id": "context-kit", "name": "Context Kit", - "description": "Builds the institutional knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill that confirms what Motion knows, imports what already lives in Drive/Notion, researches what it can, and collects the rest.", - "version": "0.9.0", + "description": "Builds the brand knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill. Install first, then ad-naming for naming conventions and query contract, then creative-corpus for the full creative library.", + "version": "1.0.0", "updatePolicy": "manual", "uninstallPolicy": "allowed", "resources": [ @@ -62,6 +62,46 @@ }, "executable": false }, + { + "id": "media-buying-scaffold", + "type": "file", + "sourcePath": "brain/context-kit/media-buying.md", + "target": { + "root": "agent_brain", + "path": "context-kit/media-buying.md" + }, + "executable": false + }, + { + "id": "business-team-scaffold", + "type": "file", + "sourcePath": "brain/context-kit/business-team.md", + "target": { + "root": "agent_brain", + "path": "context-kit/business-team.md" + }, + "executable": false + }, + { + "id": "landing-pages-scaffold", + "type": "file", + "sourcePath": "brain/context-kit/landing-pages.md", + "target": { + "root": "agent_brain", + "path": "context-kit/landing-pages.md" + }, + "executable": false + }, + { + "id": "context-kit-refresh-workflow", + "type": "file", + "sourcePath": "workflows/context-kit-refresh.ts", + "target": { + "root": "agent_brain", + "path": "context-kit/workflows/context-kit-refresh.ts" + }, + "executable": false + }, { "id": "context-kit-skill", "type": "directory", diff --git a/packages/context-kit/skills/SKILL.md b/packages/context-kit/skills/SKILL.md index 239c8302..5bc822e5 100644 --- a/packages/context-kit/skills/SKILL.md +++ b/packages/context-kit/skills/SKILL.md @@ -1,110 +1,137 @@ --- name: context-kit -description: Builds a customer's Context Kit, the institutional knowledge that makes every Runneth answer sharper. Reads the Context Kit state index, confirms what Motion knows, drafts brand-context and every Bucket B item from Motion creative data (AI glossary tag distribution first) before asking anything, imports what already lives in Google Drive or Notion, researches real customer language for voice-of-customer, and collects what only the customer knows, filling each into the brain and moving the completeness meter. Triggers on "build my context kit", "set up my context kit", "context kit", "build my brain", "sharpen Runneth", "what makes my answers better", "what do you still need from me", "what does Runneth know about us". +description: Builds a customer's Context Kit, the brand knowledge that makes every Runneth answer sharper. Reads the Context Kit state index, confirms what Motion knows, drafts brand-context and every Bucket B item from Motion creative data before asking anything, imports what already lives in Google Drive or Notion, and collects what only the customer knows. Triggers on "build my context kit", "set up my context kit", "context kit", "build my brain", "sharpen Runneth", "what do you still need from me", "what does Runneth know about us". --- # Context Kit skill -Turn a fresh brain into a filled one, without the manual sales/CS back-and-forth. The board app is the -mirror; this skill is the doer. Draft from Motion data first, then import, then collect. Never write to -`user.md`. Fill happens as deliberate in-conversation brain writes. +Turn a fresh brain into a filled one. The board app is the mirror; this skill is the doer. Draft from Motion data first, then import, then collect. Never write to `user.md`. + +Naming conventions, per-campaign KPI maps, and the Motion query contract are handled by the **ad-naming** companion package. Install that after Context Kit when the account has a structured naming system. ## Single source of truth -Every one of the 12 files lives in `/agent/brain/context-kit/.md` and is mirrored to -`/agent/apps/context-kit/data/.md` so the board can fetch it. Do NOT scatter files into -`brand-audit/` or `templates/`. Item ids: brand-context, kpis-goal, spend-threshold, competitors, -products, positioning, voice, voc, legal (file legal-compliance.md), briefing-template, source-of-truth, -guardrails. -## Status meaning (drives the board color, keep it honest) +Every one of the 15 files lives in `/agent/brain/context-kit/.md` and is mirrored to `/agent/apps/context-kit/data/.md` so the board can fetch it. + +Item ids: brand-context, kpis-goal, spend-threshold, competitors, products, positioning, voice, voc, legal (file legal-compliance.md), briefing-template, source-of-truth, guardrails, media-buying, business-team, landing-pages. + +Integration guides: `/agent/brain/context-kit/integrations/.md`, mirrored to `data/integrations/.md`. + +## Status meaning - `confirmed` / `imported`: locked in by the customer (green). -- `drafted` (butter): built from ACTUAL Motion data (glossary tags, creative summaries, transcripts, Inspo). -- `inferred` (blue): written from general brand knowledge because Motion was unavailable. MUST carry a - `sourceNote` explaining that, so the customer knows to scrutinize it. +- `drafted`: built from ACTUAL Motion data. Must carry real data, not general knowledge. +- `inferred`: written from general brand knowledge because Motion was unavailable. MUST carry a `sourceNote`. - `missing`: nothing yet. ## Step 0 — Load state + set brand name + 1. Read `/agent/brain/context-kit/context-kit-state.json`. -2. Set top-level `brandName` from `motion workspaces` (current workspace name) or brand context, BEFORE the - first state write, so the board title reads "'s Context Kit" from first load. +2. Set top-level `brandName` from `motion workspaces` or brand context before the first state write. 3. Run `motion brand-context --data-query "summary"`, `motion workspace-goal`, `motion spend-threshold`. -4. Note which context sources are connected (Google Drive, Notion, reviews platform, Shopify, attribution). +4. Note which context sources are connected (Google Drive, Notion, reviews platform). + +## Step 0b — Register Knoweth lanes (first run only) + +If `context-kit-state.json` shows `lanesRegistered: false` or the field is absent, register the three core lanes before any brain writes: + +| Lane ID | Path | Patterns | +|---|---|---| +| `context-kit-core` | `/agent/brain/context-kit/` | `context-kit-state.json`, `guardrails.md`, `legal-compliance.md`, `source-of-truth.md`, `briefing-template.md` | +| `context-kit-brand` | `/agent/brain/context-kit/` | `brand-context.md`, `voice.md`, `voc.md`, `positioning.md`, `products.md`, `competitors.md` | +| `context-kit-performance` | `/agent/brain/context-kit/` | `kpis-goal.md`, `spend-threshold.md` | + +Set `lanesRegistered: true` in state. Do not re-register on subsequent runs. + +## Step 0c — Register the refresh workflow (first run only) + +If `context-kit-state.json` shows `refreshWorkflowId` absent: +1. Read `/agent/brain/context-kit/workflows/context-kit-refresh.ts`. +2. Run `workflow push /agent/brain/context-kit/workflows/context-kit-refresh.ts --name context-kit-refresh`. +3. Save the workflow ID to state as `refreshWorkflowId`. +4. `task add --kind workflow --workflow-id --name "Context Kit Refresh"` → save as `refreshTaskId`. +5. Mirror state. ## Step 1 — Build and open the board (first run only) -Sync stages files but does not build apps. Fill `/agent/apps/context-kit/buildeth.app.json` (replace -`__CONVERSATION_ID__` and `__WORKSPACE_ID__`), run `app build context-kit`, then `app list` for the URL. -`astro.config.mjs` ships in the package. After first build the board reads `data/` at runtime, no rebuild -needed for content/state changes. - -## Step 2 — Bucket A: confirm, and auto-draft brand-context -- **kpis-goal, spend-threshold:** show the live Motion value, one-tap confirm, write the full doc, set the - `preview` string, mark `confirmed`. -- **brand-context (auto-draft, never ask from scratch):** if `motion brand-context` has content, show and - confirm; if empty, draft from `motion meta insights --date-range last_30d --sort topSpend --include-metrics` - (+ TikTok if connected). Foundation only (brand name, origin story, positioning statement, product - description, proof points, 2-sentence tone, 2-sentence audience). Present, confirm, write, save to workspace - config, mirror to `data/`, mark `confirmed`. + +Fill `/agent/apps/context-kit/buildeth.app.json` (replace `__CONVERSATION_ID__` and `__WORKSPACE_ID__`), run `app build context-kit`, then `app list` for the URL. + +## Step 2 — Bucket A: confirm and auto-draft brand-context + +- **kpis-goal:** show the live Motion workspace-goal value. Present, confirm, write the full doc with `## Latest Import From Motion` and blank `## Runneth Instructions`. Mark `confirmed`. Note: the per-campaign KPI map is handled by the **ad-naming** package. +- **spend-threshold:** same pattern. +- **brand-context:** if `motion brand-context` has content, show and confirm; if empty, draft from `motion meta insights --date-range last_30d --sort topSpend --include-metrics`. Foundation only (brand name, origin story, positioning, product description, proof points, 2-sentence tone, 2-sentence audience). Present, confirm, write, save to workspace config, mirror, mark `confirmed`. ## Step 3 — Proactive import + connect offers + If any Bucket B/C item is missing: -- Drive/Notion connected: "I see you have [Drive/Notion] connected, want me to search there first?" -- No reviews platform: suggest it (powers the best voice-of-customer). -Import confirmed docs into `/agent/brain/context-kit/.md`, mirror to `data/`, mark `imported`. - -## Step 4 — Bucket B: glossary tag distribution is the data spine, then fallback chain -Before drafting ANY Bucket B item, pull the ground-truth spine once: -1. `motion ai-glossary` (which tag categories exist for this workspace). -2. `motion meta insights --include-glossary --date-range last_30d --sort topSpend` and read the tag - distribution from `creatives[].glossaryTags[]`. This is what the brand actually bets spend on. - -Category-to-item mapping (use as the factual spine, then layer language on top): -- `intended-audience` -> positioning (who the top-spend ads target) -- `messaging-angle` -> positioning + voice (what they lead with, how they frame claims) -- `hook-tactic` -> voice + voc (how ads open, which customer-language patterns win spend) -- `visual-format` + `asset-type` -> voice (UGC vs polished vs demo vs testimonial) -- `offer-type` -> products (which offer structures attach to which products) -- `seasonality` -> products + competitors - -Per-item **fallback chain** (explicit, sequential): -1. Motion first: glossary spine + creative summaries + hooks; Inspo brands for **competitors**; video - transcripts (`--include-transcript`) for **voc**; `review-audit` + `brand-relevant-keywords` + web research - for real customer language on **voc** (ad transcripts are only a labeled supplement there). -2. Good Motion signal -> draft, status `drafted`. -3. Motion empty or errors -> if Drive/Notion connected, search + import, status `imported`. -4. Neither -> draft from general brand knowledge, status `inferred`, and set a `sourceNote` like - "Drafted from general brand knowledge, Motion ad data was unavailable. Review and correct." -5. Even that unreliable -> leave `missing` and show thought starters / ask. -After each: mirror the file to `data/.md`, tell the user "I drafted [item], click the card to review or -tell me what to change." Never silently skip; say what was missing. - -Content depth: **voice** is the brand's voice (4-6 named characteristics with sounds-like/doesn't pairs). -**voc** is the customer's voice: a 7-category swipe file (pain, emotional language, desire, before/after, -objections, competitor complaints, trigger events), near-verbatim, plus the Sarah Levinger lived-context -layer (generation, what shaped their trust, 5-10 trigger moments with the emotion on each). +- Drive/Notion connected: offer to search there first. +- No reviews platform: suggest connecting it (powers voice-of-customer). + +Import confirmed docs, mirror, mark `imported`. + +## Step 4 — Bucket B: glossary spine, then draft each item + +Pull the ground-truth spine once: +1. `motion ai-glossary` +2. `motion meta insights --include-glossary --date-range last_30d --sort topSpend` — read tag distribution from `creatives[].glossaryTags[]` + +Category-to-item mapping: +- `intended-audience` → positioning + voc +- `messaging-angle` → positioning + voice +- `hook-tactic` → voice + voc +- `visual-format` + `asset-type` → voice +- `offer-type` → products +- `seasonality` → products + competitors + +Per-item fallback chain (explicit, sequential): +1. Motion glossary spine + summaries + transcripts → draft, status `drafted`. +2. Motion empty → Drive/Notion if connected → status `imported`. +3. Neither → general brand knowledge → status `inferred` + `sourceNote`. +4. Still unreliable → leave `missing`, show thought starters. + +After each: mirror to `data/.md`, tell the user what was drafted. + +**voice**: 4-6 named characteristics with sounds-like/doesn't-sound-like pairs. +**voc**: 7-category swipe file (pain, emotional language, desire, before/after, objections, competitor complaints, trigger events), near-verbatim from transcripts. ## Step 5 — Bucket C: collect with depth -legal, briefing-template, source-of-truth, guardrails: file drop when they have it, use the item's -thoughtStarters when stuck, one prompt when short. Prompt for the COMPLETE rule set. Write to -`/agent/brain/context-kit/.md` (legal -> legal-compliance.md), mirror to `data/`, mark `confirmed`. + +legal, briefing-template, source-of-truth, guardrails, media-buying, business-team, landing-pages: file drop when they have it, thought starters when stuck. Write, mirror, mark `confirmed`. ## Step 6 — Keep the map correct -- Refresh `/agent/INDEX.md` entries pointing to `/agent/brain/context-kit/` as the canonical home. -- After every change update BOTH state copies (`/agent/brain/context-kit/context-kit-state.json` and the - app's `data/context-kit-state.json`). No rebuild needed for data-only changes. -## Rules -- Never touch `user.md`. Never overwrite an already-filled file (create-if-absent, edit-in-place on confirm). -- Reserve `drafted` for real Motion-data content; use `inferred` + a `sourceNote` for general-knowledge drafts. -- Plain, non-technical language with the customer. No file paths or JSON in chat. -- One item at a time; celebrate the Level 3 unlock when the meter completes. -- The self-improvement loop is ALWAYS ON (see the package instruction): on any creative-strategy turn, if a context-kit file was thin and you inferred, say so and offer once to capture the detail, then save it to the right file. Keep it to one in-chat offer, no persistent queue. +- Refresh `/agent/INDEX.md` for all `/agent/brain/context-kit/` files. +- Update BOTH state copies after every change. + +## Step 7 — Offer the weekly refresh routine +After the completeness meter hits 100%, offer: + +``` +routine add \ + --name "Context Kit weekly refresh" \ + --cron "0 9 * * 1" \ + --delivery "Send a summary in a new web conversation." \ + --prompt "Run the context-kit refresh task (task id: ). Wait for completion with task wait. Read the returned JSON. Open a new conversation summarising what changed — highlight shifts in voice, voc, or competitors. Flag items not updated in 3+ weeks as stale. Deliver with conversation send --new." +``` + +Save routine ID to state as `refreshRoutineId`. + +## Rules +- Never touch `user.md`. +- Create-if-absent; edit-in-place on confirm. Never overwrite a filled file. +- Reserve `drafted` for real Motion data. Use `inferred` + `sourceNote` for general-knowledge drafts. +- Plain language with the customer. No file paths or JSON in chat. ## Bucket A: two-section import contract -When writing brand-context, kpis-goal, and spend-threshold, use the durable import format so a Motion re-sync never wipes a customer correction: -- `## Latest Import From Motion` holds the current Motion value. -- `## Runneth Instructions` holds the customer's corrections and rules. -On conflict, follow `Runneth Instructions` and say so plainly. A refresh updates only the import section and preserves instructions. Confirming still moves the item to `confirmed`. -## Bucket D: integration source guides (Your tools) -Bucket D is one card per connected source, capturing how the customer wants Runneth to USE that tool (not what it can do). Create-when-connected: when a customer connects a source or gives rules for it, write `/agent/brain/context-kit/integrations/.md`, mirror to `data/integrations/.md`, and set the item `confirmed` (or `imported` if pulled from an existing doc). Each guide answers: what it applies to (account/workspace/folder/table/brand), what to use it for, what to avoid, the customer's rules, and any open question. Leave the shipped generic scaffolds (ad-platform, asset-library, data-warehouse, reviews) `missing` with their thoughtStarters until the customer connects that source. Bucket D is excluded from the completeness meter. +- `## Latest Import From Motion` — current Motion value. +- `## Runneth Instructions` — customer corrections and rules. + +On conflict, follow `Runneth Instructions`. A refresh updates only the import section. + +## Bucket D: integration source guides + +Files at `/agent/brain/context-kit/integrations/.md`, mirrored to `data/integrations/.md`. The ad-platform guide (workspace ID, attribution windows, conversion events, metric gotchas) is provided by the **ad-naming** package when installed. Other guides (asset-library, data-warehouse, reviews) use their thoughtStarters until the customer connects that source. + +Bucket D excluded from completeness meter. diff --git a/packages/context-kit/workflows/context-kit-refresh.ts b/packages/context-kit/workflows/context-kit-refresh.ts new file mode 100755 index 00000000..95a08aed --- /dev/null +++ b/packages/context-kit/workflows/context-kit-refresh.ts @@ -0,0 +1,272 @@ +import type { Workflow } from "@runneth/workflow/v1"; + +type RefreshOutput = { + changed: string[]; + unchanged: string[]; + stale: string[]; + highlights: string[]; + lastRefreshed: string; + itemsUpdated: number; +}; + +/** + * Context Kit Brand Refresh Workflow + * + * Re-pulls all Motion-sourced brand data for Bucket A and Bucket B items. + * Does NOT touch naming decoder, KPI map, or query contract — those are + * owned by the ad-naming package and its own refresh workflow. + * + * Writes ONLY "## Latest Import From Motion" sections. + * Preserves "## Runneth Instructions" exactly. + * + * Motion CLI pattern for bash tasks: + * ENVELOPE=$(motion ...) # capture envelope to var + * FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') # parse file path + * jq '...' "$FILE" # read actual data + * + * Agent tasks write structured output to /tmp/ck-results/*.json. + * Bash tasks read those files. No stdout-parsing of agent responses. + */ +export const wf: Workflow, RefreshOutput> = async ({ + task, +}) => { + // ─── Step 1: Pull Motion data via bash ─────────────────────────────────── + + await task.bash({ + key: "pull-workspace-data", + script: ` + set -euo pipefail + mkdir -p /tmp/ck-refresh + + ENVELOPE=$(motion workspace-goal) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + cp "$FILE" /tmp/ck-refresh/workspace-goal.json + + ENVELOPE=$(motion spend-threshold) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + cp "$FILE" /tmp/ck-refresh/spend-threshold.json + + ENVELOPE=$(motion brand-context --data-query "summary") + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + cp "$FILE" /tmp/ck-refresh/brand-context.json + + echo "workspace-data-done" + `, + }); + + await task.bash({ + key: "pull-glossary-spine", + script: ` + set -euo pipefail + + ENVELOPE=$(motion ai-glossary) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + cp "$FILE" /tmp/ck-refresh/glossary.json + + ENVELOPE=$(motion meta insights --include-glossary --date-range last_30d --sort topSpend --limit 100) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + cp "$FILE" /tmp/ck-refresh/insights-glossary.json + + echo "glossary-done" + `, + }); + + await task.bash({ + key: "pull-inspo-brands", + script: ` + set -euo pipefail + + ENVELOPE=$(motion inspo brands --limit 20) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + cp "$FILE" /tmp/ck-refresh/inspo-brands.json + + echo "inspo-done" + `, + }); + + await task.bash({ + key: "pull-voc-transcripts", + script: ` + set -euo pipefail + + ENVELOPE=$(motion meta insights --include-transcript --date-range last_30d --sort topSpend --limit 20) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + cp "$FILE" /tmp/ck-refresh/transcripts.json + + echo "voc-done" + `, + }); + + // ─── Step 2: Agent re-drafts each item ─────────────────────────────────── + // Each agent writes a result JSON to /tmp/ck-results/.json. + // Format: {"changed": true/false, "preview": "one short sentence"} + + await task.bash({ + key: "setup-results-dir", + script: `mkdir -p /tmp/ck-results`, + }); + + await task.agent({ + key: "update-brand-context", + prompt: ` + Update /agent/brain/context-kit/brand-context.md from refreshed Motion data. + + Source files: /tmp/ck-refresh/brand-context.json and /tmp/ck-refresh/insights-glossary.json + (read .creatives[].glossaryTags[] from insights-glossary for tag distribution). + + Rules: + - Read the current file first. Preserve any "## Runneth Instructions" section exactly. + - Rewrite ONLY "## Latest Import From Motion": brand name, positioning, product description, + proof points from top-spend tags, 2-sentence tone, 2-sentence audience. + - Write back to /agent/brain/context-kit/brand-context.md. + - Mirror to /agent/apps/context-kit/data/brand-context.md. + - Write {"changed": true/false, "preview": "one short sentence"} to /tmp/ck-results/brand-context.json. + `, + }); + + await task.agent({ + key: "update-kpis-goal", + prompt: ` + Update /agent/brain/context-kit/kpis-goal.md from refreshed Motion data. + + Source: /tmp/ck-refresh/workspace-goal.json (primary KPI and attribution windows). + + Note: the per-campaign KPI map is owned by the ad-naming package. + Only update the primary KPI, conversion event, and attribution window sections here. + + Rules: + - Preserve "## Runneth Instructions" exactly. + - Rewrite only "## Latest Import From Motion". + - Write back and mirror to data/kpis-goal.md. + - Write {"changed": true/false, "preview": "..."} to /tmp/ck-results/kpis-goal.json. + `, + }); + + await task.agent({ + key: "update-spend-threshold", + prompt: ` + Update /agent/brain/context-kit/spend-threshold.md from /tmp/ck-refresh/spend-threshold.json. + Preserve "## Runneth Instructions". Rewrite only "## Latest Import From Motion". + Write back, mirror, write {"changed": true/false, "preview": "..."} to /tmp/ck-results/spend-threshold.json. + `, + }); + + await task.agent({ + key: "update-voice", + prompt: ` + Update /agent/brain/context-kit/voice.md from /tmp/ck-refresh/insights-glossary.json. + Read .creatives[].glossaryTags[] — focus on visual-format, asset-type, hook-tactic, messaging-angle by spend. + Derive 4-6 named voice characteristics (sounds-like / doesn't-sound-like pairs). + Preserve "## Runneth Instructions". Rewrite only "## Latest Import From Motion". + If Motion data is empty: status = "inferred", add sourceNote. + Write back, mirror, write {"changed": true/false, "preview": "..."} to /tmp/ck-results/voice.json. + `, + }); + + await task.agent({ + key: "update-voc", + prompt: ` + Update /agent/brain/context-kit/voc.md from /tmp/ck-refresh/transcripts.json. + Read .creatives[].transcript for top-spend videos. + Build or refresh the 7-category swipe file with near-verbatim language: + pain, emotional language, desire, before/after, objections, competitor complaints, trigger events. + Preserve "## Runneth Instructions". Rewrite only "## Latest Import From Motion". + If transcripts are empty, note it clearly. + Write back, mirror, write {"changed": true/false, "preview": "..."} to /tmp/ck-results/voc.json. + `, + }); + + await task.agent({ + key: "update-competitors", + prompt: ` + Update /agent/brain/context-kit/competitors.md from /tmp/ck-refresh/inspo-brands.json. + Read .data.brands[] for the customer's followed brands. + Note name, Motion brand ID, active ad count signal for each. + Flag brands with substantially more or fewer ads than the current file shows. + Preserve "## Runneth Instructions". Rewrite only "## Latest Import From Motion". + Write back, mirror, write {"changed": true/false, "preview": "..."} to /tmp/ck-results/competitors.json. + `, + }); + + // ─── Step 3: Collect results + check staleness ──────────────────────────── + + const collectResult = await task.bash({ + key: "collect-results", + script: ` + set -euo pipefail + + node -e " + const fs = require('fs'); + const items = ['brand-context', 'kpis-goal', 'spend-threshold', 'voice', 'voc', 'competitors']; + const changed = []; + const unchanged = []; + const highlights = []; + + for (const item of items) { + try { + const result = JSON.parse(fs.readFileSync('/tmp/ck-results/' + item + '.json', 'utf8')); + if (result.changed) { + changed.push(item); + if (result.preview) highlights.push(item + ': ' + result.preview); + } else { + unchanged.push(item); + } + } catch { + unchanged.push(item); + } + } + + const stale = []; + try { + const state = JSON.parse(fs.readFileSync('/agent/brain/context-kit/context-kit-state.json', 'utf8')); + const cutoff = Date.now() - (21 * 86400 * 1000); + for (const [id, item] of Object.entries(state.items || {})) { + if (item.updatedAt && new Date(item.updatedAt).getTime() < cutoff && item.status !== 'missing') { + stale.push(id); + } + } + } catch {} + + const today = new Date().toISOString().slice(0, 10); + console.log(JSON.stringify({ changed, unchanged, stale, highlights, lastRefreshed: today, itemsUpdated: changed.length })); + " + `, + }); + + // ─── Step 4: Update state ───────────────────────────────────────────────── + + await task.bash({ + key: "update-state", + script: ` + set -euo pipefail + TODAY=$(node -e "console.log(new Date().toISOString().slice(0,10))") + + node -e " + const fs = require('fs'); + let state = {}; + try { state = JSON.parse(fs.readFileSync('/agent/brain/context-kit/context-kit-state.json', 'utf8')); } catch {} + state.lastRefreshed = '$TODAY'; + const out = JSON.stringify(state, null, 2); + fs.writeFileSync('/agent/brain/context-kit/context-kit-state.json', out); + fs.writeFileSync('/agent/apps/context-kit/data/context-kit-state.json', out); + " + `, + }); + + let output: RefreshOutput = { + changed: [], + unchanged: [], + stale: [], + highlights: [], + lastRefreshed: new Date().toISOString().slice(0, 10), + itemsUpdated: 0, + }; + + try { + output = JSON.parse(collectResult.stdout.trim()); + } catch { + // Return partial result + } + + return output; +}; From aaed20ed8196eebb7f3c8bf728c8bb0ff862a52a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 20 Jul 2026 10:42:27 +0000 Subject: [PATCH 13/25] fix(context-kit): align package contract metadata Co-authored-by: vamsi --- package-index.json | 2 +- packages/context-kit/brain/context-kit/business-team.md | 0 packages/context-kit/brain/context-kit/landing-pages.md | 0 packages/context-kit/brain/context-kit/media-buying.md | 0 packages/context-kit/workflows/context-kit-refresh.ts | 0 5 files changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 packages/context-kit/brain/context-kit/business-team.md mode change 100755 => 100644 packages/context-kit/brain/context-kit/landing-pages.md mode change 100755 => 100644 packages/context-kit/brain/context-kit/media-buying.md mode change 100755 => 100644 packages/context-kit/workflows/context-kit-refresh.ts diff --git a/package-index.json b/package-index.json index 91508910..a235266a 100644 --- a/package-index.json +++ b/package-index.json @@ -5,7 +5,7 @@ { "id": "context-kit", "name": "Context Kit", - "description": "Builds the brand knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill. Install first.", + "description": "Builds the brand knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill. Install first, then ad-naming for naming conventions and query contract, then creative-corpus for the full creative library.", "version": "1.0.0", "categories": ["baseline"], "packageManagerVersion": 1, diff --git a/packages/context-kit/brain/context-kit/business-team.md b/packages/context-kit/brain/context-kit/business-team.md old mode 100755 new mode 100644 diff --git a/packages/context-kit/brain/context-kit/landing-pages.md b/packages/context-kit/brain/context-kit/landing-pages.md old mode 100755 new mode 100644 diff --git a/packages/context-kit/brain/context-kit/media-buying.md b/packages/context-kit/brain/context-kit/media-buying.md old mode 100755 new mode 100644 diff --git a/packages/context-kit/workflows/context-kit-refresh.ts b/packages/context-kit/workflows/context-kit-refresh.ts old mode 100755 new mode 100644 From 7c73985ba5e58f95d9a73aa4a2f0a1f344ee29f3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 20 Jul 2026 10:43:29 +0000 Subject: [PATCH 14/25] =?UTF-8?q?feat(package):=20add=20ad-naming=20?= =?UTF-8?q?=E2=80=94=20naming=20decoder,=20KPI=20map,=20query=20contract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: vamsi --- package-index.json | 19 +- packages/ad-naming/brain/ad-naming-state.json | 11 + packages/ad-naming/brain/kpi-map.md | 7 + packages/ad-naming/brain/naming-decoder.md | 11 + packages/ad-naming/brain/query-contract.md | 7 + packages/ad-naming/instructions/behavior.md | 41 ++++ packages/ad-naming/package.json | 59 +++++ packages/ad-naming/runneth-package.json | 58 +++++ packages/ad-naming/skills/SKILL.md | 150 ++++++++++++ .../ad-naming/workflows/ad-naming-refresh.ts | 224 ++++++++++++++++++ 10 files changed, 586 insertions(+), 1 deletion(-) create mode 100644 packages/ad-naming/brain/ad-naming-state.json create mode 100644 packages/ad-naming/brain/kpi-map.md create mode 100644 packages/ad-naming/brain/naming-decoder.md create mode 100644 packages/ad-naming/brain/query-contract.md create mode 100644 packages/ad-naming/instructions/behavior.md create mode 100644 packages/ad-naming/package.json create mode 100644 packages/ad-naming/runneth-package.json create mode 100644 packages/ad-naming/skills/SKILL.md create mode 100644 packages/ad-naming/workflows/ad-naming-refresh.ts diff --git a/package-index.json b/package-index.json index a235266a..6c470f09 100644 --- a/package-index.json +++ b/package-index.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "indexRevision": "context-kit-1.0.0", + "indexRevision": "ad-naming-0.1.0", "packages": [ { "id": "context-kit", @@ -18,6 +18,23 @@ }, "updatePolicy": "manual", "uninstallPolicy": "allowed" + }, + { + "id": "ad-naming", + "name": "Ad Naming", + "description": "Builds the account intelligence layer that powers precise analysis. Decodes the ad naming convention into a reusable taxonomy, maps per-campaign KPI targets, and auto-discovers the Motion query contract (workspace ID, attribution windows, conversion events, metric gotchas). Install after Context Kit. Required before Creative Corpus for decoded naming in corpus files.", + "version": "0.1.0", + "categories": ["creative-strategy"], + "packageManagerVersion": 1, + "source": { + "type": "github", + "owner": "Motion-Creative", + "repo": "runneth-apps", + "ref": "main", + "path": "packages/ad-naming" + }, + "updatePolicy": "manual", + "uninstallPolicy": "allowed" } ] } diff --git a/packages/ad-naming/brain/ad-naming-state.json b/packages/ad-naming/brain/ad-naming-state.json new file mode 100644 index 00000000..1cc3ab43 --- /dev/null +++ b/packages/ad-naming/brain/ad-naming-state.json @@ -0,0 +1,11 @@ +{ + "schemaVersion": 1, + "id": "ad-naming", + "name": "Ad Naming", + "lastBuildDate": null, + "lastRefreshed": null, + "lanesRegistered": false, + "refreshWorkflowId": null, + "refreshTaskId": null, + "refreshRoutineId": null +} diff --git a/packages/ad-naming/brain/kpi-map.md b/packages/ad-naming/brain/kpi-map.md new file mode 100644 index 00000000..d492a1b7 --- /dev/null +++ b/packages/ad-naming/brain/kpi-map.md @@ -0,0 +1,7 @@ +# Per-Campaign KPI Map + +_Not yet built. Run "build my naming decoder" — the KPI map is built as part of that process._ + +--- + +This file maps each campaign segment to its optimization target, primary conversion event, and decision rules. It supplements the primary KPI goal in Context Kit with account-specific per-campaign logic. diff --git a/packages/ad-naming/brain/naming-decoder.md b/packages/ad-naming/brain/naming-decoder.md new file mode 100644 index 00000000..2d0ab383 --- /dev/null +++ b/packages/ad-naming/brain/naming-decoder.md @@ -0,0 +1,11 @@ +# Naming Decoder + +_Not yet built. Run "build my naming decoder" to auto-generate this from your ad names._ + +--- + +**Thought starters if Motion data is unavailable:** +- What delimiters do you use in ad names (underscores, dashes, pipes)? +- What dimensions do you track (platform, format, hook, creator, angle, offer)? +- Do you use a prefix system like `p-`, `fs-`, `hto-`? +- Are there multiple naming systems across different campaigns? diff --git a/packages/ad-naming/brain/query-contract.md b/packages/ad-naming/brain/query-contract.md new file mode 100644 index 00000000..677166b0 --- /dev/null +++ b/packages/ad-naming/brain/query-contract.md @@ -0,0 +1,7 @@ +# Motion Query Contract + +_Not yet built. Run "build my naming decoder" — the query contract is auto-discovered as part of that process._ + +--- + +This file is the source of truth for how to call Motion for this account: workspace ID, attribution windows, conversion event metric keys, thumbstop availability, and known data-layer gotchas. Read before any performance pull. diff --git a/packages/ad-naming/instructions/behavior.md b/packages/ad-naming/instructions/behavior.md new file mode 100644 index 00000000..2097b43d --- /dev/null +++ b/packages/ad-naming/instructions/behavior.md @@ -0,0 +1,41 @@ +# Ad Naming package instructions + +This package builds the account intelligence layer: naming decoder, per-campaign KPI map, and Motion query contract. Install after Context Kit, before Creative Corpus. + +## Knoweth lane + +One lane covers all three ad-naming brain files: + +| Lane ID | Path | Patterns | +|---|---|---| +| `ad-naming` | `/agent/brain/ad-naming/` | `naming-decoder.md`, `kpi-map.md`, `query-contract.md` | + +The skill registers this lane on first run. Check `lanesRegistered` in `/agent/brain/ad-naming/ad-naming-state.json`. + +## What each file does + +- **naming-decoder.md** — the account's ad naming taxonomy: each dimension (prefix, full name, observed values, plain-language meanings), a decode template, and worked examples. Used by the creative corpus to decode ad names in corpus files. Used by the agent to decode any ad name in chat. +- **kpi-map.md** — per-campaign KPI targets: one row per campaign segment derived from the naming decoder, showing the optimization target, dominant conversion event, and decision rules (testing cut / graduation threshold). Supplements `kpis-goal.md` in Context Kit. +- **query-contract.md** — the account's Motion CLI contract: workspace ID, attribution windows (click/view), conversion events with their exact metric keys, thumbstop availability, and data-layer gotchas (null fields, timeout risks). Read before any performance pull. + +## Read-before-work rules + +- Before any performance or reporting turn: read `query-contract.md` for attribution windows and metric key contracts. +- Before any ad name analysis: read `naming-decoder.md` to decode dimensions. +- Before any campaign-level analysis or KPI comparison: read `kpi-map.md`. + +The `ad-naming` lane injects these automatically. If a file is still empty, say so rather than inferring. + +## Refresh workflow + +Registered by the skill on first run. Refreshes all three files from live Motion data on demand or on the weekly schedule offered after first build. Saves `refreshWorkflowId` and `refreshTaskId` in state. + +## Works independently + +Ad Naming does not require Context Kit to be installed, though the two are designed to be used together. If Context Kit is installed, the ad-naming skill can supplement `kpis-goal.md` in the context-kit board by adding the per-campaign KPI map as a linked reference. + +## Rules + +- Never write ad-naming content into `user.md`. +- naming-decoder.md and kpi-map.md are derived from real Motion data — status `drafted`. If Motion is unavailable, status `inferred` with sourceNote. +- query-contract.md is auto-discovered on first run. Customer review recommended before treating as canonical. diff --git a/packages/ad-naming/package.json b/packages/ad-naming/package.json new file mode 100644 index 00000000..37eb7201 --- /dev/null +++ b/packages/ad-naming/package.json @@ -0,0 +1,59 @@ +{ + "schemaVersion": 1, + "id": "ad-naming", + "name": "Ad Naming", + "description": "Builds the account intelligence layer that powers precise analysis. Decodes the ad naming convention into a reusable taxonomy, maps per-campaign KPI targets, and auto-discovers the Motion query contract (workspace ID, attribution windows, conversion events, metric gotchas). Install after Context Kit. Required before Creative Corpus for decoded naming in corpus files.", + "version": "0.1.0", + "installPolicy": "manual", + "updatePolicy": "manual", + "uninstallPolicy": "allowed", + "resources": [ + { + "id": "ad-naming-behavior", + "type": "package_instruction", + "sourcePath": "instructions/behavior.md" + }, + { + "id": "ad-naming-state", + "type": "file", + "sourcePath": "brain/ad-naming/ad-naming-state.json", + "target": { "root": "agent_brain", "path": "ad-naming/ad-naming-state.json" }, + "executable": false + }, + { + "id": "naming-decoder-scaffold", + "type": "file", + "sourcePath": "brain/ad-naming/naming-decoder.md", + "target": { "root": "agent_brain", "path": "ad-naming/naming-decoder.md" }, + "executable": false + }, + { + "id": "kpi-map-scaffold", + "type": "file", + "sourcePath": "brain/ad-naming/kpi-map.md", + "target": { "root": "agent_brain", "path": "ad-naming/kpi-map.md" }, + "executable": false + }, + { + "id": "query-contract-scaffold", + "type": "file", + "sourcePath": "brain/ad-naming/query-contract.md", + "target": { "root": "agent_brain", "path": "ad-naming/query-contract.md" }, + "executable": false + }, + { + "id": "ad-naming-refresh-workflow", + "type": "file", + "sourcePath": "workflows/ad-naming-refresh.ts", + "target": { "root": "agent_brain", "path": "ad-naming/workflows/ad-naming-refresh.ts" }, + "executable": false + }, + { + "id": "ad-naming-skill", + "type": "directory", + "sourcePath": "skills", + "target": { "root": "agent_skills", "path": "ad-naming" }, + "executablePaths": [] + } + ] +} diff --git a/packages/ad-naming/runneth-package.json b/packages/ad-naming/runneth-package.json new file mode 100644 index 00000000..96d9577f --- /dev/null +++ b/packages/ad-naming/runneth-package.json @@ -0,0 +1,58 @@ +{ + "schemaVersion": 1, + "id": "ad-naming", + "name": "Ad Naming", + "description": "Builds the account intelligence layer that powers precise analysis. Decodes the ad naming convention into a reusable taxonomy, maps per-campaign KPI targets, and auto-discovers the Motion query contract (workspace ID, attribution windows, conversion events, metric gotchas). Install after Context Kit. Required before Creative Corpus for decoded naming in corpus files.", + "version": "0.1.0", + "updatePolicy": "manual", + "uninstallPolicy": "allowed", + "resources": [ + { + "id": "ad-naming-behavior", + "type": "package_instruction", + "sourcePath": "instructions/behavior.md" + }, + { + "id": "ad-naming-state", + "type": "file", + "sourcePath": "brain/ad-naming/ad-naming-state.json", + "target": { "root": "agent_brain", "path": "ad-naming/ad-naming-state.json" }, + "executable": false + }, + { + "id": "naming-decoder-scaffold", + "type": "file", + "sourcePath": "brain/ad-naming/naming-decoder.md", + "target": { "root": "agent_brain", "path": "ad-naming/naming-decoder.md" }, + "executable": false + }, + { + "id": "kpi-map-scaffold", + "type": "file", + "sourcePath": "brain/ad-naming/kpi-map.md", + "target": { "root": "agent_brain", "path": "ad-naming/kpi-map.md" }, + "executable": false + }, + { + "id": "query-contract-scaffold", + "type": "file", + "sourcePath": "brain/ad-naming/query-contract.md", + "target": { "root": "agent_brain", "path": "ad-naming/query-contract.md" }, + "executable": false + }, + { + "id": "ad-naming-refresh-workflow", + "type": "file", + "sourcePath": "workflows/ad-naming-refresh.ts", + "target": { "root": "agent_brain", "path": "ad-naming/workflows/ad-naming-refresh.ts" }, + "executable": false + }, + { + "id": "ad-naming-skill", + "type": "directory", + "sourcePath": "skills", + "target": { "root": "agent_skills", "path": "ad-naming" }, + "executablePaths": [] + } + ] +} diff --git a/packages/ad-naming/skills/SKILL.md b/packages/ad-naming/skills/SKILL.md new file mode 100644 index 00000000..d1d7381c --- /dev/null +++ b/packages/ad-naming/skills/SKILL.md @@ -0,0 +1,150 @@ +--- +name: ad-naming +description: Builds the account's naming decoder, per-campaign KPI map, and Motion query contract. Triggers on "build my naming decoder", "decode my ad names", "set up ad naming", "build the query contract", "what's my naming convention", "what are my campaign KPIs", "set up ad naming", "decode this ad name". +--- + +# Ad Naming skill + +Build the three account intelligence files from live Motion data. These underpin precise analysis, corpus decoding, and correct Motion CLI usage. + +## Step 0 — Load state + +Read `/agent/brain/ad-naming/ad-naming-state.json`. If `lastBuildDate` exists, the files are already built — offer to refresh instead. + +## Step 0b — Register Knoweth lane (first run only) + +If `lanesRegistered` is absent or false: +1. Call ContextConfig to register the `ad-naming` lane at `/agent/brain/ad-naming/` covering `naming-decoder.md`, `kpi-map.md`, `query-contract.md`. +2. Set `lanesRegistered: true` in state. + +## Step 0c — Register the refresh workflow (first run only) + +If `refreshWorkflowId` is absent: +1. Read `/agent/brain/ad-naming/workflows/ad-naming-refresh.ts`. +2. `workflow push /agent/brain/ad-naming/workflows/ad-naming-refresh.ts --name ad-naming-refresh` +3. Save workflow ID as `refreshWorkflowId`. +4. `task add --kind workflow --workflow-id --name "Ad Naming Refresh"` → save as `refreshTaskId`. +5. Mirror state. + +## Step 1 — Build the naming decoder + +1. Pull a sample of ad names: + ``` + motion meta ads --grain adnames --date-range last_90d --sort-by spend --sort-direction desc --limit 200 + ``` + Capture the envelope, parse `.file`, read `.data.summaryRows[].adName` or `.data.result.adnames[].adName`. + +2. Inspect the ad names. Identify repeating delimited patterns (prefixes, separators, values). Common patterns: `p-`, `fs-`, `as-`, `ex-`, `hto-`, `afs-`, `cn-`, `o-`. + +3. Write `/agent/brain/ad-naming/naming-decoder.md`: + ```markdown + # Naming Decoder + + ## System: + + | Prefix | Dimension | Observed values (plain meaning) | + |--------|-----------|--------------------------------| + | p- | Platform | fb (Facebook), ig (Instagram) | + | fs- | Format | vid (video), img (image) | + ... + + ## Decode template + `{platform}-{format}-{angle}-{hook}-...` → decoded meaning + + ## Worked example + `p-fb_fs-vid_as-fear_hto-hook1` decodes as: + - Platform: Facebook + - Format: video + - Angle: fear-based + - Hook tactic: hook variant 1 + ``` + +4. Status `drafted` if built from real data, `inferred` if pattern was unclear. +5. Update state. Tell the user: "I built the naming decoder from 200 top-spend ad names. Click to review — let me know if any dimensions are wrong." + +## Step 2 — Build the per-campaign KPI map + +1. Read the naming decoder (just built or already on file). +2. Pull campaign context: + ``` + motion workspace-goal + motion meta custom-conversion-metrics + ``` + Capture envelopes, parse file paths, read data. + +3. Identify campaign segments from the naming decoder's campaign dimension (if present) and from `motion meta insights --date-range last_30d --sort topSpend --limit 100` campaign name field. + +4. For each segment, determine: + - Primary optimization target (from naming + workspace goal) + - Dominant conversion event (from custom-conversion-metrics cross-referenced with campaign names) + - Testing cut threshold (from spend-threshold or workspace goal) + - Graduation threshold + +5. Write `/agent/brain/ad-naming/kpi-map.md`: + ```markdown + # Per-Campaign KPI Map + + | Campaign segment | Optimization target | Primary conversion | Testing cut | Graduation | + |---|---|---|---|---| + | TOF-broad | Lowest cost | App signup | <£100 / 2wk | ≥£500 | + | BOF-retarget | Cost cap | Calendly booked | <£50 / 1wk | ≥£300 | + ``` + +6. Update state. Note if any segments could not be determined. + +## Step 3 — Build the Motion query contract + +Auto-discover and write the account's Motion CLI contract: + +1. **Workspace ID**: from current workspace context. +2. **Attribution windows**: from `motion workspace-goal` → `conversionDetails[].attributionWindow.click` and `.view`. +3. **Conversion events**: from `motion meta custom-conversion-metrics` → each conversion's `id`, `name`, and derived metric keys (`{id}_count`, `{id}_cost`). +4. **Standard metrics**: check `motion meta metric-reference --query "appointments scheduled"` for any standard events that might apply. +5. **Thumbstop test**: run `motion meta insights --date-range last_30d --sort topSpend --limit 1 --table-kpi thumbstop_rate`. Note whether it returns a non-null value. +6. **Known data-layer gotchas** (encode as static rules): + - `campaignName` is null in `--grain ads` → use `--grain adnames` or insights for campaign reads + - `adType` may be null in ads grain → use `format` field instead + - `roas` varies by account configuration → check workspace goal for the actual judgment metric + - `last_365d` with `--include-transcript` times out → use 30-day windows + +7. Write `/agent/brain/ad-naming/query-contract.md`: + ```markdown + # Motion Query Contract + + ## Always include on every call + - Workspace ID: (use --workspace-id on commands that accept it) + - Click attribution: (--click-attribution-window) + - View attribution: (--view-attribution-window) + + ## Conversion events + | Event | Count key | Cost key | + |---|---|---| + | | _count | _cost | + + ## Thumbstop + Available: yes/no — use --table-kpi thumbstop_rate + + ## Data-layer gotchas + - ... + ``` + +8. Update state. Tell the user: "I've built the query contract — this is what gets used on every Motion pull for this account. Review and correct anything that looks wrong." + +## Step 4 — Update state and offer refresh routine + +1. Set `lastBuildDate` in state. Mirror. +2. Refresh `/agent/INDEX.md` with entries for all three files. +3. Offer the weekly refresh routine: + ``` + routine add \ + --name "Ad naming weekly refresh" \ + --cron "0 9 * * 1" \ + --delivery "Post a summary in a new web conversation." \ + --prompt "Run the ad-naming refresh task (task id: ). Wait for completion. Send a brief summary of what changed with conversation send --new." + ``` + +## Rules +- Naming decoder built from real Motion data → status `drafted`. +- If no clear pattern found in ad names: say that, show examples, ask the customer to describe the convention. Do not invent dimensions. +- Query contract auto-discovery may miss edge cases — always ask the customer to review before treating as canonical. +- When the creative corpus is installed, the naming decoder path (`/agent/brain/ad-naming/naming-decoder.md`) is what corpus files reference for decoding. diff --git a/packages/ad-naming/workflows/ad-naming-refresh.ts b/packages/ad-naming/workflows/ad-naming-refresh.ts new file mode 100644 index 00000000..5b126cbb --- /dev/null +++ b/packages/ad-naming/workflows/ad-naming-refresh.ts @@ -0,0 +1,224 @@ +import type { Workflow } from "@runneth/workflow/v1"; + +type AdNamingRefreshOutput = { + decoderChanged: boolean; + kpiMapChanged: boolean; + queryContractChanged: boolean; + highlights: string[]; + lastRefreshed: string; +}; + +/** + * Ad Naming Refresh Workflow + * + * Refreshes all three ad-naming brain files from live Motion data: + * - naming-decoder.md (from fresh ad names sample) + * - kpi-map.md (from naming decoder + workspace goal + custom conversions) + * - query-contract.md (from workspace goal + custom conversions + thumbstop test) + * + * Motion CLI pattern for bash tasks: + * ENVELOPE=$(motion ...) + * FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + * jq '...' "$FILE" + * + * Agent tasks write results to /tmp/an-results/*.json. + * Bash tasks read those files. No stdout-parsing of agent responses. + */ +export const wf: Workflow, AdNamingRefreshOutput> = async ({ + task, +}) => { + // ─── Step 1: Pull fresh data via bash ──────────────────────────────────── + + await task.bash({ + key: "pull-adnames-sample", + script: ` + set -euo pipefail + mkdir -p /tmp/an-refresh /tmp/an-results + + ENVELOPE=$(motion meta ads --grain adnames --date-range last_90d --sort-by spend --sort-direction desc --limit 200) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + cp "$FILE" /tmp/an-refresh/adnames.json + + echo "adnames-done: $(jq '.data.summaryRows | length' /tmp/an-refresh/adnames.json) rows" + `, + }); + + await task.bash({ + key: "pull-workspace-context", + script: ` + set -euo pipefail + + ENVELOPE=$(motion workspace-goal) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + cp "$FILE" /tmp/an-refresh/workspace-goal.json + + ENVELOPE=$(motion meta custom-conversion-metrics) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + cp "$FILE" /tmp/an-refresh/custom-conversions.json + + echo "workspace-context-done" + `, + }); + + await task.bash({ + key: "pull-campaign-sample", + script: ` + set -euo pipefail + + ENVELOPE=$(motion meta insights --date-range last_30d --sort topSpend --limit 100) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + # Extract just campaign names for KPI map refresh + jq '[.creatives[] | .campaignName] | unique | map(select(. != null))' "$FILE" > /tmp/an-refresh/campaign-names.json + + echo "campaign-names-done: $(jq length /tmp/an-refresh/campaign-names.json) unique names" + `, + }); + + await task.bash({ + key: "test-thumbstop", + script: ` + set -euo pipefail + + ENVELOPE=$(motion meta insights --date-range last_30d --sort topSpend --limit 1 --table-kpi thumbstop_rate) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + + # Check if thumbstop_rate returns a non-null value + THUMBSTOP=$(jq -r '.creatives[0].tableKpiMetrics.thumbstop_rate.value // "null"' "$FILE" 2>/dev/null || echo "null") + echo "$THUMBSTOP" > /tmp/an-refresh/thumbstop-available.txt + + echo "thumbstop-test-done: $THUMBSTOP" + `, + }); + + // ─── Step 2: Agent refreshes each file ─────────────────────────────────── + + await task.agent({ + key: "refresh-naming-decoder", + prompt: ` + Refresh /agent/brain/ad-naming/naming-decoder.md from a fresh ad names sample. + + Source: /tmp/an-refresh/adnames.json + Read ad names from .data.summaryRows[].adName or .data.result.adnames[].adName. + + If naming-decoder.md already exists: + - Check for new dimensions or values not in the current decoder. + - If new: add them, note what changed. + - If no change: write {"changed": false, "preview": "No new naming dimensions detected"} to /tmp/an-results/naming-decoder.json and stop. + + If naming-decoder.md does not exist: + - Parse repeating delimited patterns from ad names. + - Write a full decoder: system name, dimension table (prefix, full name, observed values with plain meanings), decode template, worked example. + - Status: "drafted" + + Write back to /agent/brain/ad-naming/naming-decoder.md. + Write {"changed": true/false, "preview": "short description of what changed"} to /tmp/an-results/naming-decoder.json. + `, + }); + + await task.agent({ + key: "refresh-kpi-map", + prompt: ` + Refresh /agent/brain/ad-naming/kpi-map.md. + + Sources: + - /agent/brain/ad-naming/naming-decoder.md (campaign dimension if present) + - /tmp/an-refresh/workspace-goal.json (primary KPI, conversion event, attribution windows) + - /tmp/an-refresh/custom-conversions.json (conversion event IDs and names, at .data.customConversions[]) + - /tmp/an-refresh/campaign-names.json (unique campaign names from last 30 days) + + Build or update the per-campaign KPI map table. + For each campaign segment (derived from naming decoder campaign dimension, or campaign name patterns): + - Optimization target (from naming + workspace goal) + - Primary conversion event (from custom-conversions matched to campaign purpose) + - Testing cut and graduation thresholds (from workspace goal or standard rules) + + If no clear segments: write a single row for the primary optimization target. + + Write back to /agent/brain/ad-naming/kpi-map.md. + Write {"changed": true/false, "preview": "..."} to /tmp/an-results/kpi-map.json. + `, + }); + + await task.agent({ + key: "refresh-query-contract", + prompt: ` + Refresh /agent/brain/ad-naming/query-contract.md. + + Sources: + - /tmp/an-refresh/workspace-goal.json (attribution windows at .data.conversionDetails[].attributionWindow) + - /tmp/an-refresh/custom-conversions.json (conversion events at .data.customConversions[]) + - /tmp/an-refresh/thumbstop-available.txt (contains "null" or a numeric value) + + Update the contract sections: + 1. Attribution windows: read click and view values from workspace-goal. + 2. Conversion events: list each conversion's name, id, _count key, _cost key. + 3. Thumbstop: read thumbstop-available.txt — if not "null", thumbstop_rate is available; otherwise note it returns null. + 4. Data-layer gotchas: keep existing static rules, do not remove them. + + If query-contract.md does not exist, create it from scratch with all sections. + + Write back to /agent/brain/ad-naming/query-contract.md. + Write {"changed": true/false, "preview": "..."} to /tmp/an-results/query-contract.json. + `, + }); + + // ─── Step 3: Collect results + update state ─────────────────────────────── + + const collectResult = await task.bash({ + key: "collect-and-update-state", + script: ` + set -euo pipefail + TODAY=$(node -e "console.log(new Date().toISOString().slice(0,10))") + + node -e " + const fs = require('fs'); + + function readResult(item) { + try { return JSON.parse(fs.readFileSync('/tmp/an-results/' + item + '.json', 'utf8')); } + catch { return { changed: false, preview: 'result file not found' }; } + } + + const decoder = readResult('naming-decoder'); + const kpiMap = readResult('kpi-map'); + const queryContract = readResult('query-contract'); + + const highlights = []; + if (decoder.changed && decoder.preview) highlights.push('naming-decoder: ' + decoder.preview); + if (kpiMap.changed && kpiMap.preview) highlights.push('kpi-map: ' + kpiMap.preview); + if (queryContract.changed && queryContract.preview) highlights.push('query-contract: ' + queryContract.preview); + + // Update state + let state = {}; + try { state = JSON.parse(fs.readFileSync('/agent/brain/ad-naming/ad-naming-state.json', 'utf8')); } catch {} + state.lastRefreshed = '$TODAY'; + if (!state.lastBuildDate) state.lastBuildDate = '$TODAY'; + fs.writeFileSync('/agent/brain/ad-naming/ad-naming-state.json', JSON.stringify(state, null, 2)); + + const output = { + decoderChanged: decoder.changed, + kpiMapChanged: kpiMap.changed, + queryContractChanged: queryContract.changed, + highlights, + lastRefreshed: '$TODAY', + }; + console.log(JSON.stringify(output)); + " + `, + }); + + let output: AdNamingRefreshOutput = { + decoderChanged: false, + kpiMapChanged: false, + queryContractChanged: false, + highlights: [], + lastRefreshed: new Date().toISOString().slice(0, 10), + }; + + try { + output = JSON.parse(collectResult.stdout.trim()); + } catch { + // Return partial result + } + + return output; +}; From 0381ea89e0e53d9dce1974ec8ba6873e760dc6fe Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 20 Jul 2026 10:43:57 +0000 Subject: [PATCH 15/25] fix(ad-naming): align brain files with manifest Co-authored-by: vamsi --- packages/ad-naming/brain/{ => ad-naming}/ad-naming-state.json | 0 packages/ad-naming/brain/{ => ad-naming}/kpi-map.md | 0 packages/ad-naming/brain/{ => ad-naming}/naming-decoder.md | 0 packages/ad-naming/brain/{ => ad-naming}/query-contract.md | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename packages/ad-naming/brain/{ => ad-naming}/ad-naming-state.json (100%) rename packages/ad-naming/brain/{ => ad-naming}/kpi-map.md (100%) rename packages/ad-naming/brain/{ => ad-naming}/naming-decoder.md (100%) rename packages/ad-naming/brain/{ => ad-naming}/query-contract.md (100%) diff --git a/packages/ad-naming/brain/ad-naming-state.json b/packages/ad-naming/brain/ad-naming/ad-naming-state.json similarity index 100% rename from packages/ad-naming/brain/ad-naming-state.json rename to packages/ad-naming/brain/ad-naming/ad-naming-state.json diff --git a/packages/ad-naming/brain/kpi-map.md b/packages/ad-naming/brain/ad-naming/kpi-map.md similarity index 100% rename from packages/ad-naming/brain/kpi-map.md rename to packages/ad-naming/brain/ad-naming/kpi-map.md diff --git a/packages/ad-naming/brain/naming-decoder.md b/packages/ad-naming/brain/ad-naming/naming-decoder.md similarity index 100% rename from packages/ad-naming/brain/naming-decoder.md rename to packages/ad-naming/brain/ad-naming/naming-decoder.md diff --git a/packages/ad-naming/brain/query-contract.md b/packages/ad-naming/brain/ad-naming/query-contract.md similarity index 100% rename from packages/ad-naming/brain/query-contract.md rename to packages/ad-naming/brain/ad-naming/query-contract.md From eb64e062848e21e5ea3bb569d820e508279641eb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 20 Jul 2026 10:45:34 +0000 Subject: [PATCH 16/25] =?UTF-8?q?feat(package):=20add=20creative-corpus=20?= =?UTF-8?q?=E2=80=94=20durable=20per-creative=20brain=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: vamsi --- package-index.json | 19 +- packages/creative-corpus/README.md | 40 ++ .../brain/meta/corpus-state.json | 15 + .../brain/meta/creatives/PLAYBOOK.md | 98 +++++ .../creative-corpus/instructions/behavior.md | 73 ++++ packages/creative-corpus/package.json | 52 +++ packages/creative-corpus/runneth-package.json | 51 +++ .../scripts/corpus-refresh.mjs | 235 ++++++++++++ packages/creative-corpus/skills/SKILL.md | 63 +++ .../creative-corpus/workflows/corpus-build.ts | 362 ++++++++++++++++++ 10 files changed, 1007 insertions(+), 1 deletion(-) create mode 100644 packages/creative-corpus/README.md create mode 100644 packages/creative-corpus/brain/meta/corpus-state.json create mode 100644 packages/creative-corpus/brain/meta/creatives/PLAYBOOK.md create mode 100644 packages/creative-corpus/instructions/behavior.md create mode 100644 packages/creative-corpus/package.json create mode 100644 packages/creative-corpus/runneth-package.json create mode 100644 packages/creative-corpus/scripts/corpus-refresh.mjs create mode 100644 packages/creative-corpus/skills/SKILL.md create mode 100644 packages/creative-corpus/workflows/corpus-build.ts diff --git a/package-index.json b/package-index.json index 6c470f09..ee8b7569 100644 --- a/package-index.json +++ b/package-index.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "indexRevision": "ad-naming-0.1.0", + "indexRevision": "three-package-split-0.1.0", "packages": [ { "id": "context-kit", @@ -35,6 +35,23 @@ }, "updatePolicy": "manual", "uninstallPolicy": "allowed" + }, + { + "id": "creative-corpus", + "name": "Creative Corpus", + "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls summaries, transcripts, glossary tags, and decoded naming for every creative into local Markdown files — indexed by creative ID. Enables whole-account historical analysis, VoC mining, naming-convention decoding, and brief generation without re-fetching from Motion on every question. Companion to Context Kit.", + "version": "0.1.0", + "categories": ["creative-strategy"], + "packageManagerVersion": 1, + "source": { + "type": "github", + "owner": "Motion-Creative", + "repo": "runneth-apps", + "ref": "main", + "path": "packages/creative-corpus" + }, + "updatePolicy": "manual", + "uninstallPolicy": "allowed" } ] } diff --git a/packages/creative-corpus/README.md b/packages/creative-corpus/README.md new file mode 100644 index 00000000..393d513c --- /dev/null +++ b/packages/creative-corpus/README.md @@ -0,0 +1,40 @@ +# Creative Corpus + +Builds the institutional creative memory that makes every Runneth answer sharper. Seeds a per-creative Markdown library by pulling Motion summaries, transcripts, and glossary tags for every creative in your Meta account — then keeps it fresh with a daily refresh script. + +Companion package to Context Kit and Ad Naming. + +## What it builds + +- `{adname}__{id8}.md` per creative under `/agent/brain/meta/creatives/` +- Hook, full transcript, value props, glossary tags, decoded naming per file +- A Knoweth lane for auto-injection of relevant corpus files into context +- A daily script-mode refresh routine that adds new launches and updates spend state + +## What it does NOT do + +- Store live performance metrics (spend, ROAS, CPA stay in Motion) +- Replace Motion for current performance analysis +- Re-pull transcripts once indexed (they don't change) + +## Install order recommendation + +1. Context Kit +2. Ad Naming (build the naming decoder — the corpus uses it for ad name decoding) +3. Creative Corpus + +## App build gotchas + +- The corpus has no board app of its own. State is visible in the Context Kit board under a companion section if Context Kit is installed. +- Package sync stages files but does NOT run the build workflow. Run `build my corpus` after install. + +## Architecture notes + +- Build: workflow (`corpus-build.ts`) registered by the skill on first run +- Refresh: script-mode routine (`corpus-refresh.mjs`) registered after build +- State: `/agent/brain/meta/corpus-state.json` +- Playbook: `/agent/brain/meta/creatives/PLAYBOOK.md` + +## Content vs. metrics rule + +The corpus owns durable creative content. Live metrics always come from a fresh Motion pull. The only performance signal stored in corpus files is a coarse `spendState` (scaling / active / paused) refreshed daily by the script. diff --git a/packages/creative-corpus/brain/meta/corpus-state.json b/packages/creative-corpus/brain/meta/corpus-state.json new file mode 100644 index 00000000..5ac4316c --- /dev/null +++ b/packages/creative-corpus/brain/meta/corpus-state.json @@ -0,0 +1,15 @@ +{ + "schemaVersion": 1, + "id": "creative-corpus", + "name": "Creative Corpus", + "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls summaries, transcripts, glossary tags, and decoded naming for every creative into local Markdown files — indexed by creative ID. Enables whole-account historical analysis, VoC mining, naming-convention decoding, and brief generation without re-fetching from Motion on every question. Companion to Context Kit.", + "version": "0.1.0", + "totalCreatives": 0, + "lastBuildDate": null, + "lastRefreshDate": null, + "corpusLaneId": null, + "buildWorkflowId": null, + "buildTaskId": null, + "refreshRoutineId": null, + "lanesRegistered": false +} diff --git a/packages/creative-corpus/brain/meta/creatives/PLAYBOOK.md b/packages/creative-corpus/brain/meta/creatives/PLAYBOOK.md new file mode 100644 index 00000000..c0db876e --- /dev/null +++ b/packages/creative-corpus/brain/meta/creatives/PLAYBOOK.md @@ -0,0 +1,98 @@ +# Creative Corpus Playbook + +This file is the maintenance reference for the creative corpus. Keep it updated after every build and significant change. + +## What this corpus is + +A durable per-creative Markdown library. Every creative file contains durable content (hook, transcript, value props, glossary tags, decoded naming) that is fetched once and kept fresh. Live performance metrics stay in Motion and are NOT stored here — except a coarse `spendState` refreshed daily. + +## File naming convention + +`{sanitized-adname}__{id8}.md` + +- `sanitized-adname`: ad name with non-alphanumeric chars replaced by `-`, max 80 chars +- `id8`: first 8 characters of the Motion creative asset ID + +Example: `p-ugc_fs-30_as-fear__a3b4c5d6.md` + +**The `id8` is the stable key.** Even if an ad name changes, the file can be found by ID. Never rename a file after creation. + +## File schema + +```yaml +--- +id: +adName: +id8: +format: +launchDate: +campaignName: +status: +spendState: # refreshed daily by script +indexedAt: # when this file was first created +--- + +# {ad name} + +## Hook +{Opening line or first sentence of transcript. Lifted verbatim from transcript or Motion summary.} + +## Transcript +{Full transcript verbatim. "No transcript available." if Motion did not return one.} + +## Summary +{Motion's creative summary. "No summary available." if absent.} + +## Value Props +{2-5 bullet points: claims or benefits this ad makes. Lifted from summary or transcript.} + +## Glossary Tags +{One line per tag: "category: value"} + +## Decoded Ad Name +{Decoded dimensions using the naming decoder, or a note that Ad Naming is not installed.} +``` + +## Content vs. metrics rule + +| Store in corpus | Keep as live Motion pull | +|---|---| +| Hook, transcript, summary | Spend (last N days) | +| Glossary tags | ROAS, CPA, CTR | +| Decoded ad name | Thumbstop, hold rate | +| Value props | Conversion counts | +| Format, launch date | Exact impressions | +| Campaign name at launch | Current budget | +| spendState (coarse, daily refresh) | | + +## Build procedure + +Runs as a workflow (`corpus-build.ts`) registered by the creative-corpus skill. + +1. Pull roster over 3 x 30-day windows (separate calls — 90-day + summaries times out). +2. Deduplicate by creative asset ID. +3. Skip already-indexed files (create-if-absent, unless `forceRebuild: true`). +4. Enrich in batches of ≤15 IDs: `motion meta insights --scope creative-asset-id --include-transcript --include-glossary --date-range last_365d`. +5. Write one MD file per creative. +6. Update `corpus-state.json` with final counts. + +## Refresh procedure + +Runs as a script-mode routine (`corpus-refresh.mjs`) on a daily `0 5 * * *` cron. + +1. Pull `motion meta insights --date-range last_7d`. +2. New IDs → enrich and write (same batch procedure as build). +3. Existing IDs → update `spendState` and `status` in-place only. Do NOT re-pull transcripts. +4. Update `corpus-state.json`. + +## Troubleshooting + +**Timeout on enrichment batch:** Reduce batch size. If ≤15 still times out, try `--date-range last_30d` instead of `last_365d` for the enrichment call (creative must have run recently to appear). + +**Transcript missing:** Some creative types (DPA, catalog ads) do not have transcripts. `"No transcript available."` is correct. + +**Stale spendState:** Check that the daily routine is active with `routine list`. If paused, resume it. + +**File count mismatch vs Motion account:** The corpus covers the 90-day build window + daily incremental. Creatives older than 90 days at build time are not in the corpus unless manually added. To add an older creative: `motion meta insights --scope creative-asset-id --creative-asset-id --include-transcript --include-glossary --date-range last_365d` and write the file manually. + +## Build log diff --git a/packages/creative-corpus/instructions/behavior.md b/packages/creative-corpus/instructions/behavior.md new file mode 100644 index 00000000..88f3a615 --- /dev/null +++ b/packages/creative-corpus/instructions/behavior.md @@ -0,0 +1,73 @@ +# Creative Corpus package instructions + +This package builds and maintains a durable per-creative library for the connected Meta account. Every creative the account has ever run gets its own Markdown file in `/agent/brain/meta/creatives/`, indexed by creative asset ID. + +## Install order + +1. Context Kit (brand knowledge foundation) +2. Ad Naming (naming decoder at `/agent/brain/ad-naming/naming-decoder.md` — corpus uses it for decoded naming in corpus files) +3. Creative Corpus (this package) + +Creative Corpus works without ad-naming, but corpus files will have a placeholder in the "Decoded Ad Name" section until the naming decoder is installed. + +## Knoweth lane + +| Lane ID | Path | Kind | +|---|---|---| +| `creative-corpus` | `/agent/brain/meta/creatives/` | `directory` | + +The skill registers this lane on first run. Individual creative files are surfaced by Knoweth based on creative IDs or ad names mentioned in the conversation. + +## Content vs. metrics rule (critical) + +| What lives in the corpus | What stays a live Motion pull | +|---|---| +| Hook / opening line | Spend (last N days) | +| Full transcript | ROAS, CPA, CTR | +| Glossary tags (AI-derived) | Thumbstop, hold rate | +| Decoded ad name (from ad-naming) | Current status | +| Summary / value props | Recent impressions | +| Format, launch date | Conversion counts | +| `spendState` (coarse, daily refresh) | | + +## Naming decoder path + +The corpus checks for the naming decoder at `/agent/brain/ad-naming/naming-decoder.md` (the ad-naming package path). If ad-naming is not installed, corpus files carry: `"Install the ad-naming package to enable ad name decoding."` in the Decoded Ad Name section. + +## File naming convention + +`{sanitized-adname}__{id8}.md` +- `sanitized-adname`: ad name with non-alphanumeric chars → `-`, max 80 chars +- `id8`: first 8 characters of the Motion creative asset ID + +The `id8` is the stable key. Never rename a file after creation. + +## When to use the corpus + +Use corpus files when: +- The question is about a specific creative by name or ID +- Analyzing patterns across the full account (hook tactics, messaging angles, format mix) +- Building briefs that reference past hooks or transcripts +- VoC mining beyond the current `last_30d` pull +- Looking up paused or historical creatives + +Use live Motion pulls for current performance numbers. + +## Refresh behavior + +The daily refresh script (`corpus-refresh.mjs`) runs as a script-mode routine: +1. Pulls `last_7d` for new creative IDs not yet in the corpus. +2. New IDs: fetches enrichment in batches of ≤15, writes new corpus files. +3. Existing IDs in the `last_7d` pull: updates `spendState` and `status` in-place. +4. Does NOT re-pull transcripts or summaries for already-indexed creatives. + +## State file + +`/agent/brain/meta/corpus-state.json` tracks: `totalCreatives`, `lastBuildDate`, `lastRefreshDate`, `corpusLaneId`, `buildWorkflowId`, `buildTaskId`, `refreshRoutineId`. + +## Rules + +- Never re-pull transcripts/summaries for already-indexed files. +- Always batch enrichment at ≤15 IDs per `motion meta insights --scope creative-asset-id` call. +- Use `--date-range last_365d` on ID-scoped calls. +- Write is create-if-absent; refresh is update-in-place via `id8` key. diff --git a/packages/creative-corpus/package.json b/packages/creative-corpus/package.json new file mode 100644 index 00000000..9314a223 --- /dev/null +++ b/packages/creative-corpus/package.json @@ -0,0 +1,52 @@ +{ + "schemaVersion": 1, + "id": "creative-corpus", + "name": "Creative Corpus", + "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls summaries, transcripts, glossary tags, and decoded naming for every creative into local Markdown files — indexed by creative ID. Enables whole-account historical analysis, VoC mining, naming-convention decoding, and brief generation without re-fetching from Motion on every question. Companion to Context Kit.", + "version": "0.1.0", + "installPolicy": "manual", + "updatePolicy": "manual", + "uninstallPolicy": "allowed", + "resources": [ + { + "id": "creative-corpus-behavior", + "type": "package_instruction", + "sourcePath": "instructions/behavior.md" + }, + { + "id": "creative-corpus-state", + "type": "file", + "sourcePath": "brain/meta/corpus-state.json", + "target": { "root": "agent_brain", "path": "meta/corpus-state.json" }, + "executable": false + }, + { + "id": "creative-corpus-playbook", + "type": "file", + "sourcePath": "brain/meta/creatives/PLAYBOOK.md", + "target": { "root": "agent_brain", "path": "meta/creatives/PLAYBOOK.md" }, + "executable": false + }, + { + "id": "creative-corpus-build-workflow", + "type": "file", + "sourcePath": "workflows/corpus-build.ts", + "target": { "root": "agent_brain", "path": "meta/corpus-build.ts" }, + "executable": false + }, + { + "id": "creative-corpus-refresh-script", + "type": "file", + "sourcePath": "scripts/corpus-refresh.mjs", + "target": { "root": "agent_brain", "path": "meta/corpus-refresh.mjs" }, + "executable": true + }, + { + "id": "creative-corpus-skill", + "type": "directory", + "sourcePath": "skills", + "target": { "root": "agent_skills", "path": "creative-corpus" }, + "executablePaths": [] + } + ] +} diff --git a/packages/creative-corpus/runneth-package.json b/packages/creative-corpus/runneth-package.json new file mode 100644 index 00000000..34a8b07b --- /dev/null +++ b/packages/creative-corpus/runneth-package.json @@ -0,0 +1,51 @@ +{ + "schemaVersion": 1, + "id": "creative-corpus", + "name": "Creative Corpus", + "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls summaries, transcripts, glossary tags, and decoded naming for every creative into local Markdown files — indexed by creative ID. Enables whole-account historical analysis, VoC mining, naming-convention decoding, and brief generation without re-fetching from Motion on every question. Companion to Context Kit.", + "version": "0.1.0", + "updatePolicy": "manual", + "uninstallPolicy": "allowed", + "resources": [ + { + "id": "creative-corpus-behavior", + "type": "package_instruction", + "sourcePath": "instructions/behavior.md" + }, + { + "id": "creative-corpus-state", + "type": "file", + "sourcePath": "brain/meta/corpus-state.json", + "target": { "root": "agent_brain", "path": "meta/corpus-state.json" }, + "executable": false + }, + { + "id": "creative-corpus-playbook", + "type": "file", + "sourcePath": "brain/meta/creatives/PLAYBOOK.md", + "target": { "root": "agent_brain", "path": "meta/creatives/PLAYBOOK.md" }, + "executable": false + }, + { + "id": "creative-corpus-build-workflow", + "type": "file", + "sourcePath": "workflows/corpus-build.ts", + "target": { "root": "agent_brain", "path": "meta/corpus-build.ts" }, + "executable": false + }, + { + "id": "creative-corpus-refresh-script", + "type": "file", + "sourcePath": "scripts/corpus-refresh.mjs", + "target": { "root": "agent_brain", "path": "meta/corpus-refresh.mjs" }, + "executable": true + }, + { + "id": "creative-corpus-skill", + "type": "directory", + "sourcePath": "skills", + "target": { "root": "agent_skills", "path": "creative-corpus" }, + "executablePaths": [] + } + ] +} diff --git a/packages/creative-corpus/scripts/corpus-refresh.mjs b/packages/creative-corpus/scripts/corpus-refresh.mjs new file mode 100644 index 00000000..11ecd85d --- /dev/null +++ b/packages/creative-corpus/scripts/corpus-refresh.mjs @@ -0,0 +1,235 @@ +#!/usr/bin/env node +/** + * Creative Corpus Daily Refresh Script + * + * Script-mode routine: runs without an agent turn. + * Exit 0 = success. Exit non-zero = failure (triggers agent fallback run). + * + * Motion CLI pattern: + * execFileSync returns the envelope JSON to stdout. + * Parse .file from the envelope, then read from that path. + * Never redirect the motion command itself. + * + * What it does: + * 1. Pulls motion meta insights --date-range last_7d to find new/changed creatives. + * 2. New IDs: fetch enrichment in batches of ≤15, write new corpus files. + * 3. Existing IDs: update spendState + status in-place (no transcript re-pull). + * 4. Updates corpus-state.json with new counts and lastRefreshDate. + */ + +import { execFileSync } from "node:child_process"; +import { existsSync, readFileSync, writeFileSync, readdirSync } from "node:fs"; +import { join } from "node:path"; + +const CORPUS_DIR = "/agent/brain/meta/creatives"; +const STATE_FILE = "/agent/brain/meta/corpus-state.json"; +const BATCH_SIZE = 15; +const ROUTINE_TRIGGER = process.env.ROUTINE_TRIGGER ?? ""; + +// ─── Test gate ─────────────────────────────────────────────────────────────── +if (ROUTINE_TRIGGER === "test") { + process.stderr.write("[test] corpus-refresh: would pull last_7d and update corpus\n"); + process.stderr.write("[test] corpus-refresh: skipping real Motion calls\n"); + process.exit(0); +} + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +function motionCall(args) { + const envelopeStr = execFileSync("motion", args, { encoding: "utf8" }); + const envelope = JSON.parse(envelopeStr); + if (!envelope.successful) { + throw new Error("motion " + args[0] + " failed: " + (envelope.message || "unknown error")); + } + return envelope.file; +} + +function readJson(filePath) { + return JSON.parse(readFileSync(filePath, "utf8")); +} + +function corpusFilePath(adName, id) { + const slug = (adName || "unknown").replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 80); + const id8 = id.slice(0, 8); + return join(CORPUS_DIR, `${slug}__${id8}.md`); +} + +function updateSpendStateInFile(filePath, spendState, status) { + if (!existsSync(filePath)) return; + let content = readFileSync(filePath, "utf8"); + content = content.replace(/^spendState: .+$/m, `spendState: ${spendState}`); + content = content.replace(/^status: .+$/m, `status: ${status}`); + writeFileSync(filePath, content); +} + +function readState() { + try { return readJson(STATE_FILE); } catch { return {}; } +} + +function writeState(state) { + writeFileSync(STATE_FILE, JSON.stringify(state, null, 2)); +} + +const today = new Date().toISOString().slice(0, 10); +const errors = []; +let newFilesWritten = 0; +let existingFilesUpdated = 0; + +try { + // Step 1: Pull last_7d roster + process.stderr.write("[corpus-refresh] Pulling last_7d roster...\n"); + + const rosterFile = motionCall([ + "meta", "insights", + "--date-range", "last_7d", + "--sort", "topSpend", + "--include-metrics", + "--limit", "500", + ]); + + const rosterData = readJson(rosterFile); + const creatives = (rosterData.creatives || []).map(c => ({ + id: c.id, + adName: c.adName || "unknown", + format: c.format || "unknown", + status: c.status || "unknown", + spend: c.metrics?.spend || 0, + })).filter(c => c.id); + + process.stderr.write(`[corpus-refresh] Found ${creatives.length} creatives in last_7d\n`); + + // Step 2: Split new vs existing + const newCreatives = []; + const existingCreatives = []; + + for (const c of creatives) { + if (existsSync(corpusFilePath(c.adName, c.id))) { + existingCreatives.push(c); + } else { + newCreatives.push(c); + } + } + + process.stderr.write(`[corpus-refresh] New: ${newCreatives.length}, Existing: ${existingCreatives.length}\n`); + + // Step 3: Update spendState on existing files (no Motion calls needed) + for (const c of existingCreatives) { + const spendState = c.spend > 1000 ? "scaling" : c.spend > 0 ? "active" : "paused"; + updateSpendStateInFile(corpusFilePath(c.adName, c.id), spendState, c.status); + existingFilesUpdated++; + } + + // Step 4: Fetch enrichment for new creatives in batches of ≤15 + const decoderExists = existsSync("/agent/brain/ad-naming/naming-decoder.md"); + + for (let i = 0; i < newCreatives.length; i += BATCH_SIZE) { + const batch = newCreatives.slice(i, i + BATCH_SIZE); + const batchNum = Math.floor(i / BATCH_SIZE) + 1; + + process.stderr.write(`[corpus-refresh] Enriching batch ${batchNum} (${batch.length} IDs)...\n`); + + try { + const idFlags = batch.flatMap(c => ["--creative-asset-id", c.id]); + + const enrichFile = motionCall([ + "meta", "insights", + "--scope", "creative-asset-id", + ...idFlags, + "--include-transcript", + "--include-glossary", + "--date-range", "last_365d", + "--include-metrics", + ]); + + const enrichData = readJson(enrichFile); + const creativeMap = {}; + for (const c of (enrichData.creatives || [])) { + if (c.id) creativeMap[c.id] = c; + } + + for (const meta of batch) { + try { + const creative = creativeMap[meta.id] || {}; + const id8 = meta.id.slice(0, 8); + const spendState = meta.spend > 1000 ? "scaling" : meta.spend > 0 ? "active" : "paused"; + + const tags = (creative.glossaryTags || []) + .map(t => `- ${t.categoryId}: ${t.tagName}`) + .join("\n") || "No tags returned."; + + const transcript = creative.transcript || "No transcript available."; + const summary = creative.summary || "No summary available."; + const hook = transcript !== "No transcript available." + ? (transcript.split(/[.!?]/)[0]?.trim() || "No hook extracted.") + : (summary !== "No summary available." ? summary.slice(0, 150) : "No hook available."); + + const decodedName = decoderExists + ? `See /agent/brain/ad-naming/naming-decoder.md for dimension definitions. Raw: ${meta.adName}` + : "Install Ad Naming to enable ad name decoding."; + + const md = [ + "---", + `id: ${meta.id}`, + `adName: ${meta.adName}`, + `id8: ${id8}`, + `format: ${meta.format}`, + `launchDate: ${creative.launchDate || "unknown"}`, + `campaignName: ${creative.campaignName || "unknown"}`, + `status: ${meta.status}`, + `spendState: ${spendState}`, + `indexedAt: ${today}`, + "---", + "", + `# ${meta.adName}`, + "", + "## Hook", + hook, + "", + "## Transcript", + transcript, + "", + "## Summary", + summary, + "", + "## Glossary Tags", + tags, + "", + "## Decoded Ad Name", + decodedName, + ].join("\n"); + + writeFileSync(corpusFilePath(meta.adName, meta.id), md); + newFilesWritten++; + } catch (creativeErr) { + errors.push(`Failed to write ${meta.id}: ${creativeErr.message}`); + } + } + } catch (batchErr) { + errors.push(`Batch ${batchNum} failed: ${batchErr.message}`); + } + } + + // Step 5: Update corpus state + const totalFiles = readdirSync(CORPUS_DIR) + .filter(f => f.endsWith(".md") && f !== "PLAYBOOK.md").length; + + const state = readState(); + state.totalCreatives = totalFiles; + state.lastRefreshDate = today; + writeState(state); + + process.stderr.write( + `[corpus-refresh] Done. New: ${newFilesWritten}, Updated: ${existingFilesUpdated}, Total: ${totalFiles}\n` + ); + + if (errors.length > 0) { + process.stderr.write(`[corpus-refresh] ${errors.length} errors:\n${errors.join("\n")}\n`); + process.exit(1); + } + + process.exit(0); + +} catch (topErr) { + process.stderr.write(`[corpus-refresh] Fatal: ${topErr.message}\n`); + process.exit(1); +} diff --git a/packages/creative-corpus/skills/SKILL.md b/packages/creative-corpus/skills/SKILL.md new file mode 100644 index 00000000..e6411d2a --- /dev/null +++ b/packages/creative-corpus/skills/SKILL.md @@ -0,0 +1,63 @@ +--- +name: creative-corpus +description: Builds and maintains the durable per-creative brain library for this Meta account. Triggers on "build my corpus", "index my creatives", "build creative library", "set up creative corpus", "index the account", "build the brain library". +--- + +# Creative Corpus skill + +Build the corpus once, keep it fresh with a daily script routine. After build, every analysis reads local files instead of re-hitting Motion. + +## Step 0 — Check state + +Read `/agent/brain/meta/corpus-state.json`. If `lastBuildDate` exists, the corpus is already built — go to refresh mode (offer to run the refresh task or check the routine is set up). + +If `totalCreatives` is 0 or absent, this is a first build. Proceed with the full build workflow. + +## Step 1 — Register the Knoweth lane (first run only) + +If `corpusLaneId` is absent from state: +1. Call ContextConfig with action `update` to register the `creative-corpus` lane pointing at `/agent/brain/meta/creatives/` as a directory lane with `read: true`. +2. Save the returned lane ID (or a generated stable ID) to state as `corpusLaneId`. + +## Step 2 — Register and run the build workflow (first run only) + +1. Read the workflow source at `/agent/brain/meta/corpus-build.ts`. +2. Run `workflow push /agent/brain/meta/corpus-build.ts --name creative-corpus-build`. +3. Save the returned workflow ID to `corpus-state.json` as `buildWorkflowId`. +4. Create the task: `task add --kind workflow --workflow-id --name "Creative Corpus Build"`. Save as `buildTaskId`. +5. Run the task: `task run --id `. Tell the customer: "I'm building your creative corpus — this pulls summaries, transcripts, and tags for every creative in your account. It runs in the background and will take a few minutes depending on your library size. I'll let you know when it's done." +6. `task wait --run ` with a generous timeout. When complete, read the output and report: "Built [n] creative files. Your creative brain is ready." + +## Step 3 — Set up the daily refresh routine (after build completes) + +1. Register the refresh script as a script-mode routine: +``` +routine add \ + --name "Creative corpus daily refresh" \ + --cron "0 5 * * *" \ + --delivery "Update corpus state only — no conversation needed unless errors occur." \ + --prompt "Run the corpus refresh script. If the script fails, report the error in a new web conversation." \ + --script /agent/brain/meta/corpus-refresh.mjs +``` +2. Save the routine ID to `corpus-state.json` as `refreshRoutineId`. +3. Tell the customer: "I've set up a daily refresh that runs at 5am. New creative launches will appear in the corpus the next morning." + +## Step 4 — Using the corpus + +After build, the corpus lane is active. When the customer asks questions that benefit from corpus depth: + +- **"What hooks have we ever run about fear?"** → `rg -l "fear" /agent/brain/meta/creatives/` then read matching files. +- **"Show me every UGC creative from last year"** → filter corpus files by `format: ugc` and `launchDate`. +- **"What did this ad say?"** → find by ad name or ID, read `transcript` field. +- **"What customer language do we use around [topic]?"** → grep transcripts across the full corpus. +- **"Build me briefs based on our best hooks"** → read top-spend corpus files, extract hook lines, generate briefs. + +Always say: "I'm reading this from your creative corpus — [n] creatives indexed locally." Do not re-pull from Motion for content questions when the corpus file exists. + +## Rules + +- Do not re-pull transcripts or summaries for already-indexed creatives. They are durable content. +- Always batch ≤15 IDs per enrichment call. +- The corpus does not replace live Motion pulls for current performance numbers. +- If a creative ID is mentioned that is not in the corpus, offer to fetch and add it: `motion meta insights --scope creative-asset-id --creative-asset-id --include-transcript --include-glossary --date-range last_365d`. +- Keep the PLAYBOOK.md updated when the build finishes. It is the maintenance reference. diff --git a/packages/creative-corpus/workflows/corpus-build.ts b/packages/creative-corpus/workflows/corpus-build.ts new file mode 100644 index 00000000..42440faa --- /dev/null +++ b/packages/creative-corpus/workflows/corpus-build.ts @@ -0,0 +1,362 @@ +import type { Workflow } from "@runneth/workflow/v1"; + +type CorpusBuildOutput = { + totalBuilt: number; + totalSkipped: number; + totalFailed: number; + batches: number; + buildDate: string; +}; + +/** + * Creative Corpus Build Workflow + * + * Pulls every creative in the Meta account over a 90-day window, + * batches IDs into groups of ≤15, fetches summary + transcript + glossary + * for each batch, and writes one Markdown file per creative. + * + * Motion CLI pattern for bash tasks: + * ENVELOPE=$(motion meta insights ...) + * FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + * jq '...' "$FILE" + * + * Split: bash for Motion pulls + file writes; agent only for naming decode (LLM needed). + * Agent tasks write structured output to /tmp files; bash reads those. + */ +export const wf: Workflow< + { forceRebuild?: boolean }, + CorpusBuildOutput +> = async ({ input, task }) => { + const forceRebuild = input?.forceRebuild ?? false; + + // ─── Step 1: Pull creative roster across 3 x 30-day windows ────────────── + // Separate calls — 90-day + summaries times out (Jude's lesson). + // No transcripts/summaries here — just the roster for deduplication. + + await task.bash({ + key: "roster-last-30d", + script: ` + set -euo pipefail + mkdir -p /tmp/corpus-roster /tmp/corpus-batches /agent/brain/meta/creatives + + ENVELOPE=$(motion meta insights --date-range last_30d --sort topSpend --include-metrics --limit 500) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + + jq '[.creatives[] | { + id: .id, + adName: (.adName // "unknown"), + format: (.format // "unknown"), + launchDate: (.launchDate // null), + campaignName: (.campaignName // null), + status: (.status // "unknown"), + spend: (.metrics.spend // 0) + }]' "$FILE" > /tmp/corpus-roster/batch-30.json + + echo "30d roster: $(jq length /tmp/corpus-roster/batch-30.json) creatives" + `, + }); + + await task.bash({ + key: "roster-31-60d", + script: ` + set -euo pipefail + + # Portable date arithmetic via node (avoids GNU vs BSD date differences) + START=$(node -e "const d=new Date();d.setDate(d.getDate()-60);console.log(d.toISOString().slice(0,10))") + END=$(node -e "const d=new Date();d.setDate(d.getDate()-31);console.log(d.toISOString().slice(0,10))") + + ENVELOPE=$(motion meta insights --start-date "$START" --end-date "$END" --sort topSpend --include-metrics --limit 500) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + + jq '[.creatives[] | { + id: .id, + adName: (.adName // "unknown"), + format: (.format // "unknown"), + launchDate: (.launchDate // null), + campaignName: (.campaignName // null), + status: (.status // "unknown"), + spend: (.metrics.spend // 0) + }]' "$FILE" > /tmp/corpus-roster/batch-60.json + + echo "31-60d roster: $(jq length /tmp/corpus-roster/batch-60.json) creatives" + `, + }); + + await task.bash({ + key: "roster-61-90d", + script: ` + set -euo pipefail + + START=$(node -e "const d=new Date();d.setDate(d.getDate()-90);console.log(d.toISOString().slice(0,10))") + END=$(node -e "const d=new Date();d.setDate(d.getDate()-61);console.log(d.toISOString().slice(0,10))") + + ENVELOPE=$(motion meta insights --start-date "$START" --end-date "$END" --sort topSpend --include-metrics --limit 500) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + + jq '[.creatives[] | { + id: .id, + adName: (.adName // "unknown"), + format: (.format // "unknown"), + launchDate: (.launchDate // null), + campaignName: (.campaignName // null), + status: (.status // "unknown"), + spend: 0 + }]' "$FILE" > /tmp/corpus-roster/batch-90.json + + echo "61-90d roster: $(jq length /tmp/corpus-roster/batch-90.json) creatives" + `, + }); + + // ─── Step 2: Deduplicate + create batches ───────────────────────────────── + + const batchSetup = await task.bash({ + key: "create-batches", + script: ` + set -euo pipefail + + node -e " + const fs = require('fs'); + const path = require('path'); + + const r30 = JSON.parse(fs.readFileSync('/tmp/corpus-roster/batch-30.json', 'utf8') || '[]'); + const r60 = JSON.parse(fs.readFileSync('/tmp/corpus-roster/batch-60.json', 'utf8') || '[]'); + const r90 = JSON.parse(fs.readFileSync('/tmp/corpus-roster/batch-90.json', 'utf8') || '[]'); + + // Deduplicate by id — keep highest-spend entry + const byId = new Map(); + for (const c of [...r30, ...r60, ...r90]) { + if (!c.id) continue; + const existing = byId.get(c.id); + if (!existing || (c.spend || 0) > (existing.spend || 0)) { + byId.set(c.id, c); + } + } + const allCreatives = Array.from(byId.values()); + + // Skip already-indexed unless forceRebuild + const forceRebuild = ${forceRebuild}; + const toProcess = forceRebuild ? allCreatives : allCreatives.filter(c => { + const id8 = c.id.slice(0, 8); + const slug = c.adName.replace(/[^a-zA-Z0-9-_]/g, '-').slice(0, 80); + return !fs.existsSync('/agent/brain/meta/creatives/' + slug + '__' + id8 + '.md'); + }); + + // Batch into groups of 15 + const batches = []; + for (let i = 0; i < toProcess.length; i += 15) { + batches.push(toProcess.slice(i, i + 15)); + } + + // Write each batch file + batches.forEach((batch, i) => { + fs.writeFileSync('/tmp/corpus-batches/batch-' + i + '.json', JSON.stringify(batch)); + }); + + const manifest = { total: allCreatives.length, toProcess: toProcess.length, batchCount: batches.length }; + fs.writeFileSync('/tmp/corpus-batches/manifest.json', JSON.stringify(manifest)); + console.log(JSON.stringify(manifest)); + " + `, + }); + + let manifest = { total: 0, toProcess: 0, batchCount: 0 }; + try { + manifest = JSON.parse(batchSetup.stdout.trim()); + } catch { + // Default to 0 batches — nothing to process + } + + // ─── Step 3: Check for naming decoder (used during file writes) ─────────── + + await task.bash({ + key: "check-naming-decoder", + script: ` + if [ -f /agent/brain/ad-naming/naming-decoder.md ]; then + echo "decoder-present" + else + echo "decoder-absent" + fi + `, + }); + + // ─── Step 4: Process batches — fetch enrichment + write MD files ────────── + // Each batch: bash fetches enrichment from Motion, then writes the MD files. + // Agent is only called when naming decode needs LLM interpretation. + + let totalBuilt = 0; + let totalFailed = 0; + + for (let batchIndex = 0; batchIndex < manifest.batchCount; batchIndex++) { + // Fetch enrichment for this batch via bash + await task.bash({ + key: `fetch-enrichment-${batchIndex}`, + script: ` + set -euo pipefail + + # Read the batch to get IDs + BATCH_FILE="/tmp/corpus-batches/batch-${batchIndex}.json" + IDS=$(jq -r '.[].id' "$BATCH_FILE") + + # Build the --creative-asset-id flags + ID_FLAGS="" + while IFS= read -r id; do + ID_FLAGS="$ID_FLAGS --creative-asset-id $id" + done <<< "$IDS" + + # Fetch enrichment (single motion call — no pipe on the motion command itself) + ENVELOPE=$(motion meta insights --scope creative-asset-id $ID_FLAGS --include-transcript --include-glossary --date-range last_365d --include-metrics) + FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') + + # Save enrichment file path for the write step + echo "$FILE" > /tmp/corpus-batches/enrichment-${batchIndex}-path.txt + + echo "batch-${batchIndex}: enrichment fetched from $FILE" + `, + }); + + // Write MD files for this batch + const writeResult = await task.bash({ + key: `write-files-${batchIndex}`, + script: ` + set -euo pipefail + + BATCH_FILE="/tmp/corpus-batches/batch-${batchIndex}.json" + ENRICHMENT_FILE=$(cat /tmp/corpus-batches/enrichment-${batchIndex}-path.txt) + DECODER_EXISTS=$([ -f /agent/brain/ad-naming/naming-decoder.md ] && echo "true" || echo "false") + TODAY=$(node -e "console.log(new Date().toISOString().slice(0,10))") + + node -e " + const fs = require('fs'); + const path = require('path'); + + const batch = JSON.parse(fs.readFileSync('$BATCH_FILE', 'utf8')); + const enrichData = JSON.parse(fs.readFileSync('$ENRICHMENT_FILE', 'utf8')); + const creativeMap = {}; + for (const c of (enrichData.creatives || [])) { + if (c.id) creativeMap[c.id] = c; + } + + const decoderExists = $DECODER_EXISTS; + let built = 0; + let failed = 0; + + for (const meta of batch) { + try { + const creative = creativeMap[meta.id] || {}; + const id8 = meta.id.slice(0, 8); + const slug = meta.adName.replace(/[^a-zA-Z0-9-_]/g, '-').slice(0, 80); + const filePath = path.join('/agent/brain/meta/creatives', slug + '__' + id8 + '.md'); + + const spend = meta.spend || 0; + const spendState = spend > 1000 ? 'scaling' : spend > 0 ? 'active' : 'paused'; + + const tags = (creative.glossaryTags || []) + .map(t => '- ' + t.categoryId + ': ' + t.tagName) + .join('\n') || 'No tags returned.'; + + const transcript = creative.transcript || 'No transcript available.'; + const summary = creative.summary || 'No summary available.'; + const hook = transcript !== 'No transcript available.' + ? transcript.split(/[.!?]/)[0]?.trim() || 'No hook extracted.' + : (summary !== 'No summary available.' ? summary.slice(0, 150) : 'No hook available.'); + + const decodedName = decoderExists + ? 'See /agent/brain/ad-naming/naming-decoder.md for dimension definitions. Raw: ' + meta.adName + : 'Install Ad Naming to enable ad name decoding.'; + + const md = [ + '---', + 'id: ' + meta.id, + 'adName: ' + meta.adName, + 'id8: ' + id8, + 'format: ' + (meta.format || 'unknown'), + 'launchDate: ' + (meta.launchDate || 'unknown'), + 'campaignName: ' + (meta.campaignName || 'unknown'), + 'status: ' + (meta.status || 'unknown'), + 'spendState: ' + spendState, + 'indexedAt: $TODAY', + '---', + '', + '# ' + meta.adName, + '', + '## Hook', + hook, + '', + '## Transcript', + transcript, + '', + '## Summary', + summary, + '', + '## Glossary Tags', + tags, + '', + '## Decoded Ad Name', + decodedName, + ].join('\n'); + + fs.writeFileSync(filePath, md); + built++; + } catch (err) { + console.error('Failed to write ' + meta.id + ': ' + err.message); + failed++; + } + } + + console.log(JSON.stringify({ built, failed })); + " + `, + }); + + try { + const result = JSON.parse(writeResult.stdout.trim()); + totalBuilt += result.built ?? 0; + totalFailed += result.failed ?? 0; + } catch { + // Continue — partial batch + } + } + + // ─── Step 5: Update corpus state ────────────────────────────────────────── + + await task.bash({ + key: "update-corpus-state", + script: ` + set -euo pipefail + TODAY=$(node -e "console.log(new Date().toISOString().slice(0,10))") + + node -e " + const fs = require('fs'); + let state = {}; + try { state = JSON.parse(fs.readFileSync('/agent/brain/meta/corpus-state.json', 'utf8')); } catch {} + + const files = fs.readdirSync('/agent/brain/meta/creatives') + .filter(f => f.endsWith('.md') && f !== 'PLAYBOOK.md'); + + state.totalCreatives = files.length; + state.lastBuildDate = '$TODAY'; + fs.writeFileSync('/agent/brain/meta/corpus-state.json', JSON.stringify(state, null, 2)); + console.log(files.length + ' creatives indexed'); + " + + # Append to playbook build log + TOTAL=$(node -e " + const fs = require('fs'); + const s = JSON.parse(fs.readFileSync('/agent/brain/meta/corpus-state.json', 'utf8')); + console.log(s.totalCreatives); + ") + echo "" >> /agent/brain/meta/creatives/PLAYBOOK.md + echo "- $TODAY: Build run — \${TOTAL} total creatives indexed" >> /agent/brain/meta/creatives/PLAYBOOK.md + `, + }); + + const buildDate = new Date().toISOString().slice(0, 10); + + return { + totalBuilt, + totalSkipped: manifest.toProcess - totalBuilt - totalFailed, + totalFailed, + batches: manifest.batchCount, + buildDate, + }; +}; From e1472ebbd631c6bbeb0e25fbc3600dcf1a2b2da0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 20 Jul 2026 11:41:23 +0000 Subject: [PATCH 17/25] docs: add repository and CLI engineering guide Co-authored-by: vamsi --- README.md | 4 + docs/REPOSITORY_AND_CLI_GUIDE.md | 1065 ++++++++++++++++++++++++++++++ 2 files changed, 1069 insertions(+) create mode 100644 docs/REPOSITORY_AND_CLI_GUIDE.md diff --git a/README.md b/README.md index 6c6ac2db..f85281fe 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ The app & skill library behind the **Runneth Use Case Library** at [runneth.motionapp.com](https://runneth.motionapp.com). Each top-level directory is a self-contained use case — a skill bundle or a sandbox app — that a Runneth user can install in a couple of clicks. The public site reads this repo's curated metadata **live** via the GitHub raw API (~60s cache), so editing the files here *is* how the site is organized. +For the package-manager contract, repository architecture, and an evidence-ranked +inventory of local and external CLI commands, see +[`docs/REPOSITORY_AND_CLI_GUIDE.md`](docs/REPOSITORY_AND_CLI_GUIDE.md). + ## How a use case is structured | File | Purpose | diff --git a/docs/REPOSITORY_AND_CLI_GUIDE.md b/docs/REPOSITORY_AND_CLI_GUIDE.md new file mode 100644 index 00000000..e4136a33 --- /dev/null +++ b/docs/REPOSITORY_AND_CLI_GUIDE.md @@ -0,0 +1,1065 @@ +# Runneth Apps Repository and CLI Guide + +This is the durable engineering map for `Motion-Creative/runneth-apps`. Use it when +adding packages, changing the public use-case library, building sandbox apps, or +writing skills that invoke Runneth platform commands. + +The repository contains many command examples but does not implement most of the +Runneth platform CLI. This guide separates: + +- **Implemented here**: code and parsers in this repository. +- **External, consumed here**: Runneth/Motion commands invoked by skills and workflows. +- **Documentary or conflicted**: examples whose current platform contract cannot be + established from this repository alone. + +Generic shell, Git, package-manager, and media utility commands are summarized rather +than listing every incidental invocation. + +## Repository map + +The repository has two independent distribution systems. + +### Runneth package manager + +| Surface | Location | Role | +|---|---|---| +| Registry | `package-index.json` | Advertises installable packages and sync policy | +| Package source | selected by `source.path` | Installer manifests and package resources; current examples use `packages//` | +| Validator | `scripts/validate-runneth-package-index.mjs` | Enforces the checked-in package contract | +| CI | `.github/workflows/validate-catalog.yml` | Runs package validation on PRs and `main` | + +The checked-out cumulative feature branch contains three package candidates: + +1. `context-kit`: brand knowledge, a board app, skill, and refresh workflow. +2. `ad-naming`: naming decoder, KPI map, query contract, skill, and refresh workflow. +3. `creative-corpus`: durable per-creative files, build workflow, and daily refresh script. + +At the time this guide was written, `origin/main` still had an empty package index and +none of these package directories. They are not released through the main registry +until their stacked PRs merge in order: Context Kit #150, Ad Naming #156, then Creative +Corpus #157. Always inspect `origin/main`, not only the working tree, when deciding +whether a package is released. + +Install order is documented behavior, not a schema-level dependency: + +```text +Context Kit -> Ad Naming -> Creative Corpus +``` + +### Public use-case library (legacy contract, active surface) + +| Surface | Location | Role | +|---|---|---| +| Ordered catalog | `.use-case-library/catalog.json` | Controls which use cases are public | +| Categories | `.use-case-library/categories.json` | Controls public site tabs | +| Use cases | top-level or `landing-page-bundle//` | Skills, metadata, and sandbox app source | +| Validator | `scripts/validate-catalog.mjs` | Enforces the catalog contract | +| Site | `use-case-library-site/` | React/Vite frontend and Fastify server | + +The package validator deliberately ignores use-case directories unless the package +index references them. The package registry and public use-case catalog are not +interchangeable. The catalog currently publishes 10 cards and deliberately retains +24 additional excluded cards with reasons. Most standalone sandbox app roots are +excluded, not public. + +For a public card: + +- `use-case.json` owns card metadata, category, and status; +- `marketing.md` owns customer-facing copy; +- `README.md` owns the “How it is built” content; +- `install-config.json` optionally owns installation steps and customization tokens; +- `SKILL.md` contains skill behavior when applicable. + +Status is read only from `use-case.json`. The catalog validator applies stricter +requirements to shown cards than excluded cards, and validates install-config version +and changelog structure globally. Post-install intros are specified in +`.use-case-library/post-install-intro-spec.md` but are not fully CI-enforced. + +### Other significant areas + +- `corpus-search/`: the repository's most complete locally implemented CLI. +- `building-integrations/`: integration-generation guidance and templates. +- `brain-onboard/`, `runneth-classic/`, `paid-strategy-audit/`: broad examples of + Motion CLI consumption. +- App roots such as `conversation-manager/`, `brief-qa/`, `video-qa/`, + `file-explorer/`, and `creative-qa/`: independent frontend/server projects. +- `scripts/`: zero-dependency repository validators. + +There is no root npm or pnpm workspace. The public site is a pnpm workspace; most +other apps have independent manifests and lockfiles. + +The public site reads catalog content from GitHub with an approximately 60-second +cache. Its server also persists reviews and brain submissions in SQLite, so production +deployments require durable mounted storage; it is not only a stateless catalog proxy. +See `use-case-library-site/server/src/github.ts`, +`use-case-library-site/server/src/db.ts`, and +`use-case-library-site/server/src/brain-submissions-db.ts`. + +## Package contract + +### Transitional three-file contract + +A package currently participates in three contracts: + +1. `package-index.json`: registry metadata and source location. +2. `packages//package.json`: read by the VM installer. +3. `packages//runneth-package.json`: read by the checked-in repository validator. + +The Context Kit README documents this temporary split explicitly. Runtime +`package.json` is canonical for installation; `runneth-package.json` is a compatibility +copy for the current CI validator. Keep shared metadata and resource lists semantically +aligned, but do not make the files byte-identical: + +- Runtime `package.json` currently includes `installPolicy`. +- The checked-in validator rejects `installPolicy` in `runneth-package.json`. + +Treat `scripts/validate-runneth-package-index.mjs` as the authority for current CI +and the runtime installer response as the authority for installation behavior. +This is transitional, not a permanent recommendation. + +### Checked-in validator index requirements + +`package-index.json` has exactly these top-level keys: + +```json +{ + "schemaVersion": 1, + "indexRevision": "non-empty-string", + "packages": [] +} +``` + +Each entry has exactly: + +```json +{ + "id": "package-id", + "name": "Package Name", + "description": "Exact manifest description.", + "version": "1.2.3", + "categories": ["non-empty"], + "packageManagerVersion": 1, + "source": { + "type": "github", + "owner": "Motion-Creative", + "repo": "runneth-apps", + "ref": "main", + "path": "packages/package-id" + }, + "updatePolicy": "manual", + "uninstallPolicy": "allowed" +} +``` + +Important checked-in constraints: + +- `version` is strict numeric `X.Y.Z`. +- `categories` must be a non-empty array of non-empty strings. +- `indexRevision` only needs to be non-empty; revision bumps are conventional. +- Package IDs and resource IDs must be unique. +- The ID regex is `[a-z0-9][a-z0-9-]*`; despite the validator's “kebab-case” + message, consecutive and trailing hyphens currently pass. +- `updatePolicy` is `auto` or `manual`; `uninstallPolicy` is `allowed` or `protected`. +- Empty package and resource arrays are accepted. +- Categories are not checked against the public catalog taxonomy and may duplicate. +- Source type must be `github`. +- Owner, repository, and ref must be exactly `Motion-Creative`, `runneth-apps`, + and `main`. +- Index and validator manifest must match on ID, name, description, version, + update policy, and uninstall policy. +- Objects use strict key sets; unsupported extra keys fail validation. + +Registry source refs remain `main` even while testing a package from a feature branch. +The validator resolves manifests from the local checkout; it does not verify that +`source.ref: main` currently contains the path. + +The checked-in validator rejects `installPolicy` in index entries, while newer +managed-sync behavior has required it in runtime-facing metadata. The current branch +cannot express one index entry that proves both contracts. Direct branch installation, +when supported by the live package CLI, reads runtime `package.json` and bypasses this +index conflict. + +### Current validator manifest + +`runneth-package.json` has exactly: + +```json +{ + "schemaVersion": 1, + "id": "package-id", + "name": "Package Name", + "description": "Exact index description.", + "version": "1.2.3", + "updatePolicy": "manual", + "uninstallPolicy": "allowed", + "resources": [] +} +``` + +Allowed resource types: + +| Type | Required fields | Meaning | +|---|---|---| +| `package_instruction` | `id`, `type`, `sourcePath` | Installs package-owned prompt instructions | +| `file` | `id`, `type`, `sourcePath`, `target`, `executable` | Installs one file | +| `directory` | `id`, `type`, `sourcePath`, `target`, `executablePaths` | Recursively installs a directory | + +Allowed target roots: + +- `agent_apps` +- `agent_brain` +- `agent_skills` +- `agent_tools` + +Current package layout conventions: + +- `instructions/behavior.md` is declared as `package_instruction`; +- `skills/` is installed as an `agent_skills` directory; +- files under `brain/` are mapped explicitly into `agent_brain`; +- app source under `apps//` is installed as an `agent_apps` directory; +- v1 workflow and script files are ordinary resources staged into brain paths, then + registered by an installed skill. + +Declared resource source paths must be relative, exist, have the declared file kind, +and contain no symlink segment. `..`, leading `/`, and `//` are rejected. The validator +does not verify target existence, target writability, or target collisions. + +`executable: true` and `executablePaths` are installer metadata. The source file does +not need Git executable mode. Current CI accepts the corpus script as mode `100644`; +installer chmod behavior is external and is not verified by this repository. + +### What package installation does not do + +Current package instructions treat installation as staging declared resources. They +do not rely on installation to: + +- build apps; +- push or register workflows; +- create tasks or routines; +- install arbitrary dependencies; +- run setup interviews; +- enforce package dependency order. + +Only the “sync does not build apps” behavior is explicitly documented by the package +README. Confirm broader installer behavior against the live platform before making it +a new contract. + +Activation belongs in the installed skill and package instructions. In the current +package series: + +- Context Kit builds its app and registers its refresh workflow on first use. +- Ad Naming registers its refresh workflow on first use. +- Creative Corpus registers and runs its build workflow, then creates a refresh routine. + +### Fleet approval + +The current validator treats `updatePolicy: "auto"` as auto-installable and requires +the `runneth-fleet-change-approved` PR label for: + +- a new auto package; +- a manual-to-auto transition; +- any fingerprint change while the base package is auto, including auto-to-manual; +- removal of an auto package. + +The fingerprint contains categories, source, uninstall policy, update policy, and +version. It excludes name, description, package-manager version, index revision, +resources, and package content. The checked-in gate does not understand runtime +`installPolicy`. + +All three current packages are manual, so this gate is inactive for them. + +There is a known push-event defect: on a push to `main`, PR base and label context are +absent, so the current validator can treat every auto package as newly auto and cannot +observe the approval label. Adding the label also does not normally trigger the default +PR workflow by itself; rerun CI or push a new commit. + +### Package validation + +Run: + +```bash +node --test scripts/validate-runneth-package-index.mjs +``` + +Also verify shared runtime and validator fields, ignoring only runtime-only +`installPolicy`: + +```bash +node - <<'NODE' +const fs = require("node:fs"); + +for (const id of ["context-kit", "ad-naming", "creative-corpus"]) { + const runtime = JSON.parse(fs.readFileSync(`packages/${id}/package.json`)); + const validator = JSON.parse(fs.readFileSync(`packages/${id}/runneth-package.json`)); + + for (const key of [ + "schemaVersion", + "id", + "name", + "description", + "version", + "updatePolicy", + "uninstallPolicy", + "resources", + ]) { + if (JSON.stringify(runtime[key]) !== JSON.stringify(validator[key])) { + throw new Error(`${id}: ${key} differs`); + } + } +} +NODE +``` + +The repository validator does not compile workflow TypeScript, build packaged apps, +test installation, validate target collisions, or enforce version bumps. + +### Release model + +There is no package publish, archive, tag, or immutable-release CI job. Release +effectively occurs when package files and their index entry reach `main`; current index +sources are mutable `main` references. + +The current rollout is stacked: + +```text +Context Kit PR #150 -> Ad Naming PR #156 -> Creative Corpus PR #157 +``` + +A successful direct feature-branch install proves runtime manifest and resource +behavior only. It does not prove registry ingestion, managed sync, fleet behavior, or +availability through the configured main ref. After merge, test `main` and package +discovery separately. + +## CLI authority model + +Use this order when command examples disagree: + +1. A local parser or executable implementation. +2. Current platform `--help` output on a VM. +3. Current package workflows and skills. +4. Older use-case skills and README examples. + +The repository implements validators, `corpus-search`, and several standalone scripts. +`package`, `motion`, `app`, `workflow`, `task`, `routine`, `conversation`, `reminder`, +`slack`, and `integrations` are external platform commands. + +Primary command evidence: + +| Family | Strongest repository evidence | +|---|---| +| Package schema | `scripts/validate-runneth-package-index.mjs` | +| Package runtime behavior | package READMEs/skills plus live install results; no local installer | +| Current Motion package calls | `packages/*/workflows/`, `packages/*/skills/` | +| Broader/older Motion calls | `brain-onboard/`, `runneth-classic/`, `paid-strategy-audit/`, `competitor-intel/` | +| App lifecycle | app README files and package skills; no local app CLI parser | +| Workflow/task/routine | `packages/*/skills/SKILL.md`; external platform CLIs | +| Slack/integrations | onboarding, health-alert, competitor, and permissions skills | +| Corpus Search | `corpus-search/bin/corpus_search_cli.py` | +| Standalone Python/shell tools | each script's parser, usage string, or source | + +## Repository and build commands + +### Local CI checks + +```bash +node --test scripts/validate-catalog.mjs +node --test scripts/validate-runneth-package-index.mjs + +cd use-case-library-site +pnpm install --frozen-lockfile +pnpm build +``` + +The GitHub workflow uses Node 20 and pnpm 9. +Package validation checks out full history so it can compare the base index. A normal +local run lacks `GITHUB_BASE_REF`, the PR event payload, and labels, so it does not +exercise fleet approval exactly as CI does. The site build covers only +`use-case-library-site`; it does not build the packaged Context Kit app. + +### Public site + +From `use-case-library-site/`: + +```bash +pnpm install:all +pnpm build +pnpm start +pnpm dev:server +pnpm dev:frontend +PORT=8080 pnpm start +``` + +Read `use-case-library-site/package.json` and its README before assuming these scripts +apply to another app. + +### Context Kit app source + +`packages/context-kit/apps/context-kit/` is an Astro project. Its local npm scripts +compile only the source project; the external `app` CLI performs VM registration and +deployment. + +## External `package` CLI + +No package CLI implementation or complete command schema lives in this repository. +Package PRs have used the following direct-install form, but it is external evidence, +not a locally implemented or parsed contract. Confirm it with live package help: + +```bash +package install "github:Motion-Creative/runneth-apps/packages/#" +``` + +One exact PR example is: + +```bash +package install \ + "github:Motion-Creative/runneth-apps/packages/context-kit#context-kit-package" +``` + +When supported, direct installation reads runtime `package.json`; it does not test +`package-index.json`, `runneth-package.json`, fleet approval, managed sync, or whether +the configured registry `main` ref contains the package. After merge, separately test +the main ref and registry/managed-sync path. + +Do not invent `package list`, `package uninstall`, or package-sync syntax from this +repository; confirm those with platform help. + +## External `motion` CLI + +Current package data workflows treat the Motion commands they invoke as returning an +envelope on stdout: + +```bash +ENVELOPE=$(motion ...) +FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') +jq '...' "$FILE" +``` + +For those calls, do not assume the envelope itself is the data payload. This is not a +universal Motion rule: `motion analyze-media` consumers parse stdout directly. + +### Workspace and account context + +```bash +motion workspaces +motion workspace-goal +motion spend-threshold +motion reports +motion ai-glossary +motion creative-trends +motion benchmark-compare +motion cache search-summaries +``` + +Observed brand-context queries: + +```bash +motion brand-context --data-query "summary" +motion brand-context --data-query brand_identity +motion brand-context --data-query "strategy positioning customer audience product" +motion brand-context --data-query \ + "brand foundations, fundamentals, product information, competitors, customer voice analysis" +``` + +### Meta creative and performance data + +Common current forms: + +```bash +motion meta insights \ + --date-range last_7d \ + --sort topSpend \ + --include-metrics \ + --limit 500 + +motion meta insights \ + --date-range last_30d \ + --sort topSpend \ + --include-metrics \ + --limit 500 + +motion meta insights \ + --start-date "$START" \ + --end-date "$END" \ + --sort topSpend \ + --include-metrics \ + --limit 500 + +motion meta insights \ + --scope creative-asset-id \ + --creative-asset-id \ + --include-transcript \ + --include-glossary \ + --date-range last_365d \ + --include-metrics + +motion meta insights \ + --include-glossary \ + --date-range last_30d \ + --sort topSpend \ + --limit 100 + +motion meta insights \ + --include-transcript \ + --date-range last_30d \ + --sort topSpend \ + --limit 20 + +motion meta insights \ + --date-range last_30d \ + --sort topSpend \ + --limit 1 \ + --table-kpi thumbstop_rate +``` + +The creative-corpus code repeats `--creative-asset-id` for batches of at most 15 IDs. + +Naming and conversion discovery: + +```bash +motion meta ads \ + --grain adnames \ + --date-range last_90d \ + --sort-by spend \ + --sort-direction desc \ + --limit 200 + +motion meta custom-conversion-metrics +motion meta metric-reference --query "appointments scheduled" +motion meta age-gender +``` + +Workspace-specific performance examples also use: + +```bash +motion meta insights \ + --workspace-id \ + --date-range last_30d \ + --limit 1000 \ + --sort topSpend \ + --include-metrics \ + --group-by creative \ + --chart-kpi "_count" \ + --chart-kpi "_cost" +``` + +Other observed flags include `--group-by name`, `--include-northbeam`, +`--click-attribution-window`, and `--view-attribution-window`. + +### TikTok + +```bash +motion tiktok insights --date-range last_30d --include-metrics +motion tiktok insights --grain ads + +motion tiktok insights \ + --workspace-id \ + --date-range last_30d \ + --limit 1000 \ + --sort-by spend \ + --sort-direction desc \ + --grain ads \ + --include-metrics +``` + +### Competitor and inspiration data + +```bash +motion search-brands --search-term "" --limit 5 --with-brand-context +motion search-brands --search-term "" --limit 5 + +motion inspo-creatives \ + --brand-id \ + --status active \ + --sort newestLaunchDate \ + --limit 150 + +motion inspo-creatives \ + --brand-id \ + --status active \ + --sort oldestLaunchDate \ + --limit 150 + +motion inspo-creatives \ + --brand-id \ + --status inactive \ + --sort newestLaunchDate \ + --limit 150 + +motion inspo-creatives --brand-id --include-glossary --limit 50 +motion inspo-context --brand-id +motion meta competitor-ad-insights \ + --ad-library-creative-id \ + --include-glossary \ + --with-summary +``` + +`motion inspo brands --limit 20` appears in one Context Kit workflow but is not +corroborated elsewhere. Confirm it with `motion --help` before depending on it. + +### Older or generic Motion forms + +Older use cases contain forms such as: + +```bash +motion creative-insights \ + --workspace-id \ + --date-range last_30d \ + --limit 150 \ + --sort topSpend \ + --summary-sections adDescription \ + --summary-sections hookOrHeadline \ + --include-metrics + +# Documentary partial form; confirm section names and required IDs with live help. +motion creative-insights \ + --scope creative-asset-id \ + --summary-sections
+ +motion custom-conversion-metrics +motion age-gender-breakdown +motion analyze-media --filename --prompt +``` + +Do not automatically substitute these for the current namespaced Meta/TikTok commands. +Command naming has drifted, so check live help when maintaining legacy skills. + +## External `app` CLI + +Observed forms: + +```bash +app create +app create --route +app build +app verify +app list +``` + +For a newly created app, examples require: + +1. Run `app create ` or the route form. +2. Populate its source and `buildeth.app.json`. +3. Run `app build `. +4. Run `app verify `. +5. Run `app list` to obtain the route/URL. + +Package-staged apps such as Context Kit already include source/config templates and +start from runtime placeholder replacement plus `app build`. + +`app remove` is mentioned in prose but no complete syntax is established here. + +## External workflow, task, and routine CLIs + +### Workflow registration + +```bash +workflow push /agent/brain/context-kit/workflows/context-kit-refresh.ts \ + --name context-kit-refresh + +workflow push /agent/brain/ad-naming/workflows/ad-naming-refresh.ts \ + --name ad-naming-refresh + +workflow push /agent/brain/meta/corpus-build.ts \ + --name creative-corpus-build +``` + +### Workflow-backed tasks + +```bash +task add --kind workflow --workflow-id --name "Context Kit Refresh" +task add --kind workflow --workflow-id --name "Ad Naming Refresh" +task add --kind workflow --workflow-id --name "Creative Corpus Build" + +task run --id +task wait --run +``` + +No task-listing syntax is established by the checked-in package skills. Confirm it +with platform help before making it part of a package contract. + +### Routines + +Current package examples: + +```bash +routine list + +routine add \ + --name "Creative corpus daily refresh" \ + --cron "0 5 * * *" \ + --delivery "Update corpus state only — no conversation needed unless errors occur." \ + --prompt "Run the corpus refresh script. If the script fails, report the error in a new web conversation." \ + --script /agent/brain/meta/corpus-refresh.mjs +``` + +Workflow-backed weekly refresh examples use: + +```bash +routine add \ + --name "" \ + --cron "0 9 * * 1" \ + --delivery "" \ + --prompt "" +``` + +Routine prompt prose contains this conversation-delivery form: + +```bash +conversation send --new +``` + +It is not independently corroborated by an implementation or standalone invocation. +Verify it with live help. + +The Creative Corpus playbook mentions resuming a paused routine but gives no verified +`routine resume` syntax. + +## ContextConfig and Knoweth + +These are described as tool/API operations, not shell commands. + +Current packages register: + +- `context-kit-core`, `context-kit-brand`, and `context-kit-performance` lanes; +- an `ad-naming` lane for the decoder, KPI map, and query contract; +- a `creative-corpus` directory lane for per-creative files. + +Creative Corpus explicitly asks for ContextConfig action `update`; the other packages +say “register” without a complete schema. No ContextConfig implementation or Knoweth +CLI is present in this repository. Use the live tool schema, not invented shell syntax. + +## External Slack and integration CLIs + +Observed forms: + +```bash +integrations list + +slack doctor +slack memberships list +slack search channels + +slack send --channel --text "" +slack send --channel --thread --text "" +slack send --conversation --text "" + +google doctor +notion doctor +``` + +These appear in onboarding, health alerts, competitor intelligence, permissions, and +update workflows. The thread form repairs a malformed checked-in example and remains +unverified until confirmed with live Slack CLI help. + +## Legacy `reminder` CLI + +The repository does not implement reminders and contains incompatible command shapes. +Only these read forms have argument syntax: + +```bash +reminder list +reminder get +``` + +`reminder delete` and `reminder complete` are mentioned only as command names; their +arguments are unknown. + +Competing creation examples include: + +```bash +reminder add \ + --name "" \ + --cron "" \ + --timezone "" \ + --content "" +``` + +```bash +reminder add \ + --title "" \ + --description "<description>" \ + --schedule "<natural-language schedule>" +``` + +Other files substitute `--body`, or use `--recurrence`, `--message`, and +`--conversation-id`. `reminder update` is explicitly described as unavailable. + +Treat all reminder creation syntax as legacy until verified on a current VM. New package +work uses routines. + +## Locally implemented `corpus-search` CLI + +Implementation: + +- Wrapper: `corpus-search/corpus-search.sh` +- Parser: `corpus-search/bin/corpus_search_cli.py` + +First-time installation: + +```bash +bash /agent/tools/corpus-search/install.sh +``` + +Invocation after installation: + +```bash +bash /agent/tools/corpus-search/corpus-search.sh <command> +``` + +Exact parser surface: + +```text +corpus-search init +corpus-search status +corpus-search check-endpoint + +corpus-search index markdown + --source <directory> + --kind <free-form-tag> + [--tenant <tenant>] + [--pattern <glob>] + [--limit <integer>] + [--quiet] + +corpus-search embed + [--model <model>] + [--dim <integer>] + [--batch-size <integer>] + [--max-chunks <integer>] + [--quiet] + +corpus-search query <query> + [--top <integer>] + [--kind <kind>] + [--role <role>] + [--workspace <workspace>] + [--user <user>] + [--since <ISO-date>] + [--until <ISO-date>] + [--pool <integer>] + [--no-vector] + [--rerank | --no-rerank] + [--rerank-pool <integer>] + [--format human|json] + +corpus-search refresh [--no-embed] [--quiet] +corpus-search demo [--queries <query> ...] +``` + +Known documentation defects: + +- `runneth-classic/SKILL.md` uses unsupported `search --query`; the parser uses + `query <query>`. +- `corpus-search/README.md` shows unsupported `--brand`; remove it or add parser + support. Use `--workspace` only when the indexed workspace field intentionally + represents the desired scope. +- The README and example config disagree on the default embedding batch size; parser and + loaded config behavior are authoritative. +- `corpus-search/lib/rerank.py` says reranking is off by default, while current parser + and example config behavior make it on by default. + +## Other locally implemented entrypoints + +### Import from AI + +```bash +python3 import-from-ai/lib/parse.py \ + --input <file-zip-or-directory> \ + --output <manifest.json> \ + --user-handle <handle> \ + --home-base <directory> \ + --provider chatgpt|claude|gemini \ + --import-id <id> + +python3 import-from-ai/lib/render-review.py \ + --manifest <manifest.json> \ + --output <review.html> \ + [--design-system <design-system.json>] +``` + +### Review processor + +```bash +python3 review-library/scripts/process_reviews.py \ + --config <config.json> \ + [--cache-output <path>] +``` + +### Video asset pipeline + +```text +python3 video-asset-search/scripts/run_pipeline.py + --drive-url <url> [--folder-name <name>] [--uploads-dir <path>] +python3 video-asset-search/scripts/fetch_drive.py + <drive-url> [--dest <directory>] +python3 video-asset-search/scripts/process_video.py + <video-path> [--folder-name <name>] [--uploads-dir <path>] + [--object-taxonomy <text>] +python3 video-asset-search/scripts/query_shots.py + <query> [--limit <integer>] [--threshold <float>] [--cut-clips] +python3 video-asset-search/scripts/asset_db.py +python3 video-asset-search/scripts/embed_onnx.py [text] +``` + +The checked-in video scripts contain installer placeholders such as `{{BRAIN_PATH}}`. +They are templates and are not runnable unchanged from the repository. + +### Identity resolvers + +```bash +bash /agent/brain/admin/slack-whoami.sh <slack-user-id> [display-name] +bash /agent/brain/admin/motion-whoami.sh [display-name] +``` + +`team-member-memory` and `add-roles-permissions` contain different generations of these +scripts. Determine which package installed the active resolver before relying on path +or failure semantics. + +`import-from-ai/SKILL.md` passes an email positionally to `motion-whoami.sh`, but the +implemented resolver interprets that position as a display name and resolves email +itself. Treat that invocation as stale; it can provision an incorrect handle. + +### Migration and refresh scripts + +```bash +bash competitor-intel/migration-helper.sh +bash team-member-memory/migration-helper.sh + +ROUTINE_TRIGGER=test /agent/brain/meta/corpus-refresh.mjs +/agent/brain/meta/corpus-refresh.mjs +``` + +The Creative Corpus test trigger exits without making Motion calls. + +## Supporting external utilities + +Skills and scripts also consume: + +- `secure-fetch run` for authenticated HTTP without exposing secrets; +- `secret env` in current Git examples; +- `secret run` in gated/stale integration examples; current guidance warns that it + requires platform-controlled command allowlisting; +- `sqlite3` for local corpus storage; +- `ffmpeg` and `ffprobe` for media processing; +- `gdown` for Google Drive downloads; +- `fly launch` and `fly deploy` in deployment guidance; +- `jq`, `rg`, `curl`, Node, Python, pnpm, and standard shell utilities. + +These tools are dependencies, not repository-owned CLIs. Read the invoking file and +the tool's live help before copying options into a new package. + +## Known cross-repository inconsistencies + +Check these before reusing an old example: + +1. Package runtime and validator manifests are temporarily split. +2. The package registry and public use-case catalog are independent. +3. Current package instructions assume resource staging and explicitly say app builds + are separate; broader installer activation behavior requires live confirmation. +4. Package dependency order exists only in prose. +5. Motion command naming has drifted between generic and channel-namespaced forms. +6. `motion inspo brands` has only one repository example. +7. Age/gender guidance conflicts: some skills invoke it while Brain Onboard prohibits it. +8. Reminder creation syntax has four incompatible shapes. +9. ContextConfig registration semantics are incomplete. +10. Corpus Search has stale command examples. +11. Some local scripts are installer templates with unresolved placeholders. +12. CI does not type-check package workflows or build packaged apps. +13. A feature branch can validate locally even while its registry source points to files + not yet present on `main`. + +## Package authoring checklist + +1. Start from current `origin/main` or explicitly stack dependent package branches. +2. Choose a stable lowercase kebab-case package ID. +3. Create both runtime `package.json` and validator `runneth-package.json`. +4. Keep shared metadata and resource lists aligned. +5. Add a non-empty category and strict `X.Y.Z` version to the index. +6. Keep registry `source.ref` as `main`. +7. Verify every source path and executable declaration. +8. Document activation explicitly; do not assume the installer performs it without + live platform evidence. +9. Run both repository validators. +10. Validate workflow source using an explicitly documented compiler/runtime check; + repository CI supplies no workflow tsconfig. A successful VM `workflow push` is + separate runtime evidence. +11. Test script-mode entrypoints through their test gates when available. +12. Install from the feature branch on a fresh VM. +13. Confirm expected brain, skill, app, workflow, task, and routine artifacts. +14. Review CI and merge dependent PRs in order. + +## Fresh-VM test template + +The direct-install URI below is PR-observed external behavior, not a parser contract in +this repository. Confirm it against the VM's current package help first. Use one +cumulative ref that contains all three package paths. For the rollout described above, +that ref is `vamsi/creative-corpus-package-16a2`. + +```bash +REF="vamsi/creative-corpus-package-16a2" +package install "github:Motion-Creative/runneth-apps/packages/context-kit#$REF" +package install "github:Motion-Creative/runneth-apps/packages/ad-naming#$REF" +package install "github:Motion-Creative/runneth-apps/packages/creative-corpus#$REF" +``` + +An `installPolicy` backend 400 means the live runtime manifest contract was not +satisfied; repository CI alone cannot detect that failure. + +Confirm Motion access: + +```bash +motion workspaces +motion meta insights --date-range last_7d --sort topSpend --limit 1 +``` + +Run the package skills in order: + +```text +Build my Context Kit +Build my naming decoder +Build my corpus +``` + +Expected durable files include: + +```text +/agent/.agents/skills/context-kit/SKILL.md +/agent/.agents/skills/ad-naming/SKILL.md +/agent/.agents/skills/creative-corpus/SKILL.md +/agent/apps/context-kit/buildeth.app.json +/agent/brain/context-kit/context-kit-state.json +/agent/brain/context-kit/workflows/context-kit-refresh.ts +/agent/brain/ad-naming/ad-naming-state.json +/agent/brain/ad-naming/naming-decoder.md +/agent/brain/ad-naming/kpi-map.md +/agent/brain/ad-naming/query-contract.md +/agent/brain/ad-naming/workflows/ad-naming-refresh.ts +/agent/brain/meta/corpus-state.json +/agent/brain/meta/creatives/PLAYBOOK.md +/agent/brain/meta/corpus-build.ts +/agent/brain/meta/corpus-refresh.mjs +``` + +Exercise executable staging safely: + +```bash +ROUTINE_TRIGGER=test /agent/brain/meta/corpus-refresh.mjs +``` + +Check the Context Kit app through `app list`, and inspect task/routine state using only +commands confirmed by live platform help. Context Kit and Ad Naming offer weekly +routines; they do not create them automatically. Creative Corpus creates its daily +routine only after the corpus build completes. + +After merge, separately repeat installation from `main` and test managed/index +discovery. Feature-branch installation bypasses those release surfaces. + +## Maintaining this guide + +When command behavior changes: + +1. Update the implementation or source skill first. +2. Update this guide from parser code or live `--help`, not memory. +3. Preserve the distinction between implemented, externally consumed, and documentary. +4. Add known conflicts instead of silently choosing one unsupported syntax. +5. Re-run repository validation and link checking. From ff24f09d61541c0a95dc13ffb2f66421b1a097e1 Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Mon, 20 Jul 2026 12:00:16 +0000 Subject: [PATCH 18/25] fix(context-kit): align fresh install state Co-authored-by: vamsi <thedumsieffect@users.noreply.github.com> --- .../context-kit/data/context-kit-state.json | 45 +++++++++++++++++-- .../brain/context-kit/context-kit-state.json | 45 +++++++++++++++++-- 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/packages/context-kit/apps/context-kit/data/context-kit-state.json b/packages/context-kit/apps/context-kit/data/context-kit-state.json index 18d2c3d5..c582b88c 100644 --- a/packages/context-kit/apps/context-kit/data/context-kit-state.json +++ b/packages/context-kit/apps/context-kit/data/context-kit-state.json @@ -23,7 +23,7 @@ "id": "brand-context", "label": "Brand context", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Keeps every output unmistakably on-brand.", "preview": "", "dataFile": "brand-context.md" @@ -32,7 +32,7 @@ "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Every readout leads with the metric you care about.", "preview": "", "dataFile": "kpis-goal.md" @@ -41,7 +41,7 @@ "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Separates a proven winner from early noise.", "preview": "", "dataFile": "spend-threshold.md" @@ -145,6 +145,45 @@ "Any past ad types or messages to avoid repeating?" ] }, + { + "id": "media-buying", + "label": "Media buying model", + "bucket": "C", + "status": "missing", + "why": "Keeps creative recommendations aligned with how you actually buy.", + "dataFile": "media-buying.md", + "thoughtStarters": [ + "How do you primarily buy? (ABO, CBO, Advantage+)", + "What does a typical campaign structure look like?", + "Any budget rules or pacing preferences Runneth should know?" + ] + }, + { + "id": "business-team", + "label": "Business & team", + "bucket": "C", + "status": "missing", + "why": "Runneth works better knowing who is using it and how decisions get made.", + "dataFile": "business-team.md", + "thoughtStarters": [ + "Who reviews and approves creatives?", + "Who owns the brief-to-production pipeline?", + "Any team rules or review steps Runneth should respect?" + ] + }, + { + "id": "landing-pages", + "label": "Landing pages", + "bucket": "C", + "status": "missing", + "why": "Briefs land harder when Runneth knows what the ad is sending to.", + "dataFile": "landing-pages.md", + "thoughtStarters": [ + "What are your main landing page URLs or page types?", + "What is the primary CTA on your top pages?", + "Any pages that are off-limits for ads?" + ] + }, { "id": "integration-ad-platform", "label": "Ad platform", diff --git a/packages/context-kit/brain/context-kit/context-kit-state.json b/packages/context-kit/brain/context-kit/context-kit-state.json index 18d2c3d5..c582b88c 100644 --- a/packages/context-kit/brain/context-kit/context-kit-state.json +++ b/packages/context-kit/brain/context-kit/context-kit-state.json @@ -23,7 +23,7 @@ "id": "brand-context", "label": "Brand context", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Keeps every output unmistakably on-brand.", "preview": "", "dataFile": "brand-context.md" @@ -32,7 +32,7 @@ "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Every readout leads with the metric you care about.", "preview": "", "dataFile": "kpis-goal.md" @@ -41,7 +41,7 @@ "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Separates a proven winner from early noise.", "preview": "", "dataFile": "spend-threshold.md" @@ -145,6 +145,45 @@ "Any past ad types or messages to avoid repeating?" ] }, + { + "id": "media-buying", + "label": "Media buying model", + "bucket": "C", + "status": "missing", + "why": "Keeps creative recommendations aligned with how you actually buy.", + "dataFile": "media-buying.md", + "thoughtStarters": [ + "How do you primarily buy? (ABO, CBO, Advantage+)", + "What does a typical campaign structure look like?", + "Any budget rules or pacing preferences Runneth should know?" + ] + }, + { + "id": "business-team", + "label": "Business & team", + "bucket": "C", + "status": "missing", + "why": "Runneth works better knowing who is using it and how decisions get made.", + "dataFile": "business-team.md", + "thoughtStarters": [ + "Who reviews and approves creatives?", + "Who owns the brief-to-production pipeline?", + "Any team rules or review steps Runneth should respect?" + ] + }, + { + "id": "landing-pages", + "label": "Landing pages", + "bucket": "C", + "status": "missing", + "why": "Briefs land harder when Runneth knows what the ad is sending to.", + "dataFile": "landing-pages.md", + "thoughtStarters": [ + "What are your main landing page URLs or page types?", + "What is the primary CTA on your top pages?", + "Any pages that are off-limits for ads?" + ] + }, { "id": "integration-ad-platform", "label": "Ad platform", From d16f9a57f868cc2462d0f2befb4af889ef8d8eb3 Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Mon, 20 Jul 2026 12:00:16 +0000 Subject: [PATCH 19/25] fix(context-kit): align fresh install state Co-authored-by: vamsi <thedumsieffect@users.noreply.github.com> --- .../context-kit/data/context-kit-state.json | 45 +++++++++++++++++-- .../brain/context-kit/context-kit-state.json | 45 +++++++++++++++++-- 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/packages/context-kit/apps/context-kit/data/context-kit-state.json b/packages/context-kit/apps/context-kit/data/context-kit-state.json index 18d2c3d5..c582b88c 100644 --- a/packages/context-kit/apps/context-kit/data/context-kit-state.json +++ b/packages/context-kit/apps/context-kit/data/context-kit-state.json @@ -23,7 +23,7 @@ "id": "brand-context", "label": "Brand context", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Keeps every output unmistakably on-brand.", "preview": "", "dataFile": "brand-context.md" @@ -32,7 +32,7 @@ "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Every readout leads with the metric you care about.", "preview": "", "dataFile": "kpis-goal.md" @@ -41,7 +41,7 @@ "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Separates a proven winner from early noise.", "preview": "", "dataFile": "spend-threshold.md" @@ -145,6 +145,45 @@ "Any past ad types or messages to avoid repeating?" ] }, + { + "id": "media-buying", + "label": "Media buying model", + "bucket": "C", + "status": "missing", + "why": "Keeps creative recommendations aligned with how you actually buy.", + "dataFile": "media-buying.md", + "thoughtStarters": [ + "How do you primarily buy? (ABO, CBO, Advantage+)", + "What does a typical campaign structure look like?", + "Any budget rules or pacing preferences Runneth should know?" + ] + }, + { + "id": "business-team", + "label": "Business & team", + "bucket": "C", + "status": "missing", + "why": "Runneth works better knowing who is using it and how decisions get made.", + "dataFile": "business-team.md", + "thoughtStarters": [ + "Who reviews and approves creatives?", + "Who owns the brief-to-production pipeline?", + "Any team rules or review steps Runneth should respect?" + ] + }, + { + "id": "landing-pages", + "label": "Landing pages", + "bucket": "C", + "status": "missing", + "why": "Briefs land harder when Runneth knows what the ad is sending to.", + "dataFile": "landing-pages.md", + "thoughtStarters": [ + "What are your main landing page URLs or page types?", + "What is the primary CTA on your top pages?", + "Any pages that are off-limits for ads?" + ] + }, { "id": "integration-ad-platform", "label": "Ad platform", diff --git a/packages/context-kit/brain/context-kit/context-kit-state.json b/packages/context-kit/brain/context-kit/context-kit-state.json index 18d2c3d5..c582b88c 100644 --- a/packages/context-kit/brain/context-kit/context-kit-state.json +++ b/packages/context-kit/brain/context-kit/context-kit-state.json @@ -23,7 +23,7 @@ "id": "brand-context", "label": "Brand context", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Keeps every output unmistakably on-brand.", "preview": "", "dataFile": "brand-context.md" @@ -32,7 +32,7 @@ "id": "kpis-goal", "label": "KPIs & goal", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Every readout leads with the metric you care about.", "preview": "", "dataFile": "kpis-goal.md" @@ -41,7 +41,7 @@ "id": "spend-threshold", "label": "Spend threshold", "bucket": "A", - "status": "inferred", + "status": "missing", "why": "Separates a proven winner from early noise.", "preview": "", "dataFile": "spend-threshold.md" @@ -145,6 +145,45 @@ "Any past ad types or messages to avoid repeating?" ] }, + { + "id": "media-buying", + "label": "Media buying model", + "bucket": "C", + "status": "missing", + "why": "Keeps creative recommendations aligned with how you actually buy.", + "dataFile": "media-buying.md", + "thoughtStarters": [ + "How do you primarily buy? (ABO, CBO, Advantage+)", + "What does a typical campaign structure look like?", + "Any budget rules or pacing preferences Runneth should know?" + ] + }, + { + "id": "business-team", + "label": "Business & team", + "bucket": "C", + "status": "missing", + "why": "Runneth works better knowing who is using it and how decisions get made.", + "dataFile": "business-team.md", + "thoughtStarters": [ + "Who reviews and approves creatives?", + "Who owns the brief-to-production pipeline?", + "Any team rules or review steps Runneth should respect?" + ] + }, + { + "id": "landing-pages", + "label": "Landing pages", + "bucket": "C", + "status": "missing", + "why": "Briefs land harder when Runneth knows what the ad is sending to.", + "dataFile": "landing-pages.md", + "thoughtStarters": [ + "What are your main landing page URLs or page types?", + "What is the primary CTA on your top pages?", + "Any pages that are off-limits for ads?" + ] + }, { "id": "integration-ad-platform", "label": "Ad platform", From 5cc90472fe8170cb3bd4a1d4b47e6f141db5d994 Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Mon, 20 Jul 2026 12:42:06 +0000 Subject: [PATCH 20/25] fix(context-kit): add app manifest identity Co-authored-by: vamsi <thedumsieffect@users.noreply.github.com> --- packages/context-kit/README.md | 7 ++++--- packages/context-kit/apps/context-kit/buildeth.app.json | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/context-kit/README.md b/packages/context-kit/README.md index 9304fe47..e1dd96af 100644 --- a/packages/context-kit/README.md +++ b/packages/context-kit/README.md @@ -5,9 +5,10 @@ Builds the institutional knowledge that makes every Runneth answer sharper: brai ## App build gotchas (learned in staging — read before editing the app) -- **`buildeth.app.json` must be v3** with `conversationId`, `workspaceId`, `oauthEnabled: true`, - `data: { "dir": "data" }`, `static: { "dist": "dist", "index": "index.html" }`. It ships as a template - with `__CONVERSATION_ID__` / `__WORKSPACE_ID__` tokens the skill substitutes before `app build`. +- **`buildeth.app.json` must be v3** with `name: "context-kit"`, `route: "/context-kit"`, + `conversationId`, `workspaceId`, `oauthEnabled: true`, `data: { "dir": "data" }`, and + `static: { "dist": "dist", "index": "index.html" }`. It ships as a template with + `__CONVERSATION_ID__` / `__WORKSPACE_ID__` tokens the skill substitutes before `app build`. - **`astro.config.mjs` must set `base: "/context-kit"`** (and `trailingSlash: "never"`). Without a base, Astro emits absolute `/_astro/...` asset URLs and `app build` rejects the static output. - **Use `<style is:global>`.** Astro scopes `<style>` by default via a build-time `data-astro-cid` attribute diff --git a/packages/context-kit/apps/context-kit/buildeth.app.json b/packages/context-kit/apps/context-kit/buildeth.app.json index 13eae038..a061b113 100644 --- a/packages/context-kit/apps/context-kit/buildeth.app.json +++ b/packages/context-kit/apps/context-kit/buildeth.app.json @@ -1,5 +1,7 @@ { "version": 3, + "name": "context-kit", + "route": "/context-kit", "conversationId": "__CONVERSATION_ID__", "workspaceId": "__WORKSPACE_ID__", "oauthEnabled": true, From 4c41685ccc852af79139d871466aa6924c7c1da6 Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Mon, 20 Jul 2026 12:42:06 +0000 Subject: [PATCH 21/25] fix(context-kit): add app manifest identity Co-authored-by: vamsi <thedumsieffect@users.noreply.github.com> --- packages/context-kit/README.md | 7 ++++--- packages/context-kit/apps/context-kit/buildeth.app.json | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/context-kit/README.md b/packages/context-kit/README.md index 9304fe47..e1dd96af 100644 --- a/packages/context-kit/README.md +++ b/packages/context-kit/README.md @@ -5,9 +5,10 @@ Builds the institutional knowledge that makes every Runneth answer sharper: brai ## App build gotchas (learned in staging — read before editing the app) -- **`buildeth.app.json` must be v3** with `conversationId`, `workspaceId`, `oauthEnabled: true`, - `data: { "dir": "data" }`, `static: { "dist": "dist", "index": "index.html" }`. It ships as a template - with `__CONVERSATION_ID__` / `__WORKSPACE_ID__` tokens the skill substitutes before `app build`. +- **`buildeth.app.json` must be v3** with `name: "context-kit"`, `route: "/context-kit"`, + `conversationId`, `workspaceId`, `oauthEnabled: true`, `data: { "dir": "data" }`, and + `static: { "dist": "dist", "index": "index.html" }`. It ships as a template with + `__CONVERSATION_ID__` / `__WORKSPACE_ID__` tokens the skill substitutes before `app build`. - **`astro.config.mjs` must set `base: "/context-kit"`** (and `trailingSlash: "never"`). Without a base, Astro emits absolute `/_astro/...` asset URLs and `app build` rejects the static output. - **Use `<style is:global>`.** Astro scopes `<style>` by default via a build-time `data-astro-cid` attribute diff --git a/packages/context-kit/apps/context-kit/buildeth.app.json b/packages/context-kit/apps/context-kit/buildeth.app.json index 13eae038..a061b113 100644 --- a/packages/context-kit/apps/context-kit/buildeth.app.json +++ b/packages/context-kit/apps/context-kit/buildeth.app.json @@ -1,5 +1,7 @@ { "version": 3, + "name": "context-kit", + "route": "/context-kit", "conversationId": "__CONVERSATION_ID__", "workspaceId": "__WORKSPACE_ID__", "oauthEnabled": true, From ff588305b0892e8946590de400be83bf8c8ace83 Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Mon, 20 Jul 2026 12:55:09 +0000 Subject: [PATCH 22/25] fix(context-kit): keep Motion refresh in agent turns Co-authored-by: vamsi <thedumsieffect@users.noreply.github.com> --- package-index.json | 2 +- packages/context-kit/README.md | 2 + packages/context-kit/instructions/behavior.md | 3 +- packages/context-kit/package.json | 9 +- packages/context-kit/runneth-package.json | 12 +- packages/context-kit/skills/SKILL.md | 48 +++- .../workflows/context-kit-refresh.ts | 272 ------------------ 7 files changed, 44 insertions(+), 304 deletions(-) delete mode 100644 packages/context-kit/workflows/context-kit-refresh.ts diff --git a/package-index.json b/package-index.json index 6c470f09..417f4f92 100644 --- a/package-index.json +++ b/package-index.json @@ -6,7 +6,7 @@ "id": "context-kit", "name": "Context Kit", "description": "Builds the brand knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill. Install first, then ad-naming for naming conventions and query contract, then creative-corpus for the full creative library.", - "version": "1.0.0", + "version": "1.0.1", "categories": ["baseline"], "packageManagerVersion": 1, "source": { diff --git a/packages/context-kit/README.md b/packages/context-kit/README.md index e1dd96af..5e992bef 100644 --- a/packages/context-kit/README.md +++ b/packages/context-kit/README.md @@ -18,6 +18,8 @@ Builds the institutional knowledge that makes every Runneth answer sharper: brai `/context-kit/data/context-kit-state.json` and the Bucket B `data/*.md` files on load, so brain updates show on refresh with no rebuild. Never go back to a static `import` of the state JSON. - **Package sync does not run `app build`.** The skill (or an operator) builds on first run. +- **Motion calls stay in agent turns.** Task-scoped bash cannot access trusted Motion + tools, so refresh routines prompt an agent to run the skill directly. ## Manifest note diff --git a/packages/context-kit/instructions/behavior.md b/packages/context-kit/instructions/behavior.md index 42b9106a..55b4ca42 100644 --- a/packages/context-kit/instructions/behavior.md +++ b/packages/context-kit/instructions/behavior.md @@ -22,7 +22,8 @@ If **ad-naming** is installed, it registers its own `ad-naming` lane covering th - Board is client-rendered: fetches `data/context-kit-state.json` and `data/*.md` at runtime. Rebuild only needed for source changes, not content. - State: `/agent/brain/context-kit/context-kit-state.json` (source of truth), mirrored to `data/context-kit-state.json`. - Lanes: registered by the skill on first run. Check `lanesRegistered` in state. -- Refresh workflow: registered by the skill on first run. Check `refreshWorkflowId` in state. +- Refresh: performed directly in an agent turn so trusted Motion tools are available. + Do not call Motion from `task.bash` or a script-mode routine. ## Self-improvement loop (always on) diff --git a/packages/context-kit/package.json b/packages/context-kit/package.json index dc07ff64..b40dbe68 100644 --- a/packages/context-kit/package.json +++ b/packages/context-kit/package.json @@ -3,7 +3,7 @@ "id": "context-kit", "name": "Context Kit", "description": "Builds the brand knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill. Install first, then ad-naming for naming conventions and query contract, then creative-corpus for the full creative library.", - "version": "1.0.0", + "version": "1.0.1", "installPolicy": "manual", "updatePolicy": "manual", "uninstallPolicy": "allowed", @@ -69,13 +69,6 @@ "target": { "root": "agent_brain", "path": "context-kit/landing-pages.md" }, "executable": false }, - { - "id": "context-kit-refresh-workflow", - "type": "file", - "sourcePath": "workflows/context-kit-refresh.ts", - "target": { "root": "agent_brain", "path": "context-kit/workflows/context-kit-refresh.ts" }, - "executable": false - }, { "id": "context-kit-skill", "type": "directory", diff --git a/packages/context-kit/runneth-package.json b/packages/context-kit/runneth-package.json index bf56f11b..6bff8895 100644 --- a/packages/context-kit/runneth-package.json +++ b/packages/context-kit/runneth-package.json @@ -3,7 +3,7 @@ "id": "context-kit", "name": "Context Kit", "description": "Builds the brand knowledge that makes every Runneth answer sharper. Seeds a brain skeleton, a Context Kit board app, and the Context Kit skill. Install first, then ad-naming for naming conventions and query contract, then creative-corpus for the full creative library.", - "version": "1.0.0", + "version": "1.0.1", "updatePolicy": "manual", "uninstallPolicy": "allowed", "resources": [ @@ -92,16 +92,6 @@ }, "executable": false }, - { - "id": "context-kit-refresh-workflow", - "type": "file", - "sourcePath": "workflows/context-kit-refresh.ts", - "target": { - "root": "agent_brain", - "path": "context-kit/workflows/context-kit-refresh.ts" - }, - "executable": false - }, { "id": "context-kit-skill", "type": "directory", diff --git a/packages/context-kit/skills/SKILL.md b/packages/context-kit/skills/SKILL.md index 5bc822e5..48a08c93 100644 --- a/packages/context-kit/skills/SKILL.md +++ b/packages/context-kit/skills/SKILL.md @@ -42,14 +42,11 @@ If `context-kit-state.json` shows `lanesRegistered: false` or the field is absen Set `lanesRegistered: true` in state. Do not re-register on subsequent runs. -## Step 0c — Register the refresh workflow (first run only) +## Step 0c — Keep Motion work in the agent turn -If `context-kit-state.json` shows `refreshWorkflowId` absent: -1. Read `/agent/brain/context-kit/workflows/context-kit-refresh.ts`. -2. Run `workflow push /agent/brain/context-kit/workflows/context-kit-refresh.ts --name context-kit-refresh`. -3. Save the workflow ID to state as `refreshWorkflowId`. -4. `task add --kind workflow --workflow-id <id> --name "Context Kit Refresh"` → save as `refreshTaskId`. -5. Mirror state. +Run every `motion` command directly in this agent turn. Do not put Motion calls in +`task.bash` or script-mode routines: task-scoped broker tokens cannot access the +trusted Motion tool. Deterministic local file processing may use bash. ## Step 1 — Build and open the board (first run only) @@ -73,7 +70,34 @@ Import confirmed docs, mirror, mark `imported`. Pull the ground-truth spine once: 1. `motion ai-glossary` -2. `motion meta insights --include-glossary --date-range last_30d --sort topSpend` — read tag distribution from `creatives[].glossaryTags[]` +2. Run: + ``` + motion meta insights \ + --date-range last_30d \ + --sort topSpend \ + --glossary-category intended-audience \ + --glossary-category messaging-angle \ + --glossary-category hook-tactic \ + --glossary-category visual-format \ + --glossary-category asset-type \ + --glossary-category offer-type \ + --glossary-category seasonality + ``` + Read the returned category data for each creative. +3. For VoC, take up to 20 top-spend creative asset IDs and enrich them in batches of + no more than 15: + ``` + motion meta insights \ + --scope creative-asset-id \ + --creative-asset-id <id> \ + --date-range last_365d \ + --summary-sections hookOrHeadline \ + --summary-sections creativeBreakdown \ + --summary-sections messagingAndPositioning \ + --summary-sections emotionalAndAudienceInsight \ + --summary-sections adDescription + ``` + Repeat `--creative-asset-id` for each ID in the batch. Category-to-item mapping: - `intended-audience` → positioning + voc @@ -84,7 +108,7 @@ Category-to-item mapping: - `seasonality` → products + competitors Per-item fallback chain (explicit, sequential): -1. Motion glossary spine + summaries + transcripts → draft, status `drafted`. +1. Motion glossary spine + summary sections → draft, status `drafted`. 2. Motion empty → Drive/Notion if connected → status `imported`. 3. Neither → general brand knowledge → status `inferred` + `sourceNote`. 4. Still unreliable → leave `missing`, show thought starters. @@ -92,7 +116,9 @@ Per-item fallback chain (explicit, sequential): After each: mirror to `data/<id>.md`, tell the user what was drafted. **voice**: 4-6 named characteristics with sounds-like/doesn't-sound-like pairs. -**voc**: 7-category swipe file (pain, emotional language, desire, before/after, objections, competitor complaints, trigger events), near-verbatim from transcripts. +**voc**: 7-category swipe file (pain, emotional language, desire, before/after, +objections, competitor complaints, trigger events). Preserve exact customer-facing +language when present in summary sections; do not label generated prose as a transcript. ## Step 5 — Bucket C: collect with depth @@ -112,7 +138,7 @@ routine add \ --name "Context Kit weekly refresh" \ --cron "0 9 * * 1" \ --delivery "Send a summary in a new web conversation." \ - --prompt "Run the context-kit refresh task (task id: <refreshTaskId from state>). Wait for completion with task wait. Read the returned JSON. Open a new conversation summarising what changed — highlight shifts in voice, voc, or competitors. Flag items not updated in 3+ weeks as stale. Deliver with conversation send --new." + --prompt "Start an agent turn and read the installed context-kit skill. Refresh Context Kit directly in that agent turn using its current Motion commands; never call Motion from task.bash. Preserve every Runneth Instructions section, update only Motion-derived content, mirror changed files and state, and open a new conversation summarising shifts in voice, VoC, or competitors plus anything stale for 3+ weeks." ``` Save routine ID to state as `refreshRoutineId`. diff --git a/packages/context-kit/workflows/context-kit-refresh.ts b/packages/context-kit/workflows/context-kit-refresh.ts deleted file mode 100644 index 95a08aed..00000000 --- a/packages/context-kit/workflows/context-kit-refresh.ts +++ /dev/null @@ -1,272 +0,0 @@ -import type { Workflow } from "@runneth/workflow/v1"; - -type RefreshOutput = { - changed: string[]; - unchanged: string[]; - stale: string[]; - highlights: string[]; - lastRefreshed: string; - itemsUpdated: number; -}; - -/** - * Context Kit Brand Refresh Workflow - * - * Re-pulls all Motion-sourced brand data for Bucket A and Bucket B items. - * Does NOT touch naming decoder, KPI map, or query contract — those are - * owned by the ad-naming package and its own refresh workflow. - * - * Writes ONLY "## Latest Import From Motion" sections. - * Preserves "## Runneth Instructions" exactly. - * - * Motion CLI pattern for bash tasks: - * ENVELOPE=$(motion ...) # capture envelope to var - * FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') # parse file path - * jq '...' "$FILE" # read actual data - * - * Agent tasks write structured output to /tmp/ck-results/*.json. - * Bash tasks read those files. No stdout-parsing of agent responses. - */ -export const wf: Workflow<Record<string, never>, RefreshOutput> = async ({ - task, -}) => { - // ─── Step 1: Pull Motion data via bash ─────────────────────────────────── - - await task.bash({ - key: "pull-workspace-data", - script: ` - set -euo pipefail - mkdir -p /tmp/ck-refresh - - ENVELOPE=$(motion workspace-goal) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - cp "$FILE" /tmp/ck-refresh/workspace-goal.json - - ENVELOPE=$(motion spend-threshold) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - cp "$FILE" /tmp/ck-refresh/spend-threshold.json - - ENVELOPE=$(motion brand-context --data-query "summary") - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - cp "$FILE" /tmp/ck-refresh/brand-context.json - - echo "workspace-data-done" - `, - }); - - await task.bash({ - key: "pull-glossary-spine", - script: ` - set -euo pipefail - - ENVELOPE=$(motion ai-glossary) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - cp "$FILE" /tmp/ck-refresh/glossary.json - - ENVELOPE=$(motion meta insights --include-glossary --date-range last_30d --sort topSpend --limit 100) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - cp "$FILE" /tmp/ck-refresh/insights-glossary.json - - echo "glossary-done" - `, - }); - - await task.bash({ - key: "pull-inspo-brands", - script: ` - set -euo pipefail - - ENVELOPE=$(motion inspo brands --limit 20) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - cp "$FILE" /tmp/ck-refresh/inspo-brands.json - - echo "inspo-done" - `, - }); - - await task.bash({ - key: "pull-voc-transcripts", - script: ` - set -euo pipefail - - ENVELOPE=$(motion meta insights --include-transcript --date-range last_30d --sort topSpend --limit 20) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - cp "$FILE" /tmp/ck-refresh/transcripts.json - - echo "voc-done" - `, - }); - - // ─── Step 2: Agent re-drafts each item ─────────────────────────────────── - // Each agent writes a result JSON to /tmp/ck-results/<item>.json. - // Format: {"changed": true/false, "preview": "one short sentence"} - - await task.bash({ - key: "setup-results-dir", - script: `mkdir -p /tmp/ck-results`, - }); - - await task.agent({ - key: "update-brand-context", - prompt: ` - Update /agent/brain/context-kit/brand-context.md from refreshed Motion data. - - Source files: /tmp/ck-refresh/brand-context.json and /tmp/ck-refresh/insights-glossary.json - (read .creatives[].glossaryTags[] from insights-glossary for tag distribution). - - Rules: - - Read the current file first. Preserve any "## Runneth Instructions" section exactly. - - Rewrite ONLY "## Latest Import From Motion": brand name, positioning, product description, - proof points from top-spend tags, 2-sentence tone, 2-sentence audience. - - Write back to /agent/brain/context-kit/brand-context.md. - - Mirror to /agent/apps/context-kit/data/brand-context.md. - - Write {"changed": true/false, "preview": "one short sentence"} to /tmp/ck-results/brand-context.json. - `, - }); - - await task.agent({ - key: "update-kpis-goal", - prompt: ` - Update /agent/brain/context-kit/kpis-goal.md from refreshed Motion data. - - Source: /tmp/ck-refresh/workspace-goal.json (primary KPI and attribution windows). - - Note: the per-campaign KPI map is owned by the ad-naming package. - Only update the primary KPI, conversion event, and attribution window sections here. - - Rules: - - Preserve "## Runneth Instructions" exactly. - - Rewrite only "## Latest Import From Motion". - - Write back and mirror to data/kpis-goal.md. - - Write {"changed": true/false, "preview": "..."} to /tmp/ck-results/kpis-goal.json. - `, - }); - - await task.agent({ - key: "update-spend-threshold", - prompt: ` - Update /agent/brain/context-kit/spend-threshold.md from /tmp/ck-refresh/spend-threshold.json. - Preserve "## Runneth Instructions". Rewrite only "## Latest Import From Motion". - Write back, mirror, write {"changed": true/false, "preview": "..."} to /tmp/ck-results/spend-threshold.json. - `, - }); - - await task.agent({ - key: "update-voice", - prompt: ` - Update /agent/brain/context-kit/voice.md from /tmp/ck-refresh/insights-glossary.json. - Read .creatives[].glossaryTags[] — focus on visual-format, asset-type, hook-tactic, messaging-angle by spend. - Derive 4-6 named voice characteristics (sounds-like / doesn't-sound-like pairs). - Preserve "## Runneth Instructions". Rewrite only "## Latest Import From Motion". - If Motion data is empty: status = "inferred", add sourceNote. - Write back, mirror, write {"changed": true/false, "preview": "..."} to /tmp/ck-results/voice.json. - `, - }); - - await task.agent({ - key: "update-voc", - prompt: ` - Update /agent/brain/context-kit/voc.md from /tmp/ck-refresh/transcripts.json. - Read .creatives[].transcript for top-spend videos. - Build or refresh the 7-category swipe file with near-verbatim language: - pain, emotional language, desire, before/after, objections, competitor complaints, trigger events. - Preserve "## Runneth Instructions". Rewrite only "## Latest Import From Motion". - If transcripts are empty, note it clearly. - Write back, mirror, write {"changed": true/false, "preview": "..."} to /tmp/ck-results/voc.json. - `, - }); - - await task.agent({ - key: "update-competitors", - prompt: ` - Update /agent/brain/context-kit/competitors.md from /tmp/ck-refresh/inspo-brands.json. - Read .data.brands[] for the customer's followed brands. - Note name, Motion brand ID, active ad count signal for each. - Flag brands with substantially more or fewer ads than the current file shows. - Preserve "## Runneth Instructions". Rewrite only "## Latest Import From Motion". - Write back, mirror, write {"changed": true/false, "preview": "..."} to /tmp/ck-results/competitors.json. - `, - }); - - // ─── Step 3: Collect results + check staleness ──────────────────────────── - - const collectResult = await task.bash({ - key: "collect-results", - script: ` - set -euo pipefail - - node -e " - const fs = require('fs'); - const items = ['brand-context', 'kpis-goal', 'spend-threshold', 'voice', 'voc', 'competitors']; - const changed = []; - const unchanged = []; - const highlights = []; - - for (const item of items) { - try { - const result = JSON.parse(fs.readFileSync('/tmp/ck-results/' + item + '.json', 'utf8')); - if (result.changed) { - changed.push(item); - if (result.preview) highlights.push(item + ': ' + result.preview); - } else { - unchanged.push(item); - } - } catch { - unchanged.push(item); - } - } - - const stale = []; - try { - const state = JSON.parse(fs.readFileSync('/agent/brain/context-kit/context-kit-state.json', 'utf8')); - const cutoff = Date.now() - (21 * 86400 * 1000); - for (const [id, item] of Object.entries(state.items || {})) { - if (item.updatedAt && new Date(item.updatedAt).getTime() < cutoff && item.status !== 'missing') { - stale.push(id); - } - } - } catch {} - - const today = new Date().toISOString().slice(0, 10); - console.log(JSON.stringify({ changed, unchanged, stale, highlights, lastRefreshed: today, itemsUpdated: changed.length })); - " - `, - }); - - // ─── Step 4: Update state ───────────────────────────────────────────────── - - await task.bash({ - key: "update-state", - script: ` - set -euo pipefail - TODAY=$(node -e "console.log(new Date().toISOString().slice(0,10))") - - node -e " - const fs = require('fs'); - let state = {}; - try { state = JSON.parse(fs.readFileSync('/agent/brain/context-kit/context-kit-state.json', 'utf8')); } catch {} - state.lastRefreshed = '$TODAY'; - const out = JSON.stringify(state, null, 2); - fs.writeFileSync('/agent/brain/context-kit/context-kit-state.json', out); - fs.writeFileSync('/agent/apps/context-kit/data/context-kit-state.json', out); - " - `, - }); - - let output: RefreshOutput = { - changed: [], - unchanged: [], - stale: [], - highlights: [], - lastRefreshed: new Date().toISOString().slice(0, 10), - itemsUpdated: 0, - }; - - try { - output = JSON.parse(collectResult.stdout.trim()); - } catch { - // Return partial result - } - - return output; -}; From dbd1b7b39478aca9e685e8275b2ddacd587af42a Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Mon, 20 Jul 2026 12:56:42 +0000 Subject: [PATCH 23/25] fix(ad-naming): keep Motion refresh in agent turns Co-authored-by: vamsi <thedumsieffect@users.noreply.github.com> --- package-index.json | 4 +- .../brain/ad-naming/ad-naming-state.json | 2 - packages/ad-naming/instructions/behavior.md | 6 +- packages/ad-naming/package.json | 9 +- packages/ad-naming/runneth-package.json | 9 +- packages/ad-naming/skills/SKILL.md | 17 +- .../ad-naming/workflows/ad-naming-refresh.ts | 224 ------------------ 7 files changed, 16 insertions(+), 255 deletions(-) delete mode 100644 packages/ad-naming/workflows/ad-naming-refresh.ts diff --git a/package-index.json b/package-index.json index 417f4f92..12702180 100644 --- a/package-index.json +++ b/package-index.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "indexRevision": "ad-naming-0.1.0", + "indexRevision": "ad-naming-0.1.1", "packages": [ { "id": "context-kit", @@ -23,7 +23,7 @@ "id": "ad-naming", "name": "Ad Naming", "description": "Builds the account intelligence layer that powers precise analysis. Decodes the ad naming convention into a reusable taxonomy, maps per-campaign KPI targets, and auto-discovers the Motion query contract (workspace ID, attribution windows, conversion events, metric gotchas). Install after Context Kit. Required before Creative Corpus for decoded naming in corpus files.", - "version": "0.1.0", + "version": "0.1.1", "categories": ["creative-strategy"], "packageManagerVersion": 1, "source": { diff --git a/packages/ad-naming/brain/ad-naming/ad-naming-state.json b/packages/ad-naming/brain/ad-naming/ad-naming-state.json index 1cc3ab43..b270eae1 100644 --- a/packages/ad-naming/brain/ad-naming/ad-naming-state.json +++ b/packages/ad-naming/brain/ad-naming/ad-naming-state.json @@ -5,7 +5,5 @@ "lastBuildDate": null, "lastRefreshed": null, "lanesRegistered": false, - "refreshWorkflowId": null, - "refreshTaskId": null, "refreshRoutineId": null } diff --git a/packages/ad-naming/instructions/behavior.md b/packages/ad-naming/instructions/behavior.md index 2097b43d..3acce379 100644 --- a/packages/ad-naming/instructions/behavior.md +++ b/packages/ad-naming/instructions/behavior.md @@ -26,9 +26,11 @@ The skill registers this lane on first run. Check `lanesRegistered` in `/agent/b The `ad-naming` lane injects these automatically. If a file is still empty, say so rather than inferring. -## Refresh workflow +## Refresh behavior -Registered by the skill on first run. Refreshes all three files from live Motion data on demand or on the weekly schedule offered after first build. Saves `refreshWorkflowId` and `refreshTaskId` in state. +Refresh all three files directly in an agent turn so trusted Motion tools are +available. Do not call Motion from `task.bash` or a script-mode routine. The weekly +agent-mode routine is offered after first build and its ID is saved in state. ## Works independently diff --git a/packages/ad-naming/package.json b/packages/ad-naming/package.json index 37eb7201..564d1dd4 100644 --- a/packages/ad-naming/package.json +++ b/packages/ad-naming/package.json @@ -3,7 +3,7 @@ "id": "ad-naming", "name": "Ad Naming", "description": "Builds the account intelligence layer that powers precise analysis. Decodes the ad naming convention into a reusable taxonomy, maps per-campaign KPI targets, and auto-discovers the Motion query contract (workspace ID, attribution windows, conversion events, metric gotchas). Install after Context Kit. Required before Creative Corpus for decoded naming in corpus files.", - "version": "0.1.0", + "version": "0.1.1", "installPolicy": "manual", "updatePolicy": "manual", "uninstallPolicy": "allowed", @@ -41,13 +41,6 @@ "target": { "root": "agent_brain", "path": "ad-naming/query-contract.md" }, "executable": false }, - { - "id": "ad-naming-refresh-workflow", - "type": "file", - "sourcePath": "workflows/ad-naming-refresh.ts", - "target": { "root": "agent_brain", "path": "ad-naming/workflows/ad-naming-refresh.ts" }, - "executable": false - }, { "id": "ad-naming-skill", "type": "directory", diff --git a/packages/ad-naming/runneth-package.json b/packages/ad-naming/runneth-package.json index 96d9577f..f96e3e79 100644 --- a/packages/ad-naming/runneth-package.json +++ b/packages/ad-naming/runneth-package.json @@ -3,7 +3,7 @@ "id": "ad-naming", "name": "Ad Naming", "description": "Builds the account intelligence layer that powers precise analysis. Decodes the ad naming convention into a reusable taxonomy, maps per-campaign KPI targets, and auto-discovers the Motion query contract (workspace ID, attribution windows, conversion events, metric gotchas). Install after Context Kit. Required before Creative Corpus for decoded naming in corpus files.", - "version": "0.1.0", + "version": "0.1.1", "updatePolicy": "manual", "uninstallPolicy": "allowed", "resources": [ @@ -40,13 +40,6 @@ "target": { "root": "agent_brain", "path": "ad-naming/query-contract.md" }, "executable": false }, - { - "id": "ad-naming-refresh-workflow", - "type": "file", - "sourcePath": "workflows/ad-naming-refresh.ts", - "target": { "root": "agent_brain", "path": "ad-naming/workflows/ad-naming-refresh.ts" }, - "executable": false - }, { "id": "ad-naming-skill", "type": "directory", diff --git a/packages/ad-naming/skills/SKILL.md b/packages/ad-naming/skills/SKILL.md index d1d7381c..afb2f38c 100644 --- a/packages/ad-naming/skills/SKILL.md +++ b/packages/ad-naming/skills/SKILL.md @@ -17,14 +17,11 @@ If `lanesRegistered` is absent or false: 1. Call ContextConfig to register the `ad-naming` lane at `/agent/brain/ad-naming/` covering `naming-decoder.md`, `kpi-map.md`, `query-contract.md`. 2. Set `lanesRegistered: true` in state. -## Step 0c — Register the refresh workflow (first run only) +## Step 0c — Keep Motion work in the agent turn -If `refreshWorkflowId` is absent: -1. Read `/agent/brain/ad-naming/workflows/ad-naming-refresh.ts`. -2. `workflow push /agent/brain/ad-naming/workflows/ad-naming-refresh.ts --name ad-naming-refresh` -3. Save workflow ID as `refreshWorkflowId`. -4. `task add --kind workflow --workflow-id <id> --name "Ad Naming Refresh"` → save as `refreshTaskId`. -5. Mirror state. +Run every `motion` command directly in this agent turn. Do not put Motion calls in +`task.bash` or script-mode routines: task-scoped broker tokens cannot access the +trusted Motion tool. Deterministic local file processing may use bash. ## Step 1 — Build the naming decoder @@ -105,7 +102,8 @@ Auto-discover and write the account's Motion CLI contract: - `campaignName` is null in `--grain ads` → use `--grain adnames` or insights for campaign reads - `adType` may be null in ads grain → use `format` field instead - `roas` varies by account configuration → check workspace goal for the actual judgment metric - - `last_365d` with `--include-transcript` times out → use 30-day windows + - For creative text, use ID-scoped `--summary-sections` pulls; do not use the + blocked `--include-transcript` fast path 7. Write `/agent/brain/ad-naming/query-contract.md`: ```markdown @@ -140,8 +138,9 @@ Auto-discover and write the account's Motion CLI contract: --name "Ad naming weekly refresh" \ --cron "0 9 * * 1" \ --delivery "Post a summary in a new web conversation." \ - --prompt "Run the ad-naming refresh task (task id: <refreshTaskId from state>). Wait for completion. Send a brief summary of what changed with conversation send --new." + --prompt "Start an agent turn and read the installed ad-naming skill. Refresh the naming decoder, KPI map, and query contract directly in that agent turn using live Motion tools; never call Motion from task.bash. Preserve customer corrections, update state, and open a new conversation with a brief summary of what changed." ``` +4. Save the routine ID as `refreshRoutineId`. ## Rules - Naming decoder built from real Motion data → status `drafted`. diff --git a/packages/ad-naming/workflows/ad-naming-refresh.ts b/packages/ad-naming/workflows/ad-naming-refresh.ts deleted file mode 100644 index 5b126cbb..00000000 --- a/packages/ad-naming/workflows/ad-naming-refresh.ts +++ /dev/null @@ -1,224 +0,0 @@ -import type { Workflow } from "@runneth/workflow/v1"; - -type AdNamingRefreshOutput = { - decoderChanged: boolean; - kpiMapChanged: boolean; - queryContractChanged: boolean; - highlights: string[]; - lastRefreshed: string; -}; - -/** - * Ad Naming Refresh Workflow - * - * Refreshes all three ad-naming brain files from live Motion data: - * - naming-decoder.md (from fresh ad names sample) - * - kpi-map.md (from naming decoder + workspace goal + custom conversions) - * - query-contract.md (from workspace goal + custom conversions + thumbstop test) - * - * Motion CLI pattern for bash tasks: - * ENVELOPE=$(motion ...) - * FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - * jq '...' "$FILE" - * - * Agent tasks write results to /tmp/an-results/*.json. - * Bash tasks read those files. No stdout-parsing of agent responses. - */ -export const wf: Workflow<Record<string, never>, AdNamingRefreshOutput> = async ({ - task, -}) => { - // ─── Step 1: Pull fresh data via bash ──────────────────────────────────── - - await task.bash({ - key: "pull-adnames-sample", - script: ` - set -euo pipefail - mkdir -p /tmp/an-refresh /tmp/an-results - - ENVELOPE=$(motion meta ads --grain adnames --date-range last_90d --sort-by spend --sort-direction desc --limit 200) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - cp "$FILE" /tmp/an-refresh/adnames.json - - echo "adnames-done: $(jq '.data.summaryRows | length' /tmp/an-refresh/adnames.json) rows" - `, - }); - - await task.bash({ - key: "pull-workspace-context", - script: ` - set -euo pipefail - - ENVELOPE=$(motion workspace-goal) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - cp "$FILE" /tmp/an-refresh/workspace-goal.json - - ENVELOPE=$(motion meta custom-conversion-metrics) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - cp "$FILE" /tmp/an-refresh/custom-conversions.json - - echo "workspace-context-done" - `, - }); - - await task.bash({ - key: "pull-campaign-sample", - script: ` - set -euo pipefail - - ENVELOPE=$(motion meta insights --date-range last_30d --sort topSpend --limit 100) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - # Extract just campaign names for KPI map refresh - jq '[.creatives[] | .campaignName] | unique | map(select(. != null))' "$FILE" > /tmp/an-refresh/campaign-names.json - - echo "campaign-names-done: $(jq length /tmp/an-refresh/campaign-names.json) unique names" - `, - }); - - await task.bash({ - key: "test-thumbstop", - script: ` - set -euo pipefail - - ENVELOPE=$(motion meta insights --date-range last_30d --sort topSpend --limit 1 --table-kpi thumbstop_rate) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - - # Check if thumbstop_rate returns a non-null value - THUMBSTOP=$(jq -r '.creatives[0].tableKpiMetrics.thumbstop_rate.value // "null"' "$FILE" 2>/dev/null || echo "null") - echo "$THUMBSTOP" > /tmp/an-refresh/thumbstop-available.txt - - echo "thumbstop-test-done: $THUMBSTOP" - `, - }); - - // ─── Step 2: Agent refreshes each file ─────────────────────────────────── - - await task.agent({ - key: "refresh-naming-decoder", - prompt: ` - Refresh /agent/brain/ad-naming/naming-decoder.md from a fresh ad names sample. - - Source: /tmp/an-refresh/adnames.json - Read ad names from .data.summaryRows[].adName or .data.result.adnames[].adName. - - If naming-decoder.md already exists: - - Check for new dimensions or values not in the current decoder. - - If new: add them, note what changed. - - If no change: write {"changed": false, "preview": "No new naming dimensions detected"} to /tmp/an-results/naming-decoder.json and stop. - - If naming-decoder.md does not exist: - - Parse repeating delimited patterns from ad names. - - Write a full decoder: system name, dimension table (prefix, full name, observed values with plain meanings), decode template, worked example. - - Status: "drafted" - - Write back to /agent/brain/ad-naming/naming-decoder.md. - Write {"changed": true/false, "preview": "short description of what changed"} to /tmp/an-results/naming-decoder.json. - `, - }); - - await task.agent({ - key: "refresh-kpi-map", - prompt: ` - Refresh /agent/brain/ad-naming/kpi-map.md. - - Sources: - - /agent/brain/ad-naming/naming-decoder.md (campaign dimension if present) - - /tmp/an-refresh/workspace-goal.json (primary KPI, conversion event, attribution windows) - - /tmp/an-refresh/custom-conversions.json (conversion event IDs and names, at .data.customConversions[]) - - /tmp/an-refresh/campaign-names.json (unique campaign names from last 30 days) - - Build or update the per-campaign KPI map table. - For each campaign segment (derived from naming decoder campaign dimension, or campaign name patterns): - - Optimization target (from naming + workspace goal) - - Primary conversion event (from custom-conversions matched to campaign purpose) - - Testing cut and graduation thresholds (from workspace goal or standard rules) - - If no clear segments: write a single row for the primary optimization target. - - Write back to /agent/brain/ad-naming/kpi-map.md. - Write {"changed": true/false, "preview": "..."} to /tmp/an-results/kpi-map.json. - `, - }); - - await task.agent({ - key: "refresh-query-contract", - prompt: ` - Refresh /agent/brain/ad-naming/query-contract.md. - - Sources: - - /tmp/an-refresh/workspace-goal.json (attribution windows at .data.conversionDetails[].attributionWindow) - - /tmp/an-refresh/custom-conversions.json (conversion events at .data.customConversions[]) - - /tmp/an-refresh/thumbstop-available.txt (contains "null" or a numeric value) - - Update the contract sections: - 1. Attribution windows: read click and view values from workspace-goal. - 2. Conversion events: list each conversion's name, id, _count key, _cost key. - 3. Thumbstop: read thumbstop-available.txt — if not "null", thumbstop_rate is available; otherwise note it returns null. - 4. Data-layer gotchas: keep existing static rules, do not remove them. - - If query-contract.md does not exist, create it from scratch with all sections. - - Write back to /agent/brain/ad-naming/query-contract.md. - Write {"changed": true/false, "preview": "..."} to /tmp/an-results/query-contract.json. - `, - }); - - // ─── Step 3: Collect results + update state ─────────────────────────────── - - const collectResult = await task.bash({ - key: "collect-and-update-state", - script: ` - set -euo pipefail - TODAY=$(node -e "console.log(new Date().toISOString().slice(0,10))") - - node -e " - const fs = require('fs'); - - function readResult(item) { - try { return JSON.parse(fs.readFileSync('/tmp/an-results/' + item + '.json', 'utf8')); } - catch { return { changed: false, preview: 'result file not found' }; } - } - - const decoder = readResult('naming-decoder'); - const kpiMap = readResult('kpi-map'); - const queryContract = readResult('query-contract'); - - const highlights = []; - if (decoder.changed && decoder.preview) highlights.push('naming-decoder: ' + decoder.preview); - if (kpiMap.changed && kpiMap.preview) highlights.push('kpi-map: ' + kpiMap.preview); - if (queryContract.changed && queryContract.preview) highlights.push('query-contract: ' + queryContract.preview); - - // Update state - let state = {}; - try { state = JSON.parse(fs.readFileSync('/agent/brain/ad-naming/ad-naming-state.json', 'utf8')); } catch {} - state.lastRefreshed = '$TODAY'; - if (!state.lastBuildDate) state.lastBuildDate = '$TODAY'; - fs.writeFileSync('/agent/brain/ad-naming/ad-naming-state.json', JSON.stringify(state, null, 2)); - - const output = { - decoderChanged: decoder.changed, - kpiMapChanged: kpiMap.changed, - queryContractChanged: queryContract.changed, - highlights, - lastRefreshed: '$TODAY', - }; - console.log(JSON.stringify(output)); - " - `, - }); - - let output: AdNamingRefreshOutput = { - decoderChanged: false, - kpiMapChanged: false, - queryContractChanged: false, - highlights: [], - lastRefreshed: new Date().toISOString().slice(0, 10), - }; - - try { - output = JSON.parse(collectResult.stdout.trim()); - } catch { - // Return partial result - } - - return output; -}; From fbecbcf7f8fa44e2e20a8a375573ad3ef5dcf6a5 Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Mon, 20 Jul 2026 13:01:11 +0000 Subject: [PATCH 24/25] fix(creative-corpus): use agent-mode full-ID builds Co-authored-by: vamsi <thedumsieffect@users.noreply.github.com> --- docs/REPOSITORY_AND_CLI_GUIDE.md | 100 ++--- package-index.json | 6 +- packages/creative-corpus/README.md | 22 +- .../brain/meta/corpus-state.json | 7 +- .../brain/meta/creatives/PLAYBOOK.md | 68 ++-- .../creative-corpus/instructions/behavior.md | 32 +- packages/creative-corpus/package.json | 18 +- packages/creative-corpus/runneth-package.json | 18 +- .../scripts/corpus-refresh.mjs | 235 ------------ packages/creative-corpus/skills/SKILL.md | 82 +++- .../creative-corpus/workflows/corpus-build.ts | 362 ------------------ 11 files changed, 202 insertions(+), 748 deletions(-) delete mode 100644 packages/creative-corpus/scripts/corpus-refresh.mjs delete mode 100644 packages/creative-corpus/workflows/corpus-build.ts diff --git a/docs/REPOSITORY_AND_CLI_GUIDE.md b/docs/REPOSITORY_AND_CLI_GUIDE.md index e4136a33..6241aa81 100644 --- a/docs/REPOSITORY_AND_CLI_GUIDE.md +++ b/docs/REPOSITORY_AND_CLI_GUIDE.md @@ -30,9 +30,9 @@ The repository has two independent distribution systems. The checked-out cumulative feature branch contains three package candidates: -1. `context-kit`: brand knowledge, a board app, skill, and refresh workflow. -2. `ad-naming`: naming decoder, KPI map, query contract, skill, and refresh workflow. -3. `creative-corpus`: durable per-creative files, build workflow, and daily refresh script. +1. `context-kit`: brand knowledge, a board app, and an agent-mode refresh skill. +2. `ad-naming`: naming decoder, KPI map, query contract, and agent-mode refresh. +3. `creative-corpus`: durable full-ID per-creative files and agent-mode daily refresh. At the time this guide was written, `origin/main` still had an empty package index and none of these package directories. They are not released through the main registry @@ -247,9 +247,10 @@ a new contract. Activation belongs in the installed skill and package instructions. In the current package series: -- Context Kit builds its app and registers its refresh workflow on first use. -- Ad Naming registers its refresh workflow on first use. -- Creative Corpus registers and runs its build workflow, then creates a refresh routine. +- Context Kit builds its app; build and refresh Motion calls run directly in agent turns. +- Ad Naming builds and refreshes directly in agent turns. +- Creative Corpus builds directly in the requesting agent turn, then creates an + agent-mode refresh routine. ### Fleet approval @@ -487,22 +488,28 @@ motion meta insights \ motion meta insights \ --scope creative-asset-id \ --creative-asset-id <id> \ - --include-transcript \ - --include-glossary \ --date-range last_365d \ - --include-metrics - -motion meta insights \ - --include-glossary \ - --date-range last_30d \ - --sort topSpend \ - --limit 100 + --include-metrics \ + --glossary-category intended-audience \ + --glossary-category messaging-angle \ + --glossary-category hook-tactic \ + --glossary-category visual-format \ + --glossary-category asset-type \ + --glossary-category offer-type \ + --glossary-category seasonality \ + --summary-sections hookOrHeadline \ + --summary-sections creativeBreakdown \ + --summary-sections messagingAndPositioning \ + --summary-sections emotionalAndAudienceInsight \ + --summary-sections adDescription motion meta insights \ - --include-transcript \ --date-range last_30d \ --sort topSpend \ - --limit 20 + --limit 100 \ + --glossary-category intended-audience \ + --glossary-category messaging-angle \ + --glossary-category hook-tactic motion meta insights \ --date-range last_30d \ @@ -511,7 +518,9 @@ motion meta insights \ --table-kpi thumbstop_rate ``` -The creative-corpus code repeats `--creative-asset-id` for batches of at most 15 IDs. +Creative Corpus repeats `--creative-asset-id` for batches of at most 15 IDs. The +current CLI rejects `--include-glossary`, and its fast path blocks +`--include-transcript`; use the repeated category and section flags above. Naming and conversion discovery: @@ -652,28 +661,24 @@ start from runtime placeholder replacement plus `app build`. ### Workflow registration -```bash -workflow push /agent/brain/context-kit/workflows/context-kit-refresh.ts \ - --name context-kit-refresh - -workflow push /agent/brain/ad-naming/workflows/ad-naming-refresh.ts \ - --name ad-naming-refresh +The external platform exposes forms such as: -workflow push /agent/brain/meta/corpus-build.ts \ - --name creative-corpus-build +```bash +workflow push <workflow-file.ts> --name <workflow-name> ``` ### Workflow-backed tasks ```bash -task add --kind workflow --workflow-id <id> --name "Context Kit Refresh" -task add --kind workflow --workflow-id <id> --name "Ad Naming Refresh" -task add --kind workflow --workflow-id <id> --name "Creative Corpus Build" - +task add --kind workflow --workflow-id <id> --name "<task-name>" task run --id <task-id> task wait --run <run-id> ``` +Do not call trusted Motion tools from `task.bash`: task-scoped broker tokens cannot +access them. The current package series therefore performs Motion work directly in +agent turns and does not ship workflow-backed Motion tasks. + No task-listing syntax is established by the checked-in package skills. Confirm it with platform help before making it part of a package contract. @@ -687,19 +692,18 @@ routine list routine add \ --name "Creative corpus daily refresh" \ --cron "0 5 * * *" \ - --delivery "Update corpus state only — no conversation needed unless errors occur." \ - --prompt "Run the corpus refresh script. If the script fails, report the error in a new web conversation." \ - --script /agent/brain/meta/corpus-refresh.mjs + --delivery "Update corpus state only — no conversation needed unless new creatives are indexed or errors occur." \ + --prompt "Start an agent turn, read the installed creative-corpus skill, and run its refresh procedure directly with trusted Motion tools." ``` -Workflow-backed weekly refresh examples use: +Weekly agent-mode refresh examples use: ```bash routine add \ --name "<name>" \ --cron "0 9 * * 1" \ --delivery "<delivery behavior>" \ - --prompt "<run task, wait, summarize>" + --prompt "<start an agent turn, read the installed skill, refresh directly, summarize>" ``` Routine prompt prose contains this conversation-delivery form: @@ -919,18 +923,13 @@ or failure semantics. implemented resolver interprets that position as a display name and resolves email itself. Treat that invocation as stale; it can provision an incorrect handle. -### Migration and refresh scripts +### Migration scripts ```bash bash competitor-intel/migration-helper.sh bash team-member-memory/migration-helper.sh - -ROUTINE_TRIGGER=test /agent/brain/meta/corpus-refresh.mjs -/agent/brain/meta/corpus-refresh.mjs ``` -The Creative Corpus test trigger exits without making Motion calls. - ## Supporting external utilities Skills and scripts also consume: @@ -967,6 +966,10 @@ Check these before reusing an old example: 12. CI does not type-check package workflows or build packaged apps. 13. A feature branch can validate locally even while its registry source points to files not yet present on `main`. +14. Current sandbox installs can fail when the external package CLI calls `chmod` on + staged files and receives `EPERM`. This repository does not implement that staging + operation; manual file copying is a test-only recovery that bypasses atomic install + behavior and should not be treated as the production fix. ## Package authoring checklist @@ -1028,29 +1031,26 @@ Expected durable files include: /agent/.agents/skills/creative-corpus/SKILL.md /agent/apps/context-kit/buildeth.app.json /agent/brain/context-kit/context-kit-state.json -/agent/brain/context-kit/workflows/context-kit-refresh.ts /agent/brain/ad-naming/ad-naming-state.json /agent/brain/ad-naming/naming-decoder.md /agent/brain/ad-naming/kpi-map.md /agent/brain/ad-naming/query-contract.md -/agent/brain/ad-naming/workflows/ad-naming-refresh.ts /agent/brain/meta/corpus-state.json /agent/brain/meta/creatives/PLAYBOOK.md -/agent/brain/meta/corpus-build.ts -/agent/brain/meta/corpus-refresh.mjs ``` -Exercise executable staging safely: - -```bash -ROUTINE_TRIGGER=test /agent/brain/meta/corpus-refresh.mjs -``` +Creative files should end in a sanitized full creative asset ID, never an eight-character +prefix. Inspect `corpus-state.json` for `filenameConventionVersion: 2`. Check the Context Kit app through `app list`, and inspect task/routine state using only commands confirmed by live platform help. Context Kit and Ad Naming offer weekly routines; they do not create them automatically. Creative Corpus creates its daily routine only after the corpus build completes. +If install fails at staging `chmod` with `EPERM`, capture the exact operation/path and +report it to the Runneth platform team. The package source cannot repair the external +installer's filesystem ownership, and manual registration should remain test-only. + After merge, separately repeat installation from `main` and test managed/index discovery. Feature-branch installation bypasses those release surfaces. diff --git a/package-index.json b/package-index.json index 69d3edae..4212b59c 100644 --- a/package-index.json +++ b/package-index.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "indexRevision": "three-package-split-0.1.0", + "indexRevision": "creative-corpus-0.2.0", "packages": [ { "id": "context-kit", @@ -39,8 +39,8 @@ { "id": "creative-corpus", "name": "Creative Corpus", - "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls summaries, transcripts, glossary tags, and decoded naming for every creative into local Markdown files — indexed by creative ID. Enables whole-account historical analysis, VoC mining, naming-convention decoding, and brief generation without re-fetching from Motion on every question. Companion to Context Kit.", - "version": "0.1.0", + "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls Motion summary sections, glossary tags, and decoded naming into local Markdown files indexed by full creative asset ID. Enables whole-account historical analysis, voice mining, naming-convention decoding, and brief generation without re-fetching durable content on every question. Companion to Context Kit and Ad Naming.", + "version": "0.2.0", "categories": ["creative-strategy"], "packageManagerVersion": 1, "source": { diff --git a/packages/creative-corpus/README.md b/packages/creative-corpus/README.md index 393d513c..7e1dd8cd 100644 --- a/packages/creative-corpus/README.md +++ b/packages/creative-corpus/README.md @@ -1,21 +1,23 @@ # Creative Corpus -Builds the institutional creative memory that makes every Runneth answer sharper. Seeds a per-creative Markdown library by pulling Motion summaries, transcripts, and glossary tags for every creative in your Meta account — then keeps it fresh with a daily refresh script. +Builds the institutional creative memory that makes every Runneth answer sharper. +Seeds a per-creative Markdown library from Motion summary sections and glossary tags, +then keeps it fresh with a daily agent-mode refresh. Companion package to Context Kit and Ad Naming. ## What it builds -- `{adname}__{id8}.md` per creative under `/agent/brain/meta/creatives/` -- Hook, full transcript, value props, glossary tags, decoded naming per file +- `{adname}__{sanitized-full-creative-id}.md` per creative under `/agent/brain/meta/creatives/` +- Hook, creative text, value props, glossary tags, decoded naming per file - A Knoweth lane for auto-injection of relevant corpus files into context -- A daily script-mode refresh routine that adds new launches and updates spend state +- A daily agent-mode refresh routine that adds new launches and updates spend state ## What it does NOT do - Store live performance metrics (spend, ROAS, CPA stay in Motion) - Replace Motion for current performance analysis -- Re-pull transcripts once indexed (they don't change) +- Re-pull summary sections once indexed ## Install order recommendation @@ -26,15 +28,17 @@ Companion package to Context Kit and Ad Naming. ## App build gotchas - The corpus has no board app of its own. State is visible in the Context Kit board under a companion section if Context Kit is installed. -- Package sync stages files but does NOT run the build workflow. Run `build my corpus` after install. +- Package sync stages files but does NOT build the corpus. Run `build my corpus` after install. ## Architecture notes -- Build: workflow (`corpus-build.ts`) registered by the skill on first run -- Refresh: script-mode routine (`corpus-refresh.mjs`) registered after build +- Build: performed directly by the installed skill in an agent turn +- Refresh: agent-mode routine that re-runs the skill's refresh procedure - State: `/agent/brain/meta/corpus-state.json` - Playbook: `/agent/brain/meta/creatives/PLAYBOOK.md` ## Content vs. metrics rule -The corpus owns durable creative content. Live metrics always come from a fresh Motion pull. The only performance signal stored in corpus files is a coarse `spendState` (scaling / active / paused) refreshed daily by the script. +The corpus owns durable creative content. Live metrics always come from a fresh Motion +pull. The only performance signal stored in corpus files is a coarse `spendState` +(scaling / active / paused) refreshed daily by the agent routine. diff --git a/packages/creative-corpus/brain/meta/corpus-state.json b/packages/creative-corpus/brain/meta/corpus-state.json index 5ac4316c..d0eca0d0 100644 --- a/packages/creative-corpus/brain/meta/corpus-state.json +++ b/packages/creative-corpus/brain/meta/corpus-state.json @@ -2,14 +2,13 @@ "schemaVersion": 1, "id": "creative-corpus", "name": "Creative Corpus", - "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls summaries, transcripts, glossary tags, and decoded naming for every creative into local Markdown files — indexed by creative ID. Enables whole-account historical analysis, VoC mining, naming-convention decoding, and brief generation without re-fetching from Motion on every question. Companion to Context Kit.", - "version": "0.1.0", + "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls Motion summary sections, glossary tags, and decoded naming into local Markdown files indexed by full creative asset ID. Enables whole-account historical analysis, voice mining, naming-convention decoding, and brief generation without re-fetching durable content on every question. Companion to Context Kit and Ad Naming.", + "version": "0.2.0", + "filenameConventionVersion": 2, "totalCreatives": 0, "lastBuildDate": null, "lastRefreshDate": null, "corpusLaneId": null, - "buildWorkflowId": null, - "buildTaskId": null, "refreshRoutineId": null, "lanesRegistered": false } diff --git a/packages/creative-corpus/brain/meta/creatives/PLAYBOOK.md b/packages/creative-corpus/brain/meta/creatives/PLAYBOOK.md index c0db876e..72b635de 100644 --- a/packages/creative-corpus/brain/meta/creatives/PLAYBOOK.md +++ b/packages/creative-corpus/brain/meta/creatives/PLAYBOOK.md @@ -4,18 +4,24 @@ This file is the maintenance reference for the creative corpus. Keep it updated ## What this corpus is -A durable per-creative Markdown library. Every creative file contains durable content (hook, transcript, value props, glossary tags, decoded naming) that is fetched once and kept fresh. Live performance metrics stay in Motion and are NOT stored here — except a coarse `spendState` refreshed daily. +A durable per-creative Markdown library. Every creative file contains durable content +(hook, Motion summary sections, value props, glossary tags, decoded naming) that is +fetched once and kept fresh. Live performance metrics stay in Motion and are NOT +stored here, except a coarse `spendState` refreshed daily. ## File naming convention -`{sanitized-adname}__{id8}.md` +`{sanitized-adname}__{sanitized-full-creative-id}.md` -- `sanitized-adname`: ad name with non-alphanumeric chars replaced by `-`, max 80 chars -- `id8`: first 8 characters of the Motion creative asset ID +- `sanitized-adname`: ad name with non-alphanumeric chars replaced by `-`, max 60 chars +- `sanitized-full-creative-id`: the complete Motion creative asset ID normalized to + safe filename characters -Example: `p-ugc_fs-30_as-fear__a3b4c5d6.md` +Example: `p-ugc_fs-30_as-fear__507f1f77bcf86cd799439011.md` -**The `id8` is the stable key.** Even if an ad name changes, the file can be found by ID. Never rename a file after creation. +**The full ID is the stable key.** Resolve existing files by full-ID suffix or exact +`id:` frontmatter. If an ad name changes, update frontmatter in place and do not rename +the file. ## File schema @@ -23,7 +29,7 @@ Example: `p-ugc_fs-30_as-fear__a3b4c5d6.md` --- id: <full motion creative asset id> adName: <full ad name as returned by Motion> -id8: <first 8 chars of id> +filenameConvention: 2 format: <image|video|carousel|unknown> launchDate: <YYYY-MM-DD> campaignName: <campaign name at time of indexing> @@ -35,16 +41,22 @@ indexedAt: <YYYY-MM-DD> # when this file was first created # {ad name} ## Hook -{Opening line or first sentence of transcript. Lifted verbatim from transcript or Motion summary.} +{Motion's hookOrHeadline section, preserved as closely as returned.} -## Transcript -{Full transcript verbatim. "No transcript available." if Motion did not return one.} +## Creative Breakdown +{Motion's creativeBreakdown summary section, if returned.} -## Summary -{Motion's creative summary. "No summary available." if absent.} +## Messaging & Positioning +{Motion's messagingAndPositioning summary section, if returned.} + +## Emotional & Audience Insight +{Motion's emotionalAndAudienceInsight summary section, if returned.} + +## Ad Description +{Motion's adDescription summary section, if returned.} ## Value Props -{2-5 bullet points: claims or benefits this ad makes. Lifted from summary or transcript.} +{2-5 claims or benefits drawn from the returned summary sections.} ## Glossary Tags {One line per tag: "category: value"} @@ -57,7 +69,7 @@ indexedAt: <YYYY-MM-DD> # when this file was first created | Store in corpus | Keep as live Motion pull | |---|---| -| Hook, transcript, summary | Spend (last N days) | +| Hook and summary sections | Spend (last N days) | | Glossary tags | ROAS, CPA, CTR | | Decoded ad name | Thumbstop, hold rate | | Value props | Conversion counts | @@ -67,32 +79,44 @@ indexedAt: <YYYY-MM-DD> # when this file was first created ## Build procedure -Runs as a workflow (`corpus-build.ts`) registered by the creative-corpus skill. +Runs directly in the creative-corpus agent turn. Motion calls must never run from +`task.bash` or a script-mode routine. 1. Pull roster over 3 x 30-day windows (separate calls — 90-day + summaries times out). 2. Deduplicate by creative asset ID. -3. Skip already-indexed files (create-if-absent, unless `forceRebuild: true`). -4. Enrich in batches of ≤15 IDs: `motion meta insights --scope creative-asset-id --include-transcript --include-glossary --date-range last_365d`. +3. Skip already-indexed full IDs (create-if-absent). +4. Enrich in batches of ≤15 IDs using repeated `--creative-asset-id`, + `--glossary-category`, and `--summary-sections` flags from the installed skill. 5. Write one MD file per creative. 6. Update `corpus-state.json` with final counts. ## Refresh procedure -Runs as a script-mode routine (`corpus-refresh.mjs`) on a daily `0 5 * * *` cron. +Runs as an agent-mode routine on a daily `0 5 * * *` cron. 1. Pull `motion meta insights --date-range last_7d`. -2. New IDs → enrich and write (same batch procedure as build). -3. Existing IDs → update `spendState` and `status` in-place only. Do NOT re-pull transcripts. +2. New full IDs → enrich and write (same batch procedure as build). +3. Existing IDs → update `spendState` and `status` in-place only. Do NOT re-pull + summary sections. 4. Update `corpus-state.json`. ## Troubleshooting **Timeout on enrichment batch:** Reduce batch size. If ≤15 still times out, try `--date-range last_30d` instead of `last_365d` for the enrichment call (creative must have run recently to appear). -**Transcript missing:** Some creative types (DPA, catalog ads) do not have transcripts. `"No transcript available."` is correct. +**Summary sections missing:** Some creative types return sparse creative text. Keep the +section empty and do not invent a transcript. **Stale spendState:** Check that the daily routine is active with `routine list`. If paused, resume it. -**File count mismatch vs Motion account:** The corpus covers the 90-day build window + daily incremental. Creatives older than 90 days at build time are not in the corpus unless manually added. To add an older creative: `motion meta insights --scope creative-asset-id --creative-asset-id <id> --include-transcript --include-glossary --date-range last_365d` and write the file manually. +**File count mismatch vs Motion account:** The corpus covers the 90-day build window +plus daily incremental updates. Creatives older than 90 days at build time are not in +the corpus unless manually added. Use the ID-scoped glossary-category and +summary-sections command from the installed skill. + +**Legacy `id8` files:** Pre-release test installs may contain filenames ending in only +the first eight ID characters. Rebuild on a fresh VM, or match each file's full `id:` +frontmatter before renaming. Never overwrite multiple legacy files that share an +eight-character prefix. ## Build log diff --git a/packages/creative-corpus/instructions/behavior.md b/packages/creative-corpus/instructions/behavior.md index 88f3a615..a6896521 100644 --- a/packages/creative-corpus/instructions/behavior.md +++ b/packages/creative-corpus/instructions/behavior.md @@ -23,7 +23,7 @@ The skill registers this lane on first run. Individual creative files are surfac | What lives in the corpus | What stays a live Motion pull | |---|---| | Hook / opening line | Spend (last N days) | -| Full transcript | ROAS, CPA, CTR | +| Motion summary sections / creative text | ROAS, CPA, CTR | | Glossary tags (AI-derived) | Thumbstop, hold rate | | Decoded ad name (from ad-naming) | Current status | | Summary / value props | Recent impressions | @@ -36,18 +36,20 @@ The corpus checks for the naming decoder at `/agent/brain/ad-naming/naming-decod ## File naming convention -`{sanitized-adname}__{id8}.md` -- `sanitized-adname`: ad name with non-alphanumeric chars → `-`, max 80 chars -- `id8`: first 8 characters of the Motion creative asset ID +`{sanitized-adname}__{sanitized-full-creative-id}.md` +- `sanitized-adname`: ad name with non-alphanumeric chars → `-`, max 60 chars +- `sanitized-full-creative-id`: the complete Motion creative asset ID, normalized + to safe filename characters -The `id8` is the stable key. Never rename a file after creation. +The full creative ID is the stable key. Match existing files by the full-ID suffix or +exact `id:` frontmatter; an ad-name change must not create a duplicate. ## When to use the corpus Use corpus files when: - The question is about a specific creative by name or ID - Analyzing patterns across the full account (hook tactics, messaging angles, format mix) -- Building briefs that reference past hooks or transcripts +- Building briefs that reference past hooks and creative text - VoC mining beyond the current `last_30d` pull - Looking up paused or historical creatives @@ -55,19 +57,27 @@ Use live Motion pulls for current performance numbers. ## Refresh behavior -The daily refresh script (`corpus-refresh.mjs`) runs as a script-mode routine: +The daily agent-mode routine reads the installed skill and performs refresh work in +an agent turn: 1. Pulls `last_7d` for new creative IDs not yet in the corpus. 2. New IDs: fetches enrichment in batches of ≤15, writes new corpus files. 3. Existing IDs in the `last_7d` pull: updates `spendState` and `status` in-place. -4. Does NOT re-pull transcripts or summaries for already-indexed creatives. +4. Does NOT re-pull summary sections for already-indexed creatives. + +Every Motion call must run directly in the agent turn. Never call Motion from +`task.bash` or a script-mode routine. ## State file -`/agent/brain/meta/corpus-state.json` tracks: `totalCreatives`, `lastBuildDate`, `lastRefreshDate`, `corpusLaneId`, `buildWorkflowId`, `buildTaskId`, `refreshRoutineId`. +`/agent/brain/meta/corpus-state.json` tracks: `totalCreatives`, `lastBuildDate`, +`lastRefreshDate`, `corpusLaneId`, `refreshRoutineId`, and +`filenameConventionVersion`. ## Rules -- Never re-pull transcripts/summaries for already-indexed files. +- Never re-pull summary sections for already-indexed files. - Always batch enrichment at ≤15 IDs per `motion meta insights --scope creative-asset-id` call. - Use `--date-range last_365d` on ID-scoped calls. -- Write is create-if-absent; refresh is update-in-place via `id8` key. +- Use repeated `--glossary-category` and `--summary-sections` flags from the skill. + Do not use rejected `--include-glossary` or blocked `--include-transcript` flags. +- Write is create-if-absent; refresh is update-in-place via the full creative ID. diff --git a/packages/creative-corpus/package.json b/packages/creative-corpus/package.json index 9314a223..95777c0e 100644 --- a/packages/creative-corpus/package.json +++ b/packages/creative-corpus/package.json @@ -2,8 +2,8 @@ "schemaVersion": 1, "id": "creative-corpus", "name": "Creative Corpus", - "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls summaries, transcripts, glossary tags, and decoded naming for every creative into local Markdown files — indexed by creative ID. Enables whole-account historical analysis, VoC mining, naming-convention decoding, and brief generation without re-fetching from Motion on every question. Companion to Context Kit.", - "version": "0.1.0", + "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls Motion summary sections, glossary tags, and decoded naming into local Markdown files indexed by full creative asset ID. Enables whole-account historical analysis, voice mining, naming-convention decoding, and brief generation without re-fetching durable content on every question. Companion to Context Kit and Ad Naming.", + "version": "0.2.0", "installPolicy": "manual", "updatePolicy": "manual", "uninstallPolicy": "allowed", @@ -27,20 +27,6 @@ "target": { "root": "agent_brain", "path": "meta/creatives/PLAYBOOK.md" }, "executable": false }, - { - "id": "creative-corpus-build-workflow", - "type": "file", - "sourcePath": "workflows/corpus-build.ts", - "target": { "root": "agent_brain", "path": "meta/corpus-build.ts" }, - "executable": false - }, - { - "id": "creative-corpus-refresh-script", - "type": "file", - "sourcePath": "scripts/corpus-refresh.mjs", - "target": { "root": "agent_brain", "path": "meta/corpus-refresh.mjs" }, - "executable": true - }, { "id": "creative-corpus-skill", "type": "directory", diff --git a/packages/creative-corpus/runneth-package.json b/packages/creative-corpus/runneth-package.json index 34a8b07b..877ca73d 100644 --- a/packages/creative-corpus/runneth-package.json +++ b/packages/creative-corpus/runneth-package.json @@ -2,8 +2,8 @@ "schemaVersion": 1, "id": "creative-corpus", "name": "Creative Corpus", - "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls summaries, transcripts, glossary tags, and decoded naming for every creative into local Markdown files — indexed by creative ID. Enables whole-account historical analysis, VoC mining, naming-convention decoding, and brief generation without re-fetching from Motion on every question. Companion to Context Kit.", - "version": "0.1.0", + "description": "Builds and maintains a durable per-creative brain library for the connected Meta account. Pulls Motion summary sections, glossary tags, and decoded naming into local Markdown files indexed by full creative asset ID. Enables whole-account historical analysis, voice mining, naming-convention decoding, and brief generation without re-fetching durable content on every question. Companion to Context Kit and Ad Naming.", + "version": "0.2.0", "updatePolicy": "manual", "uninstallPolicy": "allowed", "resources": [ @@ -26,20 +26,6 @@ "target": { "root": "agent_brain", "path": "meta/creatives/PLAYBOOK.md" }, "executable": false }, - { - "id": "creative-corpus-build-workflow", - "type": "file", - "sourcePath": "workflows/corpus-build.ts", - "target": { "root": "agent_brain", "path": "meta/corpus-build.ts" }, - "executable": false - }, - { - "id": "creative-corpus-refresh-script", - "type": "file", - "sourcePath": "scripts/corpus-refresh.mjs", - "target": { "root": "agent_brain", "path": "meta/corpus-refresh.mjs" }, - "executable": true - }, { "id": "creative-corpus-skill", "type": "directory", diff --git a/packages/creative-corpus/scripts/corpus-refresh.mjs b/packages/creative-corpus/scripts/corpus-refresh.mjs deleted file mode 100644 index 11ecd85d..00000000 --- a/packages/creative-corpus/scripts/corpus-refresh.mjs +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/bin/env node -/** - * Creative Corpus Daily Refresh Script - * - * Script-mode routine: runs without an agent turn. - * Exit 0 = success. Exit non-zero = failure (triggers agent fallback run). - * - * Motion CLI pattern: - * execFileSync returns the envelope JSON to stdout. - * Parse .file from the envelope, then read from that path. - * Never redirect the motion command itself. - * - * What it does: - * 1. Pulls motion meta insights --date-range last_7d to find new/changed creatives. - * 2. New IDs: fetch enrichment in batches of ≤15, write new corpus files. - * 3. Existing IDs: update spendState + status in-place (no transcript re-pull). - * 4. Updates corpus-state.json with new counts and lastRefreshDate. - */ - -import { execFileSync } from "node:child_process"; -import { existsSync, readFileSync, writeFileSync, readdirSync } from "node:fs"; -import { join } from "node:path"; - -const CORPUS_DIR = "/agent/brain/meta/creatives"; -const STATE_FILE = "/agent/brain/meta/corpus-state.json"; -const BATCH_SIZE = 15; -const ROUTINE_TRIGGER = process.env.ROUTINE_TRIGGER ?? ""; - -// ─── Test gate ─────────────────────────────────────────────────────────────── -if (ROUTINE_TRIGGER === "test") { - process.stderr.write("[test] corpus-refresh: would pull last_7d and update corpus\n"); - process.stderr.write("[test] corpus-refresh: skipping real Motion calls\n"); - process.exit(0); -} - -// ─── Helpers ───────────────────────────────────────────────────────────────── - -function motionCall(args) { - const envelopeStr = execFileSync("motion", args, { encoding: "utf8" }); - const envelope = JSON.parse(envelopeStr); - if (!envelope.successful) { - throw new Error("motion " + args[0] + " failed: " + (envelope.message || "unknown error")); - } - return envelope.file; -} - -function readJson(filePath) { - return JSON.parse(readFileSync(filePath, "utf8")); -} - -function corpusFilePath(adName, id) { - const slug = (adName || "unknown").replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 80); - const id8 = id.slice(0, 8); - return join(CORPUS_DIR, `${slug}__${id8}.md`); -} - -function updateSpendStateInFile(filePath, spendState, status) { - if (!existsSync(filePath)) return; - let content = readFileSync(filePath, "utf8"); - content = content.replace(/^spendState: .+$/m, `spendState: ${spendState}`); - content = content.replace(/^status: .+$/m, `status: ${status}`); - writeFileSync(filePath, content); -} - -function readState() { - try { return readJson(STATE_FILE); } catch { return {}; } -} - -function writeState(state) { - writeFileSync(STATE_FILE, JSON.stringify(state, null, 2)); -} - -const today = new Date().toISOString().slice(0, 10); -const errors = []; -let newFilesWritten = 0; -let existingFilesUpdated = 0; - -try { - // Step 1: Pull last_7d roster - process.stderr.write("[corpus-refresh] Pulling last_7d roster...\n"); - - const rosterFile = motionCall([ - "meta", "insights", - "--date-range", "last_7d", - "--sort", "topSpend", - "--include-metrics", - "--limit", "500", - ]); - - const rosterData = readJson(rosterFile); - const creatives = (rosterData.creatives || []).map(c => ({ - id: c.id, - adName: c.adName || "unknown", - format: c.format || "unknown", - status: c.status || "unknown", - spend: c.metrics?.spend || 0, - })).filter(c => c.id); - - process.stderr.write(`[corpus-refresh] Found ${creatives.length} creatives in last_7d\n`); - - // Step 2: Split new vs existing - const newCreatives = []; - const existingCreatives = []; - - for (const c of creatives) { - if (existsSync(corpusFilePath(c.adName, c.id))) { - existingCreatives.push(c); - } else { - newCreatives.push(c); - } - } - - process.stderr.write(`[corpus-refresh] New: ${newCreatives.length}, Existing: ${existingCreatives.length}\n`); - - // Step 3: Update spendState on existing files (no Motion calls needed) - for (const c of existingCreatives) { - const spendState = c.spend > 1000 ? "scaling" : c.spend > 0 ? "active" : "paused"; - updateSpendStateInFile(corpusFilePath(c.adName, c.id), spendState, c.status); - existingFilesUpdated++; - } - - // Step 4: Fetch enrichment for new creatives in batches of ≤15 - const decoderExists = existsSync("/agent/brain/ad-naming/naming-decoder.md"); - - for (let i = 0; i < newCreatives.length; i += BATCH_SIZE) { - const batch = newCreatives.slice(i, i + BATCH_SIZE); - const batchNum = Math.floor(i / BATCH_SIZE) + 1; - - process.stderr.write(`[corpus-refresh] Enriching batch ${batchNum} (${batch.length} IDs)...\n`); - - try { - const idFlags = batch.flatMap(c => ["--creative-asset-id", c.id]); - - const enrichFile = motionCall([ - "meta", "insights", - "--scope", "creative-asset-id", - ...idFlags, - "--include-transcript", - "--include-glossary", - "--date-range", "last_365d", - "--include-metrics", - ]); - - const enrichData = readJson(enrichFile); - const creativeMap = {}; - for (const c of (enrichData.creatives || [])) { - if (c.id) creativeMap[c.id] = c; - } - - for (const meta of batch) { - try { - const creative = creativeMap[meta.id] || {}; - const id8 = meta.id.slice(0, 8); - const spendState = meta.spend > 1000 ? "scaling" : meta.spend > 0 ? "active" : "paused"; - - const tags = (creative.glossaryTags || []) - .map(t => `- ${t.categoryId}: ${t.tagName}`) - .join("\n") || "No tags returned."; - - const transcript = creative.transcript || "No transcript available."; - const summary = creative.summary || "No summary available."; - const hook = transcript !== "No transcript available." - ? (transcript.split(/[.!?]/)[0]?.trim() || "No hook extracted.") - : (summary !== "No summary available." ? summary.slice(0, 150) : "No hook available."); - - const decodedName = decoderExists - ? `See /agent/brain/ad-naming/naming-decoder.md for dimension definitions. Raw: ${meta.adName}` - : "Install Ad Naming to enable ad name decoding."; - - const md = [ - "---", - `id: ${meta.id}`, - `adName: ${meta.adName}`, - `id8: ${id8}`, - `format: ${meta.format}`, - `launchDate: ${creative.launchDate || "unknown"}`, - `campaignName: ${creative.campaignName || "unknown"}`, - `status: ${meta.status}`, - `spendState: ${spendState}`, - `indexedAt: ${today}`, - "---", - "", - `# ${meta.adName}`, - "", - "## Hook", - hook, - "", - "## Transcript", - transcript, - "", - "## Summary", - summary, - "", - "## Glossary Tags", - tags, - "", - "## Decoded Ad Name", - decodedName, - ].join("\n"); - - writeFileSync(corpusFilePath(meta.adName, meta.id), md); - newFilesWritten++; - } catch (creativeErr) { - errors.push(`Failed to write ${meta.id}: ${creativeErr.message}`); - } - } - } catch (batchErr) { - errors.push(`Batch ${batchNum} failed: ${batchErr.message}`); - } - } - - // Step 5: Update corpus state - const totalFiles = readdirSync(CORPUS_DIR) - .filter(f => f.endsWith(".md") && f !== "PLAYBOOK.md").length; - - const state = readState(); - state.totalCreatives = totalFiles; - state.lastRefreshDate = today; - writeState(state); - - process.stderr.write( - `[corpus-refresh] Done. New: ${newFilesWritten}, Updated: ${existingFilesUpdated}, Total: ${totalFiles}\n` - ); - - if (errors.length > 0) { - process.stderr.write(`[corpus-refresh] ${errors.length} errors:\n${errors.join("\n")}\n`); - process.exit(1); - } - - process.exit(0); - -} catch (topErr) { - process.stderr.write(`[corpus-refresh] Fatal: ${topErr.message}\n`); - process.exit(1); -} diff --git a/packages/creative-corpus/skills/SKILL.md b/packages/creative-corpus/skills/SKILL.md index e6411d2a..b35d1774 100644 --- a/packages/creative-corpus/skills/SKILL.md +++ b/packages/creative-corpus/skills/SKILL.md @@ -5,7 +5,9 @@ description: Builds and maintains the durable per-creative brain library for thi # Creative Corpus skill -Build the corpus once, keep it fresh with a daily script routine. After build, every analysis reads local files instead of re-hitting Motion. +Build the corpus once, then keep it fresh with a daily agent-mode routine. After +build, every analysis reads local files instead of re-hitting Motion for durable +creative content. ## Step 0 — Check state @@ -19,25 +21,61 @@ If `corpusLaneId` is absent from state: 1. Call ContextConfig with action `update` to register the `creative-corpus` lane pointing at `/agent/brain/meta/creatives/` as a directory lane with `read: true`. 2. Save the returned lane ID (or a generated stable ID) to state as `corpusLaneId`. -## Step 2 — Register and run the build workflow (first run only) - -1. Read the workflow source at `/agent/brain/meta/corpus-build.ts`. -2. Run `workflow push /agent/brain/meta/corpus-build.ts --name creative-corpus-build`. -3. Save the returned workflow ID to `corpus-state.json` as `buildWorkflowId`. -4. Create the task: `task add --kind workflow --workflow-id <id> --name "Creative Corpus Build"`. Save as `buildTaskId`. -5. Run the task: `task run --id <buildTaskId>`. Tell the customer: "I'm building your creative corpus — this pulls summaries, transcripts, and tags for every creative in your account. It runs in the background and will take a few minutes depending on your library size. I'll let you know when it's done." -6. `task wait --run <runId>` with a generous timeout. When complete, read the output and report: "Built [n] creative files. Your creative brain is ready." - -## Step 3 — Set up the daily refresh routine (after build completes) - -1. Register the refresh script as a script-mode routine: +## Step 2 — Build directly in this agent turn (first run only) + +Do not run Motion from `task.bash`, a workflow bash task, or a script-mode routine. +Task-scoped broker tokens cannot access the trusted Motion tool. Perform every Motion +call directly in this agent turn; use bash only for deterministic local file work. + +1. Tell the customer you are building the corpus, then create + `/agent/brain/meta/creatives/` if needed. +2. Pull the creative roster in three separate windows: `last_30d`, days 31-60, and + days 61-90. Use `motion meta insights` with `--sort topSpend`, + `--include-metrics`, and `--limit 500`. Parse each envelope's `.file`. +3. Deduplicate by the full creative asset ID. Skip an ID when a corpus file already + has that exact `id:` frontmatter or ends in its sanitized full-ID suffix. +4. Enrich new IDs in batches of no more than 15. Repeat `--creative-asset-id` and + each category/section flag: + ``` + motion meta insights \ + --scope creative-asset-id \ + --creative-asset-id <id> \ + --date-range last_365d \ + --include-metrics \ + --glossary-category intended-audience \ + --glossary-category messaging-angle \ + --glossary-category hook-tactic \ + --glossary-category visual-format \ + --glossary-category asset-type \ + --glossary-category offer-type \ + --glossary-category seasonality \ + --summary-sections hookOrHeadline \ + --summary-sections creativeBreakdown \ + --summary-sections messagingAndPositioning \ + --summary-sections emotionalAndAudienceInsight \ + --summary-sections adDescription + ``` +5. Write one Markdown file per creative using + `{sanitized-adname}__{sanitized-full-creative-id}.md`. Limit the ad-name prefix + to 60 characters. The full ID is the stable key; never use an ID prefix. +6. Store the full `id`, ad name, format, launch date, campaign name, status, + `spendState`, and `indexedAt` in frontmatter. Store the five summary sections, + glossary tags, value props, and decoded ad name in the body. Do not label summary + sections as a verbatim transcript. +7. Update `corpus-state.json` with `totalCreatives`, `lastBuildDate`, and + `filenameConventionVersion: 2`; append the result to `PLAYBOOK.md`. +8. Report built/skipped/failed counts. If any full IDs are missing from the result, + name them rather than silently counting them as built. + +## Step 3 — Set up the daily agent-mode refresh (after build completes) + +1. Register an agent-mode routine. Do not pass `--script`: ``` routine add \ --name "Creative corpus daily refresh" \ --cron "0 5 * * *" \ - --delivery "Update corpus state only — no conversation needed unless errors occur." \ - --prompt "Run the corpus refresh script. If the script fails, report the error in a new web conversation." \ - --script /agent/brain/meta/corpus-refresh.mjs + --delivery "Update corpus state only — no conversation needed unless new creatives are indexed or errors occur." \ + --prompt "Start an agent turn and read the installed creative-corpus skill. Run its refresh procedure directly with trusted Motion tools; never call Motion from task.bash or a script. Match existing files by full creative ID, enrich new IDs in batches of 15 or fewer with the documented glossary-category and summary-sections flags, update spendState and status for existing IDs, update corpus-state.json, and open a new conversation only when new creatives were indexed or errors occurred." ``` 2. Save the routine ID to `corpus-state.json` as `refreshRoutineId`. 3. Tell the customer: "I've set up a daily refresh that runs at 5am. New creative launches will appear in the corpus the next morning." @@ -48,16 +86,20 @@ After build, the corpus lane is active. When the customer asks questions that be - **"What hooks have we ever run about fear?"** → `rg -l "fear" /agent/brain/meta/creatives/` then read matching files. - **"Show me every UGC creative from last year"** → filter corpus files by `format: ugc` and `launchDate`. -- **"What did this ad say?"** → find by ad name or ID, read `transcript` field. -- **"What customer language do we use around [topic]?"** → grep transcripts across the full corpus. +- **"What did this ad say?"** → find by full ID or ad name and read its summary sections. +- **"What customer language do we use around [topic]?"** → search the creative-text sections across the corpus. - **"Build me briefs based on our best hooks"** → read top-spend corpus files, extract hook lines, generate briefs. Always say: "I'm reading this from your creative corpus — [n] creatives indexed locally." Do not re-pull from Motion for content questions when the corpus file exists. ## Rules -- Do not re-pull transcripts or summaries for already-indexed creatives. They are durable content. +- Do not re-pull summary sections for already-indexed creatives. They are durable content. - Always batch ≤15 IDs per enrichment call. - The corpus does not replace live Motion pulls for current performance numbers. -- If a creative ID is mentioned that is not in the corpus, offer to fetch and add it: `motion meta insights --scope creative-asset-id --creative-asset-id <id> --include-transcript --include-glossary --date-range last_365d`. +- Use the sanitized full creative asset ID as the filename suffix and canonical key. + Resolve existing files by full-ID suffix or exact `id:` frontmatter so ad-name + changes do not create duplicates. +- If a creative ID is mentioned that is not in the corpus, offer to fetch and add it + with the same ID-scoped glossary-category and summary-sections command from Step 2. - Keep the PLAYBOOK.md updated when the build finishes. It is the maintenance reference. diff --git a/packages/creative-corpus/workflows/corpus-build.ts b/packages/creative-corpus/workflows/corpus-build.ts deleted file mode 100644 index 42440faa..00000000 --- a/packages/creative-corpus/workflows/corpus-build.ts +++ /dev/null @@ -1,362 +0,0 @@ -import type { Workflow } from "@runneth/workflow/v1"; - -type CorpusBuildOutput = { - totalBuilt: number; - totalSkipped: number; - totalFailed: number; - batches: number; - buildDate: string; -}; - -/** - * Creative Corpus Build Workflow - * - * Pulls every creative in the Meta account over a 90-day window, - * batches IDs into groups of ≤15, fetches summary + transcript + glossary - * for each batch, and writes one Markdown file per creative. - * - * Motion CLI pattern for bash tasks: - * ENVELOPE=$(motion meta insights ...) - * FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - * jq '...' "$FILE" - * - * Split: bash for Motion pulls + file writes; agent only for naming decode (LLM needed). - * Agent tasks write structured output to /tmp files; bash reads those. - */ -export const wf: Workflow< - { forceRebuild?: boolean }, - CorpusBuildOutput -> = async ({ input, task }) => { - const forceRebuild = input?.forceRebuild ?? false; - - // ─── Step 1: Pull creative roster across 3 x 30-day windows ────────────── - // Separate calls — 90-day + summaries times out (Jude's lesson). - // No transcripts/summaries here — just the roster for deduplication. - - await task.bash({ - key: "roster-last-30d", - script: ` - set -euo pipefail - mkdir -p /tmp/corpus-roster /tmp/corpus-batches /agent/brain/meta/creatives - - ENVELOPE=$(motion meta insights --date-range last_30d --sort topSpend --include-metrics --limit 500) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - - jq '[.creatives[] | { - id: .id, - adName: (.adName // "unknown"), - format: (.format // "unknown"), - launchDate: (.launchDate // null), - campaignName: (.campaignName // null), - status: (.status // "unknown"), - spend: (.metrics.spend // 0) - }]' "$FILE" > /tmp/corpus-roster/batch-30.json - - echo "30d roster: $(jq length /tmp/corpus-roster/batch-30.json) creatives" - `, - }); - - await task.bash({ - key: "roster-31-60d", - script: ` - set -euo pipefail - - # Portable date arithmetic via node (avoids GNU vs BSD date differences) - START=$(node -e "const d=new Date();d.setDate(d.getDate()-60);console.log(d.toISOString().slice(0,10))") - END=$(node -e "const d=new Date();d.setDate(d.getDate()-31);console.log(d.toISOString().slice(0,10))") - - ENVELOPE=$(motion meta insights --start-date "$START" --end-date "$END" --sort topSpend --include-metrics --limit 500) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - - jq '[.creatives[] | { - id: .id, - adName: (.adName // "unknown"), - format: (.format // "unknown"), - launchDate: (.launchDate // null), - campaignName: (.campaignName // null), - status: (.status // "unknown"), - spend: (.metrics.spend // 0) - }]' "$FILE" > /tmp/corpus-roster/batch-60.json - - echo "31-60d roster: $(jq length /tmp/corpus-roster/batch-60.json) creatives" - `, - }); - - await task.bash({ - key: "roster-61-90d", - script: ` - set -euo pipefail - - START=$(node -e "const d=new Date();d.setDate(d.getDate()-90);console.log(d.toISOString().slice(0,10))") - END=$(node -e "const d=new Date();d.setDate(d.getDate()-61);console.log(d.toISOString().slice(0,10))") - - ENVELOPE=$(motion meta insights --start-date "$START" --end-date "$END" --sort topSpend --include-metrics --limit 500) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - - jq '[.creatives[] | { - id: .id, - adName: (.adName // "unknown"), - format: (.format // "unknown"), - launchDate: (.launchDate // null), - campaignName: (.campaignName // null), - status: (.status // "unknown"), - spend: 0 - }]' "$FILE" > /tmp/corpus-roster/batch-90.json - - echo "61-90d roster: $(jq length /tmp/corpus-roster/batch-90.json) creatives" - `, - }); - - // ─── Step 2: Deduplicate + create batches ───────────────────────────────── - - const batchSetup = await task.bash({ - key: "create-batches", - script: ` - set -euo pipefail - - node -e " - const fs = require('fs'); - const path = require('path'); - - const r30 = JSON.parse(fs.readFileSync('/tmp/corpus-roster/batch-30.json', 'utf8') || '[]'); - const r60 = JSON.parse(fs.readFileSync('/tmp/corpus-roster/batch-60.json', 'utf8') || '[]'); - const r90 = JSON.parse(fs.readFileSync('/tmp/corpus-roster/batch-90.json', 'utf8') || '[]'); - - // Deduplicate by id — keep highest-spend entry - const byId = new Map(); - for (const c of [...r30, ...r60, ...r90]) { - if (!c.id) continue; - const existing = byId.get(c.id); - if (!existing || (c.spend || 0) > (existing.spend || 0)) { - byId.set(c.id, c); - } - } - const allCreatives = Array.from(byId.values()); - - // Skip already-indexed unless forceRebuild - const forceRebuild = ${forceRebuild}; - const toProcess = forceRebuild ? allCreatives : allCreatives.filter(c => { - const id8 = c.id.slice(0, 8); - const slug = c.adName.replace(/[^a-zA-Z0-9-_]/g, '-').slice(0, 80); - return !fs.existsSync('/agent/brain/meta/creatives/' + slug + '__' + id8 + '.md'); - }); - - // Batch into groups of 15 - const batches = []; - for (let i = 0; i < toProcess.length; i += 15) { - batches.push(toProcess.slice(i, i + 15)); - } - - // Write each batch file - batches.forEach((batch, i) => { - fs.writeFileSync('/tmp/corpus-batches/batch-' + i + '.json', JSON.stringify(batch)); - }); - - const manifest = { total: allCreatives.length, toProcess: toProcess.length, batchCount: batches.length }; - fs.writeFileSync('/tmp/corpus-batches/manifest.json', JSON.stringify(manifest)); - console.log(JSON.stringify(manifest)); - " - `, - }); - - let manifest = { total: 0, toProcess: 0, batchCount: 0 }; - try { - manifest = JSON.parse(batchSetup.stdout.trim()); - } catch { - // Default to 0 batches — nothing to process - } - - // ─── Step 3: Check for naming decoder (used during file writes) ─────────── - - await task.bash({ - key: "check-naming-decoder", - script: ` - if [ -f /agent/brain/ad-naming/naming-decoder.md ]; then - echo "decoder-present" - else - echo "decoder-absent" - fi - `, - }); - - // ─── Step 4: Process batches — fetch enrichment + write MD files ────────── - // Each batch: bash fetches enrichment from Motion, then writes the MD files. - // Agent is only called when naming decode needs LLM interpretation. - - let totalBuilt = 0; - let totalFailed = 0; - - for (let batchIndex = 0; batchIndex < manifest.batchCount; batchIndex++) { - // Fetch enrichment for this batch via bash - await task.bash({ - key: `fetch-enrichment-${batchIndex}`, - script: ` - set -euo pipefail - - # Read the batch to get IDs - BATCH_FILE="/tmp/corpus-batches/batch-${batchIndex}.json" - IDS=$(jq -r '.[].id' "$BATCH_FILE") - - # Build the --creative-asset-id flags - ID_FLAGS="" - while IFS= read -r id; do - ID_FLAGS="$ID_FLAGS --creative-asset-id $id" - done <<< "$IDS" - - # Fetch enrichment (single motion call — no pipe on the motion command itself) - ENVELOPE=$(motion meta insights --scope creative-asset-id $ID_FLAGS --include-transcript --include-glossary --date-range last_365d --include-metrics) - FILE=$(printf '%s' "$ENVELOPE" | jq -r '.file') - - # Save enrichment file path for the write step - echo "$FILE" > /tmp/corpus-batches/enrichment-${batchIndex}-path.txt - - echo "batch-${batchIndex}: enrichment fetched from $FILE" - `, - }); - - // Write MD files for this batch - const writeResult = await task.bash({ - key: `write-files-${batchIndex}`, - script: ` - set -euo pipefail - - BATCH_FILE="/tmp/corpus-batches/batch-${batchIndex}.json" - ENRICHMENT_FILE=$(cat /tmp/corpus-batches/enrichment-${batchIndex}-path.txt) - DECODER_EXISTS=$([ -f /agent/brain/ad-naming/naming-decoder.md ] && echo "true" || echo "false") - TODAY=$(node -e "console.log(new Date().toISOString().slice(0,10))") - - node -e " - const fs = require('fs'); - const path = require('path'); - - const batch = JSON.parse(fs.readFileSync('$BATCH_FILE', 'utf8')); - const enrichData = JSON.parse(fs.readFileSync('$ENRICHMENT_FILE', 'utf8')); - const creativeMap = {}; - for (const c of (enrichData.creatives || [])) { - if (c.id) creativeMap[c.id] = c; - } - - const decoderExists = $DECODER_EXISTS; - let built = 0; - let failed = 0; - - for (const meta of batch) { - try { - const creative = creativeMap[meta.id] || {}; - const id8 = meta.id.slice(0, 8); - const slug = meta.adName.replace(/[^a-zA-Z0-9-_]/g, '-').slice(0, 80); - const filePath = path.join('/agent/brain/meta/creatives', slug + '__' + id8 + '.md'); - - const spend = meta.spend || 0; - const spendState = spend > 1000 ? 'scaling' : spend > 0 ? 'active' : 'paused'; - - const tags = (creative.glossaryTags || []) - .map(t => '- ' + t.categoryId + ': ' + t.tagName) - .join('\n') || 'No tags returned.'; - - const transcript = creative.transcript || 'No transcript available.'; - const summary = creative.summary || 'No summary available.'; - const hook = transcript !== 'No transcript available.' - ? transcript.split(/[.!?]/)[0]?.trim() || 'No hook extracted.' - : (summary !== 'No summary available.' ? summary.slice(0, 150) : 'No hook available.'); - - const decodedName = decoderExists - ? 'See /agent/brain/ad-naming/naming-decoder.md for dimension definitions. Raw: ' + meta.adName - : 'Install Ad Naming to enable ad name decoding.'; - - const md = [ - '---', - 'id: ' + meta.id, - 'adName: ' + meta.adName, - 'id8: ' + id8, - 'format: ' + (meta.format || 'unknown'), - 'launchDate: ' + (meta.launchDate || 'unknown'), - 'campaignName: ' + (meta.campaignName || 'unknown'), - 'status: ' + (meta.status || 'unknown'), - 'spendState: ' + spendState, - 'indexedAt: $TODAY', - '---', - '', - '# ' + meta.adName, - '', - '## Hook', - hook, - '', - '## Transcript', - transcript, - '', - '## Summary', - summary, - '', - '## Glossary Tags', - tags, - '', - '## Decoded Ad Name', - decodedName, - ].join('\n'); - - fs.writeFileSync(filePath, md); - built++; - } catch (err) { - console.error('Failed to write ' + meta.id + ': ' + err.message); - failed++; - } - } - - console.log(JSON.stringify({ built, failed })); - " - `, - }); - - try { - const result = JSON.parse(writeResult.stdout.trim()); - totalBuilt += result.built ?? 0; - totalFailed += result.failed ?? 0; - } catch { - // Continue — partial batch - } - } - - // ─── Step 5: Update corpus state ────────────────────────────────────────── - - await task.bash({ - key: "update-corpus-state", - script: ` - set -euo pipefail - TODAY=$(node -e "console.log(new Date().toISOString().slice(0,10))") - - node -e " - const fs = require('fs'); - let state = {}; - try { state = JSON.parse(fs.readFileSync('/agent/brain/meta/corpus-state.json', 'utf8')); } catch {} - - const files = fs.readdirSync('/agent/brain/meta/creatives') - .filter(f => f.endsWith('.md') && f !== 'PLAYBOOK.md'); - - state.totalCreatives = files.length; - state.lastBuildDate = '$TODAY'; - fs.writeFileSync('/agent/brain/meta/corpus-state.json', JSON.stringify(state, null, 2)); - console.log(files.length + ' creatives indexed'); - " - - # Append to playbook build log - TOTAL=$(node -e " - const fs = require('fs'); - const s = JSON.parse(fs.readFileSync('/agent/brain/meta/corpus-state.json', 'utf8')); - console.log(s.totalCreatives); - ") - echo "" >> /agent/brain/meta/creatives/PLAYBOOK.md - echo "- $TODAY: Build run — \${TOTAL} total creatives indexed" >> /agent/brain/meta/creatives/PLAYBOOK.md - `, - }); - - const buildDate = new Date().toISOString().slice(0, 10); - - return { - totalBuilt, - totalSkipped: manifest.toProcess - totalBuilt - totalFailed, - totalFailed, - batches: manifest.batchCount, - buildDate, - }; -}; From 8794585d5be38c9d2b377b4bf9911e9905b94c88 Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Mon, 20 Jul 2026 13:02:21 +0000 Subject: [PATCH 25/25] docs: align guide with agent-mode package runtime Co-authored-by: vamsi <thedumsieffect@users.noreply.github.com> --- docs/REPOSITORY_AND_CLI_GUIDE.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/REPOSITORY_AND_CLI_GUIDE.md b/docs/REPOSITORY_AND_CLI_GUIDE.md index 6241aa81..11ff535c 100644 --- a/docs/REPOSITORY_AND_CLI_GUIDE.md +++ b/docs/REPOSITORY_AND_CLI_GUIDE.md @@ -217,16 +217,17 @@ Current package layout conventions: - `skills/` is installed as an `agent_skills` directory; - files under `brain/` are mapped explicitly into `agent_brain`; - app source under `apps/<id>/` is installed as an `agent_apps` directory; -- v1 workflow and script files are ordinary resources staged into brain paths, then - registered by an installed skill. +- when a v1 package intentionally ships a workflow or script, it is an ordinary file + resource staged into a target path; the current three-package series avoids + workflow/script Motion execution because of broker scoping. Declared resource source paths must be relative, exist, have the declared file kind, and contain no symlink segment. `..`, leading `/`, and `//` are rejected. The validator does not verify target existence, target writability, or target collisions. `executable: true` and `executablePaths` are installer metadata. The source file does -not need Git executable mode. Current CI accepts the corpus script as mode `100644`; -installer chmod behavior is external and is not verified by this repository. +not need Git executable mode. Installer chmod behavior is external and is not verified +by this repository. ### What package installation does not do @@ -337,7 +338,7 @@ Use this order when command examples disagree: 1. A local parser or executable implementation. 2. Current platform `--help` output on a VM. -3. Current package workflows and skills. +3. Current package skills and instructions. 4. Older use-case skills and README examples. The repository implements validators, `corpus-search`, and several standalone scripts. @@ -350,7 +351,7 @@ Primary command evidence: |---|---| | Package schema | `scripts/validate-runneth-package-index.mjs` | | Package runtime behavior | package READMEs/skills plus live install results; no local installer | -| Current Motion package calls | `packages/*/workflows/`, `packages/*/skills/` | +| Current Motion package calls | `packages/*/skills/` and runtime feedback | | Broader/older Motion calls | `brain-onboard/`, `runneth-classic/`, `paid-strategy-audit/`, `competitor-intel/` | | App lifecycle | app README files and package skills; no local app CLI parser | | Workflow/task/routine | `packages/*/skills/SKILL.md`; external platform CLIs | @@ -426,7 +427,7 @@ repository; confirm those with platform help. ## External `motion` CLI -Current package data workflows treat the Motion commands they invoke as returning an +Current package skills treat the Motion data commands they invoke as returning an envelope on stdout: ```bash @@ -602,9 +603,6 @@ motion meta competitor-ad-insights \ --with-summary ``` -`motion inspo brands --limit 20` appears in one Context Kit workflow but is not -corroborated elsewhere. Confirm it with `motion --help` before depending on it. - ### Older or generic Motion forms Older use cases contain forms such as: