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..11ff535c --- /dev/null +++ b/docs/REPOSITORY_AND_CLI_GUIDE.md @@ -0,0 +1,1063 @@ +# 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, 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 +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; +- 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. 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; 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 + +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 skills and instructions. +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/*/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 | +| 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 skills treat the Motion data 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 \ + --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 + +motion meta insights \ + --date-range last_30d \ + --sort topSpend \ + --limit 100 \ + --glossary-category intended-audience \ + --glossary-category messaging-angle \ + --glossary-category hook-tactic + +motion meta insights \ + --date-range last_30d \ + --sort topSpend \ + --limit 1 \ + --table-kpi thumbstop_rate +``` + +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: + +```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 +``` + +### 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 + +The external platform exposes forms such as: + +```bash +workflow push --name +``` + +### Workflow-backed tasks + +```bash +task add --kind workflow --workflow-id --name "" +task run --id +task wait --run +``` + +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. + +### 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 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." +``` + +Weekly agent-mode 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 scripts + +```bash +bash competitor-intel/migration-helper.sh +bash team-member-memory/migration-helper.sh +``` + +## 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`. +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 + +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/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/meta/corpus-state.json +/agent/brain/meta/creatives/PLAYBOOK.md +``` + +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. + +## 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. diff --git a/package-index.json b/package-index.json index ddfb614d..4212b59c 100644 --- a/package-index.json +++ b/package-index.json @@ -1,5 +1,57 @@ { "schemaVersion": 1, - "indexRevision": "initial-empty", - "packages": [] + "indexRevision": "creative-corpus-0.2.0", + "packages": [ + { + "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.1", + "categories": ["baseline"], + "packageManagerVersion": 1, + "source": { + "type": "github", + "owner": "Motion-Creative", + "repo": "runneth-apps", + "ref": "main", + "path": "packages/context-kit" + }, + "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.1", + "categories": ["creative-strategy"], + "packageManagerVersion": 1, + "source": { + "type": "github", + "owner": "Motion-Creative", + "repo": "runneth-apps", + "ref": "main", + "path": "packages/ad-naming" + }, + "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 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": { + "type": "github", + "owner": "Motion-Creative", + "repo": "runneth-apps", + "ref": "main", + "path": "packages/creative-corpus" + }, + "updatePolicy": "manual", + "uninstallPolicy": "allowed" + } + ] } diff --git a/packages/ad-naming/brain/ad-naming/ad-naming-state.json b/packages/ad-naming/brain/ad-naming/ad-naming-state.json new file mode 100644 index 00000000..b270eae1 --- /dev/null +++ b/packages/ad-naming/brain/ad-naming/ad-naming-state.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "id": "ad-naming", + "name": "Ad Naming", + "lastBuildDate": null, + "lastRefreshed": null, + "lanesRegistered": false, + "refreshRoutineId": null +} diff --git a/packages/ad-naming/brain/ad-naming/kpi-map.md b/packages/ad-naming/brain/ad-naming/kpi-map.md new file mode 100644 index 00000000..d492a1b7 --- /dev/null +++ b/packages/ad-naming/brain/ad-naming/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/ad-naming/naming-decoder.md b/packages/ad-naming/brain/ad-naming/naming-decoder.md new file mode 100644 index 00000000..2d0ab383 --- /dev/null +++ b/packages/ad-naming/brain/ad-naming/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/ad-naming/query-contract.md b/packages/ad-naming/brain/ad-naming/query-contract.md new file mode 100644 index 00000000..677166b0 --- /dev/null +++ b/packages/ad-naming/brain/ad-naming/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..3acce379 --- /dev/null +++ b/packages/ad-naming/instructions/behavior.md @@ -0,0 +1,43 @@ +# 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 behavior + +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 + +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..564d1dd4 --- /dev/null +++ b/packages/ad-naming/package.json @@ -0,0 +1,52 @@ +{ + "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.1", + "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-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..f96e3e79 --- /dev/null +++ b/packages/ad-naming/runneth-package.json @@ -0,0 +1,51 @@ +{ + "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.1", + "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-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..afb2f38c --- /dev/null +++ b/packages/ad-naming/skills/SKILL.md @@ -0,0 +1,149 @@ +--- +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 — Keep Motion work in the agent turn + +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 + +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: <System A / System B> + + | 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 + - 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 + # Motion Query Contract + + ## Always include on every call + - Workspace ID: <id> (use --workspace-id on commands that accept it) + - Click attribution: <value> (--click-attribution-window) + - View attribution: <value> (--view-attribution-window) + + ## Conversion events + | Event | Count key | Cost key | + |---|---|---| + | <name> | <id>_count | <id>_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 "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`. +- 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/context-kit/README.md b/packages/context-kit/README.md new file mode 100644 index 00000000..5e992bef --- /dev/null +++ b/packages/context-kit/README.md @@ -0,0 +1,27 @@ +# 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 `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 + that JS-created elements never get, so scoped rules (including `display:none` on panels) silently do not + apply to dynamically rendered DOM. This board renders cards in JS, so styles must be global. +- **State is fetched at runtime, not imported at build.** `index.astro` fetches + `/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 + +The VM installer reads `package.json`; the runneth-apps index validator reads `runneth-package.json`. +Both are shipped with identical resource lists until that contract is reconciled. diff --git a/packages/context-kit/apps/context-kit/astro.config.mjs b/packages/context-kit/apps/context-kit/astro.config.mjs new file mode 100644 index 00000000..7eb77fa0 --- /dev/null +++ b/packages/context-kit/apps/context-kit/astro.config.mjs @@ -0,0 +1,6 @@ +import { defineConfig } from "astro/config"; + +export default defineConfig({ + base: "/context-kit", + trailingSlash: "never", +}); diff --git a/packages/context-kit/apps/context-kit/buildeth.app.json b/packages/context-kit/apps/context-kit/buildeth.app.json new file mode 100644 index 00000000..a061b113 --- /dev/null +++ b/packages/context-kit/apps/context-kit/buildeth.app.json @@ -0,0 +1,10 @@ +{ + "version": 3, + "name": "context-kit", + "route": "/context-kit", + "conversationId": "__CONVERSATION_ID__", + "workspaceId": "__WORKSPACE_ID__", + "oauthEnabled": true, + "data": { "dir": "data" }, + "static": { "dist": "dist", "index": "index.html" } +} 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/competitors.md b/packages/context-kit/apps/context-kit/data/competitors.md new file mode 100644 index 00000000..029a97c0 --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/competitors.md @@ -0,0 +1,3 @@ +# competitors + +_Not filled in yet. Runneth mirrors this from /agent/brain/brand-audit/<slug>/ when the Context Kit skill drafts 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 new file mode 100644 index 00000000..c582b88c --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/context-kit-state.json @@ -0,0 +1,244 @@ +{ + "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" + } + ], + "items": [ + { + "id": "brand-context", + "label": "Brand context", + "bucket": "A", + "status": "missing", + "why": "Keeps every output unmistakably on-brand.", + "preview": "", + "dataFile": "brand-context.md" + }, + { + "id": "kpis-goal", + "label": "KPIs & goal", + "bucket": "A", + "status": "missing", + "why": "Every readout leads with the metric you care about.", + "preview": "", + "dataFile": "kpis-goal.md" + }, + { + "id": "spend-threshold", + "label": "Spend threshold", + "bucket": "A", + "status": "missing", + "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?" + ] + }, + { + "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", + "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/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/integrations/ad-platform.md b/packages/context-kit/apps/context-kit/data/integrations/ad-platform.md new file mode 100644 index 00000000..9d77b28f --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/integrations/ad-platform.md @@ -0,0 +1,3 @@ +# ad-platform + +_Not filled in yet. When you connect this source or tell Runneth how you want it used, Runneth writes your rules to /agent/brain/context-kit/integrations/ad-platform.md and mirrors them here. Each guide covers: what it applies to, what to use it for, what to avoid, your rules, and any open question._ diff --git a/packages/context-kit/apps/context-kit/data/integrations/asset-library.md b/packages/context-kit/apps/context-kit/data/integrations/asset-library.md new file mode 100644 index 00000000..4312e3f8 --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/integrations/asset-library.md @@ -0,0 +1,3 @@ +# asset-library + +_Not filled in yet. When you connect this source or tell Runneth how you want it used, Runneth writes your rules to /agent/brain/context-kit/integrations/asset-library.md and mirrors them here. Each guide covers: what it applies to, what to use it for, what to avoid, your rules, and any open question._ diff --git a/packages/context-kit/apps/context-kit/data/integrations/data-warehouse.md b/packages/context-kit/apps/context-kit/data/integrations/data-warehouse.md new file mode 100644 index 00000000..f11f0184 --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/integrations/data-warehouse.md @@ -0,0 +1,3 @@ +# data-warehouse + +_Not filled in yet. When you connect this source or tell Runneth how you want it used, Runneth writes your rules to /agent/brain/context-kit/integrations/data-warehouse.md and mirrors them here. Each guide covers: what it applies to, what to use it for, what to avoid, your rules, and any open question._ diff --git a/packages/context-kit/apps/context-kit/data/integrations/reviews.md b/packages/context-kit/apps/context-kit/data/integrations/reviews.md new file mode 100644 index 00000000..0df9ee3d --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/integrations/reviews.md @@ -0,0 +1,3 @@ +# reviews + +_Not filled in yet. When you connect this source or tell Runneth how you want it used, Runneth writes your rules to /agent/brain/context-kit/integrations/reviews.md and mirrors them here. Each guide covers: what it applies to, what to use it for, what to avoid, your rules, and any open question._ 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/positioning.md b/packages/context-kit/apps/context-kit/data/positioning.md new file mode 100644 index 00000000..75990bdc --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/positioning.md @@ -0,0 +1,3 @@ +# positioning + +_Not filled in yet. Runneth mirrors this from /agent/brain/brand-audit/<slug>/ when the Context Kit skill drafts it._ diff --git a/packages/context-kit/apps/context-kit/data/products.md b/packages/context-kit/apps/context-kit/data/products.md new file mode 100644 index 00000000..5d8a94cf --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/products.md @@ -0,0 +1,3 @@ +# products + +_Not filled in yet. Runneth mirrors this from /agent/brain/brand-audit/<slug>/ when the Context Kit skill drafts 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/data/voc.md b/packages/context-kit/apps/context-kit/data/voc.md new file mode 100644 index 00000000..4bcc1b8e --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/voc.md @@ -0,0 +1,3 @@ +# Voice-of-customer + +_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/data/voice.md b/packages/context-kit/apps/context-kit/data/voice.md new file mode 100644 index 00000000..894796e1 --- /dev/null +++ b/packages/context-kit/apps/context-kit/data/voice.md @@ -0,0 +1,3 @@ +# voice + +_Not filled in yet. Runneth mirrors this from /agent/brain/brand-audit/<slug>/ when the Context Kit skill drafts it._ 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..86d75990 --- /dev/null +++ b/packages/context-kit/apps/context-kit/package.json @@ -0,0 +1,12 @@ +{ + "name": "context-kit", + "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..8613d33d --- /dev/null +++ b/packages/context-kit/apps/context-kit/src/pages/index.astro @@ -0,0 +1,270 @@ +--- +// Context Kit board. Client-rendered so it mirrors mutable brain state without a rebuild. +// Tabbed sections, white cards, state carried by status pills, cards open by default. +--- +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Context Kit + + + + + +
+
+

Your Context Kit

+

Your brand, the way Runneth sees you. Confirm what we pulled, review what we drafted, and add the few things only you know. Everything gets saved to your brain and makes every answer better.

+
+
+
+
+ +
+
Already have this in Google Drive or Notion?
+
Say so in chat and Runneth pulls your brand docs directly.
+
+
+
+
+
+
+ + + + diff --git a/packages/context-kit/brain/context-kit/briefing-template.md b/packages/context-kit/brain/context-kit/briefing-template.md new file mode 100644 index 00000000..eb346694 --- /dev/null +++ b/packages/context-kit/brain/context-kit/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/brain/context-kit/business-team.md b/packages/context-kit/brain/context-kit/business-team.md new file mode 100644 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/context-kit-state.json b/packages/context-kit/brain/context-kit/context-kit-state.json new file mode 100644 index 00000000..c582b88c --- /dev/null +++ b/packages/context-kit/brain/context-kit/context-kit-state.json @@ -0,0 +1,244 @@ +{ + "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" + } + ], + "items": [ + { + "id": "brand-context", + "label": "Brand context", + "bucket": "A", + "status": "missing", + "why": "Keeps every output unmistakably on-brand.", + "preview": "", + "dataFile": "brand-context.md" + }, + { + "id": "kpis-goal", + "label": "KPIs & goal", + "bucket": "A", + "status": "missing", + "why": "Every readout leads with the metric you care about.", + "preview": "", + "dataFile": "kpis-goal.md" + }, + { + "id": "spend-threshold", + "label": "Spend threshold", + "bucket": "A", + "status": "missing", + "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?" + ] + }, + { + "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", + "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/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/landing-pages.md b/packages/context-kit/brain/context-kit/landing-pages.md new file mode 100644 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/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/media-buying.md b/packages/context-kit/brain/context-kit/media-buying.md new file mode 100644 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/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/instructions/behavior.md b/packages/context-kit/instructions/behavior.md new file mode 100644 index 00000000..55b4ca42 --- /dev/null +++ b/packages/context-kit/instructions/behavior.md @@ -0,0 +1,56 @@ +# Context Kit package instructions + +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. + +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. + +## Knoweth lane model + +Context Kit registers three lanes on first run. The skill handles registration — do NOT use `user.md` guards. + +| 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 | + +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 + +- 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: 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) + +Fires on every creative-strategy turn. + +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. + +## Bucket A import contract + +Each Auto-filled item uses two sections: +- `## Latest Import From Motion` — most recent value from Motion. +- `## Runneth Instructions` — customer corrections and rules. + +On conflict, follow `Runneth Instructions`. A refresh updates only `Latest Import From Motion`. + +## 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 + +- 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 new file mode 100644 index 00000000..b40dbe68 --- /dev/null +++ b/packages/context-kit/package.json @@ -0,0 +1,87 @@ +{ + "schemaVersion": 1, + "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.1", + "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/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-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 new file mode 100644 index 00000000..6bff8895 --- /dev/null +++ b/packages/context-kit/runneth-package.json @@ -0,0 +1,116 @@ +{ + "schemaVersion": 1, + "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.1", + "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/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-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..48a08c93 --- /dev/null +++ b/packages/context-kit/skills/SKILL.md @@ -0,0 +1,163 @@ +--- +name: context-kit +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. 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 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`: 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` 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). + +## 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 — Keep Motion work in the agent turn + +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) + +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: 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. 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 \ + --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 +- `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 + 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. + +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). 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 + +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` 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 "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`. + +## 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 + +- `## 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/creative-corpus/README.md b/packages/creative-corpus/README.md new file mode 100644 index 00000000..7e1dd8cd --- /dev/null +++ b/packages/creative-corpus/README.md @@ -0,0 +1,44 @@ +# Creative Corpus + +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}__{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 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 summary sections once indexed + +## 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 build the corpus. Run `build my corpus` after install. + +## Architecture notes + +- 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 agent routine. 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..d0eca0d0 --- /dev/null +++ b/packages/creative-corpus/brain/meta/corpus-state.json @@ -0,0 +1,14 @@ +{ + "schemaVersion": 1, + "id": "creative-corpus", + "name": "Creative Corpus", + "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, + "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..72b635de --- /dev/null +++ b/packages/creative-corpus/brain/meta/creatives/PLAYBOOK.md @@ -0,0 +1,122 @@ +# 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, 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}__{sanitized-full-creative-id}.md` + +- `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__507f1f77bcf86cd799439011.md` + +**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 + +```yaml +--- +id: +adName: +filenameConvention: 2 +format: +launchDate: +campaignName: +status: +spendState: # refreshed daily by script +indexedAt: # when this file was first created +--- + +# {ad name} + +## Hook +{Motion's hookOrHeadline section, preserved as closely as returned.} + +## Creative Breakdown +{Motion's creativeBreakdown summary section, if returned.} + +## 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 claims or benefits drawn from the returned summary sections.} + +## 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 and summary sections | 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 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 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 an agent-mode routine on a daily `0 5 * * *` cron. + +1. Pull `motion meta insights --date-range last_7d`. +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). + +**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 +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 new file mode 100644 index 00000000..a6896521 --- /dev/null +++ b/packages/creative-corpus/instructions/behavior.md @@ -0,0 +1,83 @@ +# 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) | +| 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 | +| 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}__{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 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 and creative text +- 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 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 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`, `refreshRoutineId`, and +`filenameConventionVersion`. + +## Rules + +- 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. +- 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 new file mode 100644 index 00000000..95777c0e --- /dev/null +++ b/packages/creative-corpus/package.json @@ -0,0 +1,38 @@ +{ + "schemaVersion": 1, + "id": "creative-corpus", + "name": "Creative Corpus", + "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", + "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-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..877ca73d --- /dev/null +++ b/packages/creative-corpus/runneth-package.json @@ -0,0 +1,37 @@ +{ + "schemaVersion": 1, + "id": "creative-corpus", + "name": "Creative Corpus", + "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": [ + { + "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-skill", + "type": "directory", + "sourcePath": "skills", + "target": { "root": "agent_skills", "path": "creative-corpus" }, + "executablePaths": [] + } + ] +} diff --git a/packages/creative-corpus/skills/SKILL.md b/packages/creative-corpus/skills/SKILL.md new file mode 100644 index 00000000..b35d1774 --- /dev/null +++ b/packages/creative-corpus/skills/SKILL.md @@ -0,0 +1,105 @@ +--- +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, 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 + +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 — 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 \ + --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 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." + +## 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 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 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. +- 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/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) }) }