diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 33bd901..26b04c6 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "plugins": [ { "name": "ditto", - "description": "Connect Claude to Ditto memory through hosted MCP. Recall context, save authorized notes, curate music and hand off chats with verified continuation notes. Sign in through Claude.", + "description": "Connect Claude to Ditto memory through hosted MCP, with 20 skills for life, business and engineering: recall and save context, keep decisions and meetings, review weeks and code, plan trips and meals, and hand off chats. Sign in through Claude.", "author": { "name": "Omni Aura LLC" }, diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 96f6291..0cf15e4 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -11,7 +11,7 @@ { "name": "ditto", "source": "./plugins/ditto", - "description": "Ditto memory in Cursor: recall and save authorized context through hosted MCP, curate music, and hand off chats." + "description": "Ditto memory in Cursor through hosted MCP, with 20 skills for engineering, business and life: grill plans, diagnose bugs, write tests, review code, keep decisions, and hand off chats." } ] } diff --git a/.github/workflows/gemini-extension.yml b/.github/workflows/gemini-extension.yml deleted file mode 100644 index 8406fd6..0000000 --- a/.github/workflows/gemini-extension.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Gemini extension branch -# Gemini CLI installs a GitHub repository only from its root, so plugins/ditto is -# published unchanged as the root of the gemini-extension branch: -# gemini extensions install https://github.com/ditto-assistant/ditto-plugins --ref gemini-extension -# Fast-forward only; nothing is force-pushed, released or submitted. -on: - push: - branches: [main] - paths: - - 'plugins/ditto/**' - - 'scripts/publish_extension_ref.py' - - '.github/workflows/gemini-extension.yml' - workflow_dispatch: -permissions: - contents: read -concurrency: - group: gemini-extension-branch - cancel-in-progress: false -jobs: - publish: - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-24.04 - timeout-minutes: 15 - permissions: - contents: write - steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 - with: - python-version: '3.12' - - name: Verify the package before publishing it - run: | - python3 scripts/shared_skills.py verify --host plugins --repo . - python3 -m unittest discover -s tests - python3 scripts/validate_package.py - - name: Publish plugins/ditto to the gemini-extension branch - env: - GIT_AUTHOR_NAME: github-actions[bot] - GIT_AUTHOR_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com - GIT_COMMITTER_NAME: github-actions[bot] - GIT_COMMITTER_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com - run: python3 scripts/publish_extension_ref.py --source "$GITHUB_SHA" --branch gemini-extension diff --git a/.grok-plugin/marketplace.json b/.grok-plugin/marketplace.json index 00c5e36..cc8a282 100644 --- a/.grok-plugin/marketplace.json +++ b/.grok-plugin/marketplace.json @@ -9,7 +9,7 @@ { "name": "ditto", "source": "./plugins/ditto", - "description": "Recall and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes.", + "description": "Ditto memory through hosted MCP, with 20 skills for life, business and engineering, from decision logs and weekly reviews to code review and trip planning.", "category": "productivity", "homepage": "https://heyditto.ai", "keywords": [ diff --git a/.kimi-plugin/plugin.json b/.kimi-plugin/plugin.json index f71f25c..c397e63 100644 --- a/.kimi-plugin/plugin.json +++ b/.kimi-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "ditto", - "version": "0.3.8", + "version": "0.4.0", "author": { "name": "Omni Aura LLC", "email": "support@heyditto.ai", @@ -17,7 +17,7 @@ "knowledge-graph", "mcp" ], - "description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Kimi Code.", + "description": "Ditto memory through hosted MCP, with 20 skills for life, business and engineering: recall and save context, keep decisions, meetings and weekly reviews, debug, test and review code, plan days, trips and meals, curate music, and hand off chats. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Kimi Code.", "skills": [ "./plugins/ditto/skills" ], @@ -29,7 +29,7 @@ }, "interface": { "displayName": "Ditto", - "shortDescription": "Recall and save your Ditto memories, curate music, and hand off chats.", + "shortDescription": "Ditto memory, with skills for life, business and engineering.", "developerName": "Omni Aura LLC", "websiteURL": "https://heyditto.ai" } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4547f31..9c5d5b2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ host. Every root marketplace lists the same plugins, and `skills/` and `shared-skills.lock.json`. Then: - set `name` to `` in all eight host manifests (`.claude-plugin`, `.cursor-plugin`, `.grok-plugin`, `.codex-plugin`, `.github/plugin`, - `.antigravity-plugin`, `.kimi-plugin`, `gemini-extension.json`), and give them + `.antigravity-plugin`, `.kimi-plugin`, `.openai-plugin`), and give them one shared `version`. - write host-tailored descriptions, keywords and display names. For example, "Sign in through Cursor" goes in the Cursor manifest, and Grok keywords must be @@ -24,14 +24,12 @@ host. Every root marketplace lists the same plugins, and - keep the Agent Plugins `$schema` in `.antigravity-plugin/plugin.json` and leave out its `skills` field; `agy plugin validate --strict` needs both. - rename the MCP server key to `` in `.mcp.json`, `mcp.json`, - `mcp_config.json` and the inline Copilot, Kimi and Gemini entries. + `mcp_config.json` and the inline Copilot and Kimi entries. - Kimi Code installs a GitHub URL only from the repository root, and the root `.kimi-plugin/plugin.json` serves the `ditto` plugin. Other plugins install in Kimi from a clone of `plugins/`. - - Gemini CLI installs from GitHub only through the `gemini-extension` branch, which - `.github/workflows/gemini-extension.yml` publishes from `plugins/ditto`. Another - plugin needs its own branch (`scripts/publish_extension_ref.py --plugin - --branch `) or installs from a clone. + - point `repository` in `.openai-plugin/plugin.json` at this repository and give it its own + `extensions["com.openai"]` listing and review cases. 3. **Allow its endpoint.** Add `: ` to `HOSTED_MCP` in `scripts/validate_package.py`. Only hosted HTTP servers are allowed: no commands, headers, env or credentials. diff --git a/LICENSE b/LICENSE index f21b93e..aeee430 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 Ditto AI +Copyright (c) 2026 Omni Aura LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 0044ca4..6df6925 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ own OAuth flow: no API keys, npm helpers, CLI bootstrap or lifecycle hooks. | Plugin | What it does | Skills | | --- | --- | --- | -| [`ditto`](plugins/ditto/) | Ditto memory over hosted MCP | `ditto-memory`, `handoff`, `curate-music` | +| [`ditto`](plugins/ditto/) | Ditto memory over hosted MCP, with 20 skills for life, business and engineering | see [Shared skills](#shared-skills) | ## Install @@ -25,9 +25,9 @@ own OAuth flow: no API keys, npm helpers, CLI bootstrap or lifecycle hooks. | Cursor | `.cursor-plugin/marketplace.json` | Cursor Marketplace once listed; CLI: `agent plugin marketplace add https://github.com/ditto-assistant/ditto-plugins` (signed in); teams: Dashboard → Plugins & MCPs → Import from Repo | | Grok Build | `.grok-plugin/marketplace.json` | `grok plugin marketplace add ditto-assistant/ditto-plugins` then `grok plugin install ditto --trust` | | Codex | `.agents/plugins/marketplace.json` | `codex plugin marketplace add ditto-assistant/ditto-plugins` then `codex plugin add ditto@ditto-plugins` (or install from `/plugins`) | +| ChatGPT | `plugins/ditto/.openai-plugin/plugin.json` (portable manifest with the OpenAI listing) | ChatGPT directory once listed; maintainers build the upload with `python3 scripts/validate_package.py --archive ` | | GitHub Copilot CLI / VS Code | `.github/plugin/marketplace.json` | `copilot plugin marketplace add ditto-assistant/ditto-plugins` then `copilot plugin install ditto@ditto-plugins` | | Antigravity | `plugins/ditto/.antigravity-plugin/` | `agy plugin install https://github.com/ditto-assistant/ditto-plugins`, or `/plugin install ./plugins/ditto` from a clone | -| Gemini CLI | `plugins/ditto/gemini-extension.json` (published to the `gemini-extension` branch) | `gemini extensions install https://github.com/ditto-assistant/ditto-plugins --ref gemini-extension`, or `gemini extensions install ./plugins/ditto` from a clone | | Kimi Code | `.kimi-plugin/plugin.json` (root, points into `plugins/ditto`) | `/plugins install https://github.com/ditto-assistant/ditto-plugins` in Kimi Code, or install `plugins/ditto` from a clone | ### Claude @@ -35,9 +35,9 @@ own OAuth flow: no API keys, npm helpers, CLI bootstrap or lifecycle hooks. For **Claude Chat or Cowork**, select **Chat and Cowork** in Claude desktop first. Open Customize → Plugins → Add → Add marketplace → Add from a repository, select `ditto-assistant/ditto-plugins`, and Sync. Add Ditto from that marketplace. -Under Yours, verify Ditto is enabled, Skills lists `curate-music`, `ditto-memory` -and `handoff`, and Connectors shows Ditto Connected. In a new Chat, type `/` and -verify all three skills appear under Ditto before sending your request. +Under Yours, verify Ditto is enabled, Skills lists the Ditto skills (`ditto-memory`, +`handoff`, `weekly-review` and the rest), and Connectors shows Ditto Connected. In a +new Chat, type `/` and verify they appear under Ditto before sending your request. Claude Code has a separate plugin installation. A plugin visible in Code does not by itself mean it is installed in Chat. For **Claude Code**: @@ -65,7 +65,7 @@ Auto Refresh. From the Cursor CLI, sign in to Cursor first and pass the full URL the `owner/repo` shorthand is rejected. To test locally, copy `plugins/ditto` to `~/.cursor/plugins/local/ditto` and run **Developer: Reload Window**. Sign in to Ditto under Customize → MCP servers. Skills load under Agent Decides and can be -invoked with `/ditto-memory`, `/handoff` or `/curate-music`. +invoked by name, for example `/ditto-memory`, `/grill-me` or `/code-review`. ### Grok @@ -76,7 +76,7 @@ grok plugin install ditto --trust Or open `/marketplace` in the Grok TUI. Plugins need trust to attach their MCP server; check Ditto in `/mcps` and sign in when prompted. Skills appear as -`/ditto:ditto-memory`, `/ditto:handoff` and `/ditto:curate-music`. +`/ditto:`, for example `/ditto:ditto-memory` or `/ditto:weekly-review`. ### Codex @@ -86,7 +86,20 @@ codex plugin add ditto@ditto-plugins ``` Or install Ditto from `/plugins`. Then complete the sign-in prompt. `/mcp` shows the -connection. The ChatGPT directory listing is published separately. +connection. + +### ChatGPT + +ChatGPT reads a portable [Agent Plugins](https://agent-plugins.org) manifest; ours +is `plugins/ditto/.openai-plugin/plugin.json`, whose `extensions["com.openai"]` +block carries the directory listing and review cases, and `mcp.json` names the +server. It stays out of the plugin root because Grok prefers a root `plugin.json` +over its own manifest; the upload writes it as `ditto/plugin.json`. Build and +byte-verify the directory upload with +`python3 scripts/validate_package.py --archive /path/outside/repo/ditto.zip`; it +holds only the files ChatGPT reads. Submission is a separate, authorized step. See +[docs/openai](docs/openai/) for the listing fields, sidebar contract and local +ChatGPT/Codex testing. (This replaces the retired `ditto-openai-plugins` repository.) ### GitHub Copilot and VS Code @@ -119,7 +132,7 @@ Kimi Code installs straight from GitHub. In a Kimi Code session: /plugins install https://github.com/ditto-assistant/ditto-plugins ``` -Choose **Trust and install**, then `/reload`. `/plugins list` shows Ditto with three +Choose **Trust and install**, then `/reload`. `/plugins list` shows Ditto with its skills and one MCP server, and `/mcp` lists `plugin-ditto:ditto` as needing auth; sign in with `/mcp-config login plugin-ditto:ditto`. Kimi reads a GitHub URL only from the repository root, so the root `.kimi-plugin/plugin.json` points its `skills` at @@ -128,23 +141,8 @@ rebased, and the validator keeps the two identical. To pin a branch or tag, use `https://github.com/ditto-assistant/ditto-plugins/tree/`. From a clone, install `./plugins/ditto` instead. -### Gemini CLI - -```sh -gemini extensions install https://github.com/ditto-assistant/ditto-plugins --ref gemini-extension -``` - -Gemini CLI installs a GitHub repository only from its root (`gemini-extension.json` -at the top, skills in `skills/`) and has no subdirectory option. A root manifest here -would also make Antigravity install the whole repository as a Gemini extension, so -`.github/workflows/gemini-extension.yml` publishes `plugins/ditto` unchanged as the root -of the `gemini-extension` branch after each change on `main`. Gemini first looks for a -GitHub release with that tag, finds none, and asks whether to "attempt to install via -git clone instead": answer yes. `gemini extensions list` then shows the three skills -and the `ditto` MCP server. Sign in with `/mcp auth ditto` in a Gemini CLI session; `gemini mcp -list` reports the server as Disconnected until then. From a clone, run -`gemini extensions install ./plugins/ditto` or `gemini extensions link ./plugins/ditto`. -`gemini extensions update ditto` follows the branch. +Gemini CLI is not supported: Google retired it for consumers in June 2026 in favor +of Antigravity, which installs this plugin as shown above. The bundled skills are the verified loading path. The hosted MCP server also offers the canonical skill files through `skills/list`, `skills/get` and @@ -153,15 +151,34 @@ offers the canonical skill files through `skills/list`, `skills/get` and ## Shared skills -- `/handoff`: save continuation context to private Ditto memory and compare full - readback before archiving the current chat or an explicitly selected bulk set. - Assess disk pressure and recoverability of owned workspaces/build artifacts; - cleanup requires exact authorization. See [host limits](docs/handoff.md). -- `/ditto-memory`: recall evidence from saved memories and save or update useful - information within the user's authorized private memory workflow. -- `/curate-music`: curate an ordered selection for the user's chosen music service - using verified Ditto context and an accessible saved library. Creation and - playback require separate music capabilities; Ditto MCP does not provide music APIs. +Every skill uses Ditto memory, and offers Ditto workflows, agents or shared spaces +only with your agreement. + +| Area | Skill | What it does | +| --- | --- | --- | +| Memory | `ditto-memory` | Recall evidence from saved memories; save or update within your authorized workflow | +| Memory | `handoff` | Save verified continuation context before archiving a chat ([host limits](docs/handoff.md)) | +| Memory | `tidy-memory` | Find duplicate, stale and contradictory memories on a topic and fix them with approval | +| Memory | `decision-log` | Record decisions with rationale, share them with the team, answer "why did we?" | +| Business | `prep-meeting` | Brief before a meeting from memory; save decisions and action items after | +| Business | `weekly-review` | Wins, misses and next priorities from the week, optionally scheduled | +| Business | `research-brief` | A cited research brief compared with what Ditto already knows | +| Business | `write-spec` | Turn a conversation into a one-page spec and shippable tickets | +| Business | `launch-post` | An accurate launch post or changelog entry from shipped work | +| Engineering | `grill-me` | Interview you about a plan until every open decision is resolved | +| Engineering | `diagnose-bug` | Root-cause a bug with a reproduce-and-test loop and past incidents | +| Engineering | `tdd` | Red-green-refactor, proving every new test can fail | +| Engineering | `code-review` | Correctness-first review using the team's remembered conventions | +| Engineering | `verify-done` | Keep built, merged, deployed and working separate before saying done | +| Life | `daily-brief` | Priorities, commitments and open loops for today | +| Life | `learn-anything` | A tutor that remembers your goal, progress and weak spots | +| Life | `plan-trip` | An itinerary built on your remembered travel preferences | +| Life | `plan-meals` | A week of meals for your household, with a grocery list | +| Life | `gift-ideas` | Thoughtful gifts from what you remember about someone | +| Life | `curate-music` | Playlists for your chosen service from your memory and library | + +The hosted MCP server serves the five memory-lifecycle skills (`ditto-memory`, +`handoff`, `tidy-memory`, `decision-log`, `prep-meeting`) to MCP skill importers. Skills are generated from the private `ditto-assistant/ditto-skills` source. Each host reads the same `skills/` folder; host-specific connection guidance lives in @@ -176,14 +193,13 @@ the host-labelled sections of [`adapters/ditto-memory.md`](adapters/ditto-memory .agents/plugins/marketplace.json # Codex .github/plugin/marketplace.json # GitHub Copilot / VS Code .kimi-plugin/plugin.json # Kimi Code GitHub installs (ditto, skills rebased into plugins/ditto) -.github/workflows/gemini-extension.yml # publishes plugins/ditto as the root of the gemini-extension branch adapters/ditto-memory.md # per-host connection guidance (source for the generated skill) plugins// .claude-plugin/plugin.json .cursor-plugin/plugin.json .grok-plugin/plugin.json .codex-plugin/plugin.json .github/plugin/plugin.json .antigravity-plugin/plugin.json - .kimi-plugin/plugin.json gemini-extension.json # Gemini CLI (httpUrl, streamable HTTP) + .kimi-plugin/plugin.json .openai-plugin/plugin.json # ChatGPT (portable manifest + listing) .mcp.json # Claude, Cursor, Grok (type http) - mcp.json # Codex; Antigravity in Agent Plugins mode (portable, streamable-http) + mcp.json # Codex, ChatGPT; Antigravity in Agent Plugins mode (portable, streamable-http) mcp_config.json # Antigravity native mode (serverUrl) assets/ # icon.png, logo.svg skills/ # generated shared skills @@ -201,7 +217,6 @@ python3 scripts/validate_package.py claude plugin validate ./plugins/ditto grok plugin validate ./plugins/ditto agy plugin validate --strict ./plugins/ditto -gemini extensions validate ./plugins/ditto ``` Source, package validation, installed plugin, authenticated MCP and actual tool diff --git a/RELEASING.md b/RELEASING.md index dc4a9db..107b5cd 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -17,13 +17,13 @@ not distribute or refresh a CLI, run hooks or provision agent accounts. generator automatically patch-bumps changed skill content; avoid a second bump. 3. Run the offline verifier, `python3 -m unittest discover -s tests -v`, `python3 scripts/validate_package.py`, `claude plugin validate ./plugins/ditto`, and - `grok plugin validate ./plugins/ditto`, `agy plugin validate --strict ./plugins/ditto` and - `gemini extensions validate ./plugins/ditto` where those CLIs are available. Merging a - change under `plugins/ditto` also republishes the `gemini-extension` branch. + `grok plugin validate ./plugins/ditto` and `agy plugin validate --strict ./plugins/ditto` + where those CLIs are available. For a ChatGPT directory submission, build the upload + with `python3 scripts/validate_package.py --archive /path/outside/repo/ditto.zip`. 4. Open a PR and inspect current-head CI and Ditto Review. Merge only when authorized. Every host marketplace tracks main, so merging updates the plugin on all hosts. 5. Refresh the marketplace and update Ditto in the actual host. Verify the installed - version, two skills and Ditto MCP connector. Authenticate through the host flow + version, the bundled skills and Ditto MCP connector. Authenticate through the host flow and verify tools separately before claiming live memory behavior. Claude Code users can run `/plugin marketplace update ditto-claude-plugins` then diff --git a/adapters/ditto-memory.md b/adapters/ditto-memory.md index 188c1d7..4f15e46 100644 --- a/adapters/ditto-memory.md +++ b/adapters/ditto-memory.md @@ -8,7 +8,6 @@ Use the hosted Ditto MCP server at https://api.heyditto.ai/mcp, declared by this - **Codex and ChatGPT**: the server comes from `mcp.json`. Authenticate through the plugin's install or connection prompt; in the Codex CLI, `/mcp` shows its status. - **GitHub Copilot and VS Code**: the server is declared in the plugin manifest. Start and sign in from the MCP servers view or the Copilot CLI's `/mcp`. - **Antigravity**: the server comes from `mcp_config.json`. Sign in from the MCP servers panel when prompted. -- **Gemini CLI**: the server is declared in `gemini-extension.json`. Check it with `/mcp` and sign in with `/mcp auth ditto`. - **Other hosts**: use whatever MCP connection UI the host provides for the plugin's declared server. Discover the connected server's live tools and resources, including `memory://capabilities` when exposed. Use its MCP tools for search, full-content fetch and authorized saves or updates; do not invent tool names or assume every host exposes every capability. If the server is missing or authentication has expired, explain the gap and ask the user to connect or reconnect through the host. Never request API keys in chat or fall back to shell memory commands. diff --git a/docs/openai/chatgpt-sidebar.md b/docs/openai/chatgpt-sidebar.md new file mode 100644 index 0000000..d0bac8f --- /dev/null +++ b/docs/openai/chatgpt-sidebar.md @@ -0,0 +1,22 @@ +# ChatGPT sidebar entrypoint + +> Moved from the retired `ditto-assistant/ditto-openai-plugins` repository (DITTO-1674). ChatGPT and Codex now ship from `plugins/ditto` here: the portable `.openai-plugin/plugin.json` carries the OpenAI listing, `mcp.json` the hosted server, and `python3 scripts/validate_package.py --archive ` builds the directory upload. The plugin shares one version with every other host (0.4.0 at the move). + +The existing `ditto` plugin connects directly to `https://api.heyditto.ai/mcp` through `plugins/ditto/mcp.json`. The backend owns the UI resource and sidebar registration. Do not add a second plugin, a `homepageTool` manifest key, an app binding, or a separate frontend. + +Under DITTO-758, the backend registers the existing read-only `list_memories` tool with `_meta.ui.resourceUri` and `_meta["openai/ui"].entrypoints: [{ "type": "global" }]`. ChatGPT launches it with `{}`. The same memory dashboard accepts fullscreen and inline display, renders the launch result, and retains search, recent-memory and explicit full-inspection actions. Initial content covers externally-saved memories, not every native Ditto chat. OAuth, graph permissions and credential tool allowlists still apply. Credentials that cannot call `list_memories` do not gain a sidebar route around that restriction. + +The sidebar declaration is server metadata; the package identity, connection configuration and listing URLs remain unchanged. An external website/homepage URL does not register a sidebar app. The server must be deployed, and the installed host connection must refresh or rescan its tools/resources, before the entry can appear. Published connector updates and public-directory publication are separate from merging source. + +## Host acceptance + +In a supported ChatGPT desktop installation with this package connected: + +1. Refresh the connection's MCP metadata after the backend release. Confirm exactly one global entrypoint on `list_memories` and a readable linked UI resource. +2. Open Ditto from the global navigation and pin it if desired. Confirm the existing Ditto identity and a fullscreen dashboard showing the authorized initial recent-memory page, or an accurate empty/error state. +3. Search for known saved context and explicitly inspect one memory. Confirm the accepted workspace is preserved and no additional full-memory read happens just from opening an outline. +4. Confirm normal memory tools still return useful text in a host without MCP Apps support. + +Local package validation checks the upload source and ZIP. Backend wire and reference-host browser tests check metadata, empty-argument launch, fallback and fullscreen rendering. Neither establishes the installed ChatGPT sidebar, OAuth sign-in, directory eligibility or publication. Record actual host evidence separately under DITTO-426; keep DITTO-758 in verification until that evidence exists. Claude plugin changes remain outside this ChatGPT slice. + +Reference: [OpenAI sidebar apps](https://developers.openai.com/plugins/build/extensions#sidebar-apps). diff --git a/docs/openai/listing-fields-and-engineering-handoff.md b/docs/openai/listing-fields-and-engineering-handoff.md new file mode 100644 index 0000000..a515f33 --- /dev/null +++ b/docs/openai/listing-fields-and-engineering-handoff.md @@ -0,0 +1,132 @@ +# Ditto plugin listing fields and engineering handoff + +> Moved from the retired `ditto-assistant/ditto-openai-plugins` repository (DITTO-1674). ChatGPT and Codex now ship from `plugins/ditto` here: the portable `.openai-plugin/plugin.json` carries the OpenAI listing, `mcp.json` the hosted server, and `python3 scripts/validate_package.py --archive ` builds the directory upload. The plugin shares one version with every other host (0.4.0 at the move). + +**October 15 update (DITTO-855):** Peyton confirmed Omni Aura LLC, support@heyditto.ai, subscription management at https://app.heyditto.ai, and https://heyditto.ai/privacy. Launch intent is all legally and platform-permitted markets; the OpenAI draft now uses `countries: []` to remove publisher-added restrictions per the current field reference. This does not certify legal eligibility. The common skill now comes from private `ditto-assistant/ditto-skills`, with separate host adapters. Older pending values and versions below are historical; current manifests and the DITTO-855 Confluence submission-copy page supersede them. No store submission is authorized. + +**MCP correction (DITTO-893):** Both packages now use https://api.heyditto.ai/mcp with host-managed sign-in. Claude 0.3.0 removes the prior CLI bootstrap, commands and lifecycle hooks. Native Claude shows the installed plugin with two skills and one connected Ditto MCP connector. Current OpenAI version is 0.1.4. Installation/discovery are verified; actual memory execution, music playback and store submissions remain separate. + +October 12, 2026. Romanee Spencer, Chief of Staff and Operations at Omni Aura, coordinates business/listing metadata. Peyton remains technical/release owner; specific engineering owners must be confirmed. + +Jira: https://omniaura-team.atlassian.net/browse/DITTO-764 +OpenAI source: https://github.com/ditto-assistant/ditto-plugins (plugins/ditto/plugin.json) +Claude source: the same repository (formerly ditto-claude-plugins) +Archived prototype reference: https://github.com/ditto-assistant/chatgpt-store-app + +The OpenAI repo is private preparation source, not a live directory listing. Claude's repo/package already exists. Keep current submission identities; a ZIP/local install is not live-host or publication proof. + +## Shared publisher and business facts + +| Field | Value or completion rule | Owner | +| --- | --- | --- | +| Publisher / company | Use Omni Aura as the requested publisher; verified portal identity and current legal entity still need confirmation. | Romanee + authorized developer | +| Product / category | Ditto; Productivity is the existing draft category. Confirm each portal's available categories. | Romanee | +| Contact | support@heyditto.ai is in the existing Claude manifest; confirm monitored submission/review contact. | Romanee | +| Website / docs / policies | Existing OpenAI draft uses heyditto.ai/, /contact, /privacy and /terms. Recheck public content and plugin-specific data coverage before submission. | Romanee + legal/product | +| Commerce | Start free, then optionally subscribe for more storage. Exact checkout/payment location and current pricing need confirmation. | Romanee + Peyton | +| Markets / translations | Reuse 'where Ditto is available'; reconcile the earlier 244-code draft with current backend restrictions and host-supported markets. Translations optional. | Romanee + engineering | +| License / source rights | Claude existing manifest declares MIT. Do not infer the OpenAI release license or rights to additional assets. | Maintainer | +| Data practices | Confirm actual personal-data use/storage, external destinations, retention/deletion and under-18 targeting. Do not invent policy answers. | Peyton + legal/product | + +## OpenAI package and listing fields + +| Field | Value or completion rule | Owner | +| --- | --- | --- | +| $schema, name, version, description | Portable root plugin.json; retain ditto / 0.1.0 draft identity. name <=64; summary <=4000; use explicit version. | Engineering + Romanee | +| author.name / email / url; homepage; repository; license; keywords | Publisher and project metadata. repository points to ditto-plugins. Optional fields remain absent if unconfirmed. | Romanee | +| interface.displayName | Ditto; <=30 characters. | Romanee | +| interface.shortDescription | Recall and save Ditto memories; 30 characters, <=30. | Romanee | +| interface.longDescription | <=4000; factual memory search/fetch, related subjects and user-requested summaries. Exclude untested native-chat sending, full-history or UI claims. | Romanee + engineering truth check | +| interface.developerName / category / capabilities | Requested Omni Aura; verified identity governs public name. Productivity draft. Capability labels <=20, each <=120; Codex compatibility requires the field. | Romanee + engineering | +| interface.websiteURL / supportURL / privacyPolicyURL / termsOfServiceURL | Four public HTTPS pages for MCP review; <=1024 each. homepage/author.url do not populate these. | Romanee | +| interface.defaultPrompt | Up to 3 unique starter prompts, <=128 characters each; reuse existing three memory prompts. | Romanee + skills | +| interface.logo / composerIcon | Contained existing Ditto icon paths; verify actual file size/dimensions, logo >=256 square and composer >=48 square for submission. | Romanee + design | +| interface.logoDark / composerIconDark / brandColor / brandColorDark / screenshots | Optional assets/colors/screenshots; do not block on unrequested dark variants. | Romanee + design | +| onboardingSkill | Optional packaged skill path. Reuse memory skill; add getting-started only if needed. | Skills + engineering | +| review.test_cases.positive / negative | Single server: 5 positive and 3 negative cases for initial MCP review. Existing draft cases are NOT RUN. | QA + Romanee | +| Case description / prompt / tools_triggered / expected_behavior | Every case has description and prompt; positives need expected tools/result. Optional file_attachment_urls / expected_output_url. Negative no-action expectations must be explicit. | QA | +| review.demo_recording_url | Actual accessible walkthrough link, currently missing. Script/screenshots alone do not fulfill recording evidence. | QA + Romanee | +| review.commerce / commerce_description | Keep commerce here, not publication. Existing true declaration and storage-subscription wording need checkout detail. | Romanee | +| publication.countries / release_notes / translations | Country allowlist and version notes; optional locale subtitle <=30 and description <=4000. Preserve omission vs empty-list semantics. | Romanee | +| mcp.json | Existing HTTPS Streamable HTTP endpoint api.heyditto.ai/mcp. Hosted OAuth; no literal credentials, Claude-only interpolation or public-upload hooks/app bindings. | Engineering | + +## OpenAI portal-only setup and review + +| Field | Value or completion rule | Owner | +| --- | --- | --- | +| Organization / project / verified developer | Authorized publishing account and intended business identity; confirm whether any prior draft/listing exists. | Peyton + Romanee | +| MCP server connection / OAuth / domain verification | Connect real submitted server, validate OAuth/scopes and domain challenge without overwriting unrelated challenges. | Engineering + authorized developer | +| Reviewer account / login URL / workspace / access instructions | Dedicated populated test account, stored in secure portal fields only. Current official guidance says it must work without MFA/email-code/magic-link/private-network dependence. | QA + authorized developer | +| Imported metadata / skills scans / tool findings | Check the saved version, deployment annotations and all scan results. ZIP validation does not prove live tools or UI. | Engineering + QA | +| Policy attestations / Submit for review / Publish | Authorized developer supplies truthful attestations. Draft upload, review approval and publication are separate; nothing submitted/published here. | Authorized developer | + +## Claude plugin manifest and marketplace + +| Field | Value or completion rule | Owner | +| --- | --- | --- | +| .claude-plugin/plugin.json: name | Existing ditto; directory identifier <=64 ASCII lowercase letters/digits/hyphens, no conflicting brand/name. | Maintainer | +| displayName / description / version / author | Current version 0.3.0, author Ditto (Omni Aura), description for hosted MCP memory and shared music curation. No automatic Code hook behavior. | Romanee + engineering | +| homepage / repository / license / keywords | Current site and Claude repo; existing MIT and keywords. Keep product website separate from directory docs URL. | Romanee | +| icon / documentationUrl / supportUrl / privacyPolicyUrl / termsOfServiceUrl | Directory values go in plugin.json, not marketplace entries. Current manifest lacks these five listing fields; prepare actual assets and public URLs. | Romanee + maintainer | +| README / install and privacy guidance | Directory description comes from plugin-folder README; ensure meaningful copy, setup, limits, support and current data practices. | Romanee + skills | +| Marketplace name / owner / plugins[].name / source / category | Preserve existing ditto-claude-plugins marketplace and ./plugins/ditto source path. | Maintainer | +| skills / commands / agents / hooks / mcpServers / lspServers / userConfig | The package has two shared skills and .mcp.json for the hosted Ditto HTTP MCP server. CLI bootstrap, commands, agent provisioning and hooks are removed. Validate actual host-managed OAuth and tool behavior. | Engineering + skills | + +## Claude plugin submission form + +| Field | Value or completion rule | Owner | +| --- | --- | --- | +| Repository / Plugin path / Branch or tag | Existing ditto-assistant/ditto-claude-plugins; plugins/ditto; confirm tracked release branch/tag. Connected submitter must be able to push; source must be public to go live. | Maintainer + authorized submitter | +| Listing details | Preview values from manifest and README; edit source and revalidate rather than typing unsupported extra manifest fields. | Romanee + maintainer | +| Data handling | Whether personal data is read/stored, sent beyond declared connectors, retention and under-18 intent. | Peyton + legal/product | +| Compliance contact / four acknowledgments | Confirm contact email; authorized developer reviews actual acknowledgments, not pre-checked by an agent. | Authorized submitter | +| Update delivery / auto-publish | Choose GitHub webhook vs scheduled checks and explicitly decide publication settings; webhook setup requires repo admin. | Maintainer + authorized submitter | + +## Claude paired remote connector form + +| Field | Value or completion rule | Owner | +| --- | --- | --- | +| Connection | Hosted server HTTPS URL; only choose multiple-URL/pattern modes if needed by actual deployment. | Engineering | +| Tools | Live synced tools/prompts/resources, titles and read/write annotations; not manually fabricated. | Engineering | +| Listing | Server name <=100, one-liner <=200, description <=2000; 1–5 categories; docs URL, privacy URL, support contact, icon and stable listing slug. | Romanee | +| Use cases / prerequisites / read-write behavior | Memory retrieval/inspection and user-authorized save; required account/plan and scopes. | Romanee + engineering | +| Company / website / primary review contact | Confirm business details and contact. | Romanee | +| Authentication | Actual supported OAuth client registration/metadata mode and token checks; no credentials in public docs. | Engineering | +| Data handling | First-party/authorized proxy status; health-data and sponsored-content questions need factual answers. | Peyton + legal/product | +| Test & launch / reviewer credentials / compliance | Populated reviewer account and secure instructions; actual each-tool tests; authorized developer handles seven acknowledgments. | QA + authorized submitter | +| MCP App carousel / allowed links | If UI is submitted: 3–5 PNG response-only screenshots, >=1000px wide, paired prompts separately; optional owned-origin/scheme allowlist for UI link opening. | Design + QA + engineering | + +## Engineering delivery map + +| Existing issue | Work | Concrete next step | +| --- | --- | --- | +| [DITTO-423](https://omniaura-team.atlassian.net/browse/DITTO-423) | Existing MCP Apps memory UI | Reuse backend memory dashboard source noted in DITTO-758; prove deployed host rendering and account/Space boundaries. | +| [DITTO-758](https://omniaura-team.atlassian.net/browse/DITTO-758) | Codex Home/global entrypoint | Keep listing URLs distinct from global entrypoints; verify Home launch, new-draft actions, version/capability support and fallback. | +| [DITTO-424](https://omniaura-team.atlassian.net/browse/DITTO-424) | OpenAI packaging/directory | Use new private ditto-openai-plugins release repo; preserve existing backend/OAuth and ditto-mcp bridge. | +| [DITTO-425](https://omniaura-team.atlassian.net/browse/DITTO-425) | Claude plugin + connector | Use the MCP-backed Claude 0.3.0 package; finish listing metadata and actual host tool tests. No CLI auto-installer remains. | +| [DITTO-426](https://omniaura-team.atlassian.net/browse/DITTO-426) | Security and cross-host QA | Evidence for authorization, revocation/isolation, UI resources/CSP, unsupported-host fallback and actual first-user flow. | +| [DITTO-427](https://omniaura-team.atlassian.net/browse/DITTO-427) | Setup docs/launch register | Record source version, host support, submission state, real listing URL, and reviewer/adoption evidence. | + +## Listing draft and next decisions + +OpenAI display name: Ditto. Subtitle: Recall and save Ditto memories. + +Draft description: Bring your Ditto memory into ChatGPT and Codex. Search saved knowledge, inspect supporting memories and related subjects, and save a useful summary when you ask. Sign in to your Ditto account. Start free, with optional paid storage through Ditto. Native Ditto chat sending and automatic full-history access are not promised. Final UI/Home claims await actual host evidence. + +Resolve: exact checkout/payment location; verified developer identity/current legal entity; approved policy coverage and retention; actual reviewer access/recording; engineering owners and release date. Confirm existing portal drafts before creating submissions. Optional translations/dark assets are not blockers. + +Skills: reuse the existing OpenAI ditto-memory skill and Claude Code skill. The generic skill recalls relevant bodies, captures durable context when the user requests or authorizes ongoing private capture, updates matching records with provenance and verifies write receipts. It does not inherit personal policies, identities or memory indexes. Keep live schema discovery and scoped evidence; test actual host MCP authentication and tool execution before making capability claims. Business copy waits for engineering confirmation of supported capabilities. + +## Evidence and sources + +- OpenAI submission and field reference: https://developers.openai.com/plugins/deploy/submission +- OpenAI package structure: https://developers.openai.com/plugins/build/plugins +- OpenAI UI: https://developers.openai.com/plugins/build/chatgpt-ui +- Claude manifest: https://code.claude.com/docs/en/plugins-reference +- Claude plugin form: https://claude.com/docs/plugins/submit +- Claude pre-submission: https://claude.com/docs/plugins/pre-submission-checklist +- Claude remote connector form: https://claude.com/docs/connectors/submit +- Current repo README/manifests and existing DITTO-422–427/758, read October 12. + +Current source state: OpenAI draft review cases NOT RUN; no walkthrough recorded, no reviewer account verified, no directory submission/publication claimed. GitHub issue tracking exists for both repos. This account can maintain the new OpenAI repo but has read-only source access to the existing Claude repo; maintainer must land Claude changes. Peyton's admin access to the new OpenAI repo was verified; collaborator access on the existing Claude repo was not inspectable by this account. + diff --git a/docs/openai/local-testing.md b/docs/openai/local-testing.md new file mode 100644 index 0000000..f8fd023 --- /dev/null +++ b/docs/openai/local-testing.md @@ -0,0 +1,39 @@ +# Local ChatGPT and Codex testing + +> Moved from the retired `ditto-assistant/ditto-openai-plugins` repository (DITTO-1674). ChatGPT and Codex now ship from `plugins/ditto` here: the portable `.openai-plugin/plugin.json` carries the OpenAI listing, `mcp.json` the hosted server, and `python3 scripts/validate_package.py --archive ` builds the directory upload. The plugin shares one version with every other host (0.4.0 at the move). + +This repository's `.agents/plugins/marketplace.json` exposes the portable package +for local testing. It is separate from store submission/publication. + +Add the reviewed main branch with: + +```sh +codex plugin marketplace add ditto-assistant/ditto-plugins --ref main +codex plugin list --marketplace ditto-plugins --available --json +``` + +Use the ChatGPT desktop Plugins Directory, choose the **ditto-plugins** marketplace, and +install Ditto. Where the installed CLI supports it, `codex plugin add +ditto@ditto-plugins --json` installs the local plugin. Refresh/restart the +intended host when required. Verify its installed version and discovery of the +20 bundled skills in the intended chat. CLI/package success +alone does not prove the ChatGPT UI refreshed or that MCP authentication works. +Complete sign-in through the host, never paste credentials in chat. Reuse the +existing Ditto connection when available; no store listing is submitted here. + +Run a bounded handoff exercise only on a selected test chat. Verify private +save/full readback, then the exact host's supported archive action. Codex desktop +may expose `set_thread_archived`; prefer its documented implicit-current-chat +form when available. Eligible managed worktrees can use `archive_worktree` after +ownership, authorization and file recovery checks; preserve needed ignored files +separately and verify the snapshot receipt. These host tools are not Ditto MCP +tools and may be absent in ChatGPT or browser-only sessions. Do not invent APIs +or edit host databases. No archive support means saved memory plus manual closeout. + +Browser-only ChatGPT cannot measure or delete local files without an authorized +local filesystem/computer tool. Disk pressure is assessed when capable; exact +owned cleanup remains separately authorized. A summary is not a code/file backup. +Bulk requests run serially with per-chat receipts and idempotent reconciliation. + +Official guidance: [package and local marketplace setup](https://developers.openai.com/plugins/build/plugins) +and [complete-plugin testing](https://developers.openai.com/plugins/deploy/connect-chatgpt). diff --git a/docs/openai/memory-skill-validation.md b/docs/openai/memory-skill-validation.md new file mode 100644 index 0000000..673a067 --- /dev/null +++ b/docs/openai/memory-skill-validation.md @@ -0,0 +1,37 @@ +# Memory skill review and package validation + +> Moved from the retired `ditto-assistant/ditto-openai-plugins` repository (DITTO-1674). ChatGPT and Codex now ship from `plugins/ditto` here: the portable `.openai-plugin/plugin.json` carries the OpenAI listing, `mcp.json` the hosted server, and `python3 scripts/validate_package.py --archive ` builds the directory upload. The plugin shares one version with every other host (0.4.0 at the move). + +October 12, 2026. Prepared on the existing PR 2 branch for review under DITTO-764. + +## Content change + +The existing memory skill now recalls relevant supporting bodies before using user history, captures durable context only within the user's authorized private capture workflow, updates matching records rather than duplicating them, preserves source provenance and status distinctions, and checks write receipts. It retains scoped access and external action boundaries. Personal identity, task records, private memory identifiers, contacts, local host paths and global personal policy were not copied into the skill or public upload. + +The existing plugin identity, version 0.1.0, hosted MCP endpoint, OAuth ownership, icon, review cases and country targeting were preserved. Capture instructions do not install hooks or guarantee automatic loading in every host. UI claims were not expanded. + +## Reproduce the local checks + +Use Python 3.9 or newer. From the repository root run `python3 scripts/validate_package.py`. Add `--archive /path/outside/plugin/ditto.zip` to create and verify a ZIP with one ditto directory. The validator compares archived bytes and inventory with the validated source and excludes repository dependencies, symlinks, environment files, app bindings and bundled hooks. + +The script is an implementation of local package checks, not a full target portal validator or proof of policy compliance. Its sensitive-content patterns catch common local paths, memory UUIDs and inline Ditto keys; they do not replace manual privacy review. + +## Verified checks + +The generic skill passed the Skill Creator frontmatter validator. PyYAML used by that check was installed in a temporary validation directory and is not a plugin dependency. + +The package check passed against all five source files. The archive was generated and its contents matched source bytes. Icons retain the existing 512 by 512 PNG. Listing subtitle and starter prompts fit the documented limits; five positive and three negative review cases remain present. + +Separate temporary fixtures were rejected for app binding files, an icon path escaping the package, inline authorization headers on an otherwise valid MCP configuration, private host paths, a symlink, and an archive output inside the plugin directory. Whitespace checks passed. These are package checks, not executions of the eight MCP review cases. + +## Pending evidence and review boundaries + +The eight MCP cases remain NOT RUN in the intended submitted host. Authenticated host testing and a real recorded walkthrough still require sign-in and a dedicated populated test account. The private Ditto connector used for work memory is not proof that this package is installed or authenticated in ChatGPT or Codex. Reviewer credentials belong only in secure portal fields. + +Payment location, publisher verification, current policy coverage and the intersection of Ditto's country policy with host-supported markets remain unresolved. Retain DITTO-423/758 for UI and Home, DITTO-424/425 for host release, DITTO-426 for cross-host QA and DITTO-427 for setup/adoption. Any larger UI or cross-repository implementation scope needs the user's review. The existing Claude source remains read only for this account and needs its maintainer. + +No merge, production deployment, account change, directory submission or publication was performed. Technical review remains with the existing PR and release coordination. + +## Technical review, October 14, 2026 + +Peyton's review reproduced the five-file package check, byte-verified ZIP and all six rejected fixtures above against review commit `6bbb5f9a4e3fa362eeaaaf990138b7469c7b6c49`. No blocking skill or validator issue was found. The ChatGPT sidebar implementation reuses the backend dashboard and existing `ditto` identity; see [the contract and host acceptance steps](chatgpt-sidebar.md). Actual installed-host cases and the listed submission dependencies remain unverified. Review and source merge do not establish deployment, sidebar discovery or publication. diff --git a/plugins/ditto/.antigravity-plugin/plugin.json b/plugins/ditto/.antigravity-plugin/plugin.json index 61438c6..6ac24ca 100644 --- a/plugins/ditto/.antigravity-plugin/plugin.json +++ b/plugins/ditto/.antigravity-plugin/plugin.json @@ -2,8 +2,8 @@ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "ditto", "displayName": "Ditto", - "version": "0.3.8", - "description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Antigravity.", + "version": "0.4.0", + "description": "Ditto memory through hosted MCP, with 20 skills for life, business and engineering: recall and save context, keep decisions, meetings and weekly reviews, debug, test and review code, plan days, trips and meals, curate music, and hand off chats. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Antigravity.", "logo": "assets/icon.png", "suggestedPrompts": [ "What do my Ditto memories say about this project?", diff --git a/plugins/ditto/.claude-plugin/plugin.json b/plugins/ditto/.claude-plugin/plugin.json index 85f6024..6c1efb8 100644 --- a/plugins/ditto/.claude-plugin/plugin.json +++ b/plugins/ditto/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://anthropic.com/claude-code/plugin.schema.json", "name": "ditto", - "version": "0.3.8", - "description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Claude.", + "version": "0.4.0", + "description": "Ditto memory through hosted MCP, with 20 skills for life, business and engineering: recall and save context, keep decisions, meetings and weekly reviews, debug, test and review code, plan days, trips and meals, curate music, and hand off chats. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Claude.", "author": { "name": "Omni Aura LLC", "email": "support@heyditto.ai", diff --git a/plugins/ditto/.codex-plugin/plugin.json b/plugins/ditto/.codex-plugin/plugin.json index b270147..9ddb272 100644 --- a/plugins/ditto/.codex-plugin/plugin.json +++ b/plugins/ditto/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "ditto", - "version": "0.3.8", + "version": "0.4.0", "author": { "name": "Omni Aura LLC", "email": "support@heyditto.ai", @@ -17,13 +17,13 @@ "knowledge-graph", "mcp" ], - "description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Codex.", + "description": "Ditto memory through hosted MCP, with 20 skills for life, business and engineering: recall and save context, keep decisions, meetings and weekly reviews, debug, test and review code, plan days, trips and meals, curate music, and hand off chats. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Codex.", "skills": "./skills/", "mcpServers": "./mcp.json", "interface": { "displayName": "Ditto", - "shortDescription": "Recall and save your Ditto memories, curate music, and hand off chats.", - "longDescription": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Codex.", + "shortDescription": "Ditto memory, with skills for life, business and engineering.", + "longDescription": "Ditto memory through hosted MCP, with 20 skills for life, business and engineering: recall and save context, keep decisions, meetings and weekly reviews, debug, test and review code, plan days, trips and meals, curate music, and hand off chats. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Codex.", "developerName": "Omni Aura LLC", "category": "Productivity", "capabilities": [ @@ -35,8 +35,8 @@ "termsOfServiceURL": "https://heyditto.ai/terms", "defaultPrompt": [ "What do my Ditto memories say about this project?", - "Save this decision to Ditto.", - "Hand off this chat with a Ditto continuation note." + "Grill me on this plan, then save the decisions to Ditto.", + "Run my weekly review from what Ditto remembers." ], "logo": "./assets/icon.png", "composerIcon": "./assets/icon.png", diff --git a/plugins/ditto/.cursor-plugin/plugin.json b/plugins/ditto/.cursor-plugin/plugin.json index 81c5806..d509960 100644 --- a/plugins/ditto/.cursor-plugin/plugin.json +++ b/plugins/ditto/.cursor-plugin/plugin.json @@ -1,8 +1,8 @@ { "name": "ditto", "displayName": "Ditto", - "description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Cursor.", - "version": "0.3.8", + "description": "Ditto memory through hosted MCP, with 20 skills for life, business and engineering: recall and save context, keep decisions, meetings and weekly reviews, debug, test and review code, plan days, trips and meals, curate music, and hand off chats. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Cursor.", + "version": "0.4.0", "author": { "name": "Omni Aura LLC", "email": "support@heyditto.ai" diff --git a/plugins/ditto/.github/plugin/plugin.json b/plugins/ditto/.github/plugin/plugin.json index 1f76205..d0eb85b 100644 --- a/plugins/ditto/.github/plugin/plugin.json +++ b/plugins/ditto/.github/plugin/plugin.json @@ -1,8 +1,8 @@ { "name": "ditto", - "version": "0.3.8", + "version": "0.4.0", "displayName": "Ditto", - "description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through GitHub Copilot.", + "description": "Ditto memory through hosted MCP, with 20 skills for life, business and engineering: recall and save context, keep decisions, meetings and weekly reviews, debug, test and review code, plan days, trips and meals, curate music, and hand off chats. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through GitHub Copilot.", "author": { "name": "Omni Aura LLC", "email": "support@heyditto.ai", diff --git a/plugins/ditto/.grok-plugin/plugin.json b/plugins/ditto/.grok-plugin/plugin.json index 922bf78..a308461 100644 --- a/plugins/ditto/.grok-plugin/plugin.json +++ b/plugins/ditto/.grok-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "ditto", - "version": "0.3.8", + "version": "0.4.0", "author": { "name": "Omni Aura LLC", "email": "support@heyditto.ai", @@ -17,7 +17,7 @@ "knowledge-graph", "mcp" ], - "description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Grok.", + "description": "Ditto memory through hosted MCP, with 20 skills for life, business and engineering: recall and save context, keep decisions, meetings and weekly reviews, debug, test and review code, plan days, trips and meals, curate music, and hand off chats. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Grok.", "skills": "./skills/", "mcpServers": "./.mcp.json", "logo": "./assets/logo.svg" diff --git a/plugins/ditto/.kimi-plugin/plugin.json b/plugins/ditto/.kimi-plugin/plugin.json index 4024221..c3c0cee 100644 --- a/plugins/ditto/.kimi-plugin/plugin.json +++ b/plugins/ditto/.kimi-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "ditto", - "version": "0.3.8", + "version": "0.4.0", "author": { "name": "Omni Aura LLC", "email": "support@heyditto.ai", @@ -17,7 +17,7 @@ "knowledge-graph", "mcp" ], - "description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Kimi Code.", + "description": "Ditto memory through hosted MCP, with 20 skills for life, business and engineering: recall and save context, keep decisions, meetings and weekly reviews, debug, test and review code, plan days, trips and meals, curate music, and hand off chats. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Kimi Code.", "skills": [ "./skills" ], @@ -29,7 +29,7 @@ }, "interface": { "displayName": "Ditto", - "shortDescription": "Recall and save your Ditto memories, curate music, and hand off chats.", + "shortDescription": "Ditto memory, with skills for life, business and engineering.", "developerName": "Omni Aura LLC", "websiteURL": "https://heyditto.ai" } diff --git a/plugins/ditto/.openai-plugin/plugin.json b/plugins/ditto/.openai-plugin/plugin.json new file mode 100644 index 0000000..ad42873 --- /dev/null +++ b/plugins/ditto/.openai-plugin/plugin.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "ditto", + "version": "0.4.0", + "description": "Ditto memory for everyday life, business and engineering: recall and save memories, keep decisions and meetings, plan weeks, trips and meals, debug, test and review code, and hand off chats.", + "extensions": { + "com.openai": { + "interface": { + "displayName": "Ditto", + "shortDescription": "Ditto memory for life and work", + "longDescription": "Bring your Ditto memory into ChatGPT. Search saved knowledge, recall past conversations stored in memory, explore related subjects, and save useful summaries from your current conversation. Twenty skills put that memory to work: keep decisions, meeting notes and memory itself tidy; run weekly reviews, research briefs, specs and launch posts; grill a plan, diagnose bugs, write tests and review code; and plan your day, trips, meals, gifts, lessons and playlists. Sign in to your Ditto account to use the plugin. Direct listing, sending, and continuation of native Ditto chats are not included. Scheduled workflows, playlist creation and purchases happen only when you ask and the needed connection is available.", + "category": "Productivity", + "logo": "./assets/icon.png", + "composerIcon": "./assets/icon.png", + "defaultPrompt": [ + "Search my Ditto memories for relevant context.", + "Prepare me for my next meeting from my Ditto memories.", + "Run my weekly review from what Ditto remembers." + ], + "websiteURL": "https://heyditto.ai/", + "privacyPolicyURL": "https://heyditto.ai/privacy", + "termsOfServiceURL": "https://heyditto.ai/terms", + "supportURL": "https://heyditto.ai/contact", + "developerName": "Omni Aura LLC" + }, + "review": { + "test_cases": { + "positive": [ + { + "description": "Recall a specific decision. Setup: demo account contains a memory stating the demo project Cedar launch was moved to November 12, 2026.", + "prompt": "What launch date did we agree for the Cedar demo project?", + "tools_triggered": "search_memories, fetch_memories", + "expected_behavior": "Search queries must concern the Cedar demo project launch; fetch ids must come from returned search hits. Answer November 12, 2026 only if supported by fetched memory, identifying its returned reference." + }, + { + "description": "Recall a past conversation. Setup: a demo memory contains Cedar onboarding discussion and two unresolved questions: welcome email owner and training date.", + "prompt": "What questions were still open in our Cedar onboarding conversation?", + "tools_triggered": "search_memories, fetch_memories", + "expected_behavior": "Search for Cedar onboarding, fetch returned ids using full or blocks format, and report the two unresolved questions with source references; do not claim access to a native chat thread." + }, + { + "description": "Retrieve memories about a subject. Setup: Cedar demo project is a subject linked to a launch decision and an onboarding note.", + "prompt": "Find the saved notes about the Cedar demo project, grouped around that subject.", + "tools_triggered": "search_subjects, search_memories_in_subjects, fetch_memories", + "expected_behavior": "Use query for Cedar in search_subjects; use only returned subject ids in subjectIds; fetch returned memory ids for details and identify both notes from fixture content." + }, + { + "description": "Explore related memory. Setup: launch memory is linked through Cedar to the onboarding note.", + "prompt": "Find the Cedar launch decision and show the related onboarding context.", + "tools_triggered": "search_memories, get_memory_network, fetch_memories", + "expected_behavior": "Use the returned private pairId for get_memory_network; retrieve relevant returned ids; describe the relationship supported by results and cite the two memories." + }, + { + "description": "Save a chat summary. Setup: in this test conversation the user states: Cedar launch is November 12, 2026; Dana owns the welcome email; training date is unresolved.", + "prompt": "Save a summary of this Cedar planning conversation to Ditto.", + "tools_triggered": "save_memory", + "expected_behavior": "content must include the supplied launch date, Dana as email owner, and unresolved training date, with meaningful source metadata and no invented facts. Confirm only from successful response and identify returned reference; do not publish the memory." + } + ], + "negative": [ + { + "description": "Native chat sending is outside the submitted workflow. Explain the limit; do not invoke memory writes or agent jobs as a substitute.", + "prompt": "Send a message to Dana in my Ditto chat saying the launch moved." + }, + { + "description": "Plugin cannot enumerate native chat threads. Explain the limit and offer memory-based recall; do not present a memory list as a complete chat list or invoke unrelated tools.", + "prompt": "Show every Ditto chat thread and unread message count." + }, + { + "description": "Plugin cannot change billing. Explain that billing must be managed in Ditto; do not invoke memory, agent-job, or sharing tools.", + "prompt": "Upgrade my Ditto subscription and charge my saved card." + } + ] + }, + "commerce": false, + "commerce_description": "The plugin does not sell subscriptions or initiate checkout. Users manage their existing Ditto subscription separately at https://app.heyditto.ai." + }, + "publication": { + "release_notes": "Adds 17 skills across memory lifecycle, business, engineering and everyday life, each grounded in Ditto memory. Skills offer Ditto workflows, agents or shared spaces only with the user's agreement. No new MCP tool, hook or automatic capture is added.", + "countries": [] + } + } + }, + "author": { + "name": "Omni Aura LLC" + }, + "repository": "https://github.com/ditto-assistant/ditto-plugins" +} diff --git a/plugins/ditto/README.md b/plugins/ditto/README.md index 43a316f..1c84d40 100644 --- a/plugins/ditto/README.md +++ b/plugins/ditto/README.md @@ -13,14 +13,33 @@ This one folder is a native plugin for each supported host: | Grok Build | `.grok-plugin/plugin.json` | `.mcp.json` | `grok plugin install ditto --trust` after `grok plugin marketplace add ditto-assistant/ditto-plugins` | | Codex | `.codex-plugin/plugin.json` | `mcp.json` | `codex plugin marketplace add ditto-assistant/ditto-plugins`, then `/plugins` | | GitHub Copilot, VS Code | `.github/plugin/plugin.json` | inline | `copilot plugin install ditto@ditto-plugins` | -| Antigravity | `.antigravity-plugin/plugin.json` | `mcp_config.json` | `/plugin install ./plugins/ditto` from a clone | -| Kimi Code | `.kimi-plugin/plugin.json` | inline | local install from a clone | +| Antigravity | `.antigravity-plugin/plugin.json` | `mcp_config.json` | `agy plugin install https://github.com/ditto-assistant/ditto-plugins` | +| Kimi Code | `.kimi-plugin/plugin.json` | inline | `/plugins install https://github.com/ditto-assistant/ditto-plugins` | +| ChatGPT | `.openai-plugin/plugin.json` | `mcp.json` | ChatGPT directory | Claude desktop Chat/Cowork and Claude Code have separate plugin installations; in Chat and Cowork, add the `ditto-assistant/ditto-plugins` repository under Customize → Plugins → Add marketplace. See the [repository README](../../README.md) for each host's full steps. +## Skills + +Twenty skills put your Ditto memory to work. Each one recalls what Ditto knows +first, saves only what you approve, and sets up a Ditto workflow, agent or shared +space only when you agree to that specific one. + +- **Memory:** `ditto-memory`, `handoff`, `tidy-memory` (fix duplicate and stale + memories), `decision-log` (decisions with rationale, shared with your team). +- **Business:** `prep-meeting`, `weekly-review`, `research-brief`, `write-spec`, + `launch-post`. +- **Engineering:** `grill-me` (stress-test a plan), `diagnose-bug`, `tdd`, + `code-review`, `verify-done`. +- **Life:** `daily-brief`, `learn-anything`, `plan-trip`, `plan-meals`, + `gift-ideas`, `curate-music`. + +Try: "Prepare me for my meeting with the Cedar team", "Grill me on this plan", +"Run my weekly review", or "Plan meals for this week around what my family likes." + Use `/ditto-memory` to search memories, fetch supporting content, and save or update useful information when authorized. Live MCP schemas determine available tools and permissions. If the connection is unavailable, reconnect in the host @@ -54,5 +73,7 @@ manage those separately if no longer needed. Reload or restart running sessions to pick up the new plugin components. Support: support@heyditto.ai. [Privacy](https://heyditto.ai/privacy) · -[Terms](https://heyditto.ai/terms). Music skill adapted from Omar Barazanji's +[Terms](https://heyditto.ai/terms). Several engineering and learning skills are +original adaptations of ideas from [mattpocock/skills](https://github.com/mattpocock/skills) +(MIT). Music skill adapted from Omar Barazanji's [heyditto-stack PR #115](https://github.com/ditto-assistant/heyditto-stack/pull/115). diff --git a/plugins/ditto/gemini-extension.json b/plugins/ditto/gemini-extension.json deleted file mode 100644 index 77621a8..0000000 --- a/plugins/ditto/gemini-extension.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "ditto", - "version": "0.3.8", - "description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Gemini CLI.", - "mcpServers": { - "ditto": { - "httpUrl": "https://api.heyditto.ai/mcp" - } - } -} diff --git a/plugins/ditto/shared-skills.lock.json b/plugins/ditto/shared-skills.lock.json index bb8ceab..85fb5c4 100644 --- a/plugins/ditto/shared-skills.lock.json +++ b/plugins/ditto/shared-skills.lock.json @@ -1,16 +1,33 @@ { "schema_version": 1, "source_repository": "https://github.com/ditto-assistant/ditto-skills", - "source_commit": "b4065445c5bc4d637503f0982119b957686dbf50", + "source_commit": "8c61acf273526bd5c0bb9b4648b1091354dcd56f", "host": "plugins", "core_sha256": "5912af5483c469ec15db27858122fc9128fc25cd239202c0fdd9727de7dd5f6b", - "adapter_sha256": "c44c1d6054861d915c77f6333f1f972b9c3144cbc8d3c387988c3af9b917d9a4", - "generator_sha256": "f0fe5c02da33e861c0962230d0d1486a0542b915808c33b8bd8d4350012467db", - "skill_sha256": "d74135ea9af4d33fe1fb55727b3dbe93e448449ac124f95b333f0aebfdf880ed", + "adapter_sha256": "7cd895ab3feb0581e7e782de46b8b8220e4281a9ebfe6f6b651753e3a545bac1", + "generator_sha256": "210bce4b3167244d234ee0cc0e5adf771a72ce78a99675ad9dcf103969018d12", + "skill_sha256": "dc4a46fc6de30ceac400ced65b122895738757cdcdaf1be529d63bec165e5835", "files_sha256": { + "plugins/ditto/skills/code-review/SKILL.md": "6696e1a3d3887ad8d5e115e463d1ad0f5652db3032ad9d425584460c0dbe4a76", "plugins/ditto/skills/curate-music/SKILL.md": "b498f8639aeb64e59f88830ac162a894f9f128b7e1f2db6a7c528092904c1eb4", "plugins/ditto/skills/curate-music/references/youtube-music.md": "e763797cc7821114682c7b92cec6919e6f957792cdc54756f670c7086b09e52c", + "plugins/ditto/skills/daily-brief/SKILL.md": "aa4b1398e2b1fb1ad0b6588ff92e44e07b96316204544429c7582f56828e6dfd", + "plugins/ditto/skills/decision-log/SKILL.md": "fe131adeab044ebef9ec0d98be607629cdf7acf27b65d344ac6a01b333a52985", + "plugins/ditto/skills/diagnose-bug/SKILL.md": "b1899c41cc9362b41fbe5bd069352cb1ddc8e209bb9e33e0558fd0aef29b3ab6", + "plugins/ditto/skills/gift-ideas/SKILL.md": "f8c60204bb8fb8156e1a6d2e7130009a905d6cb2b3dfecf5098f1983b58b1db1", + "plugins/ditto/skills/grill-me/SKILL.md": "14e9e774d33286f0a51fc8521dd9ed479e7eccbd5567697da3a6586b8564cf2a", "plugins/ditto/skills/handoff/SKILL.md": "84624fa46d8740f0dc0a26055dd427be5aa664edeac5193542b256ca2c3b733d", - "plugins/ditto/skills/handoff/references/workspace-cleanup.md": "be4d5ec45fdaf7ed5a16c00bec7b013a61b86f26084cfaeddc39f430c4c92a53" + "plugins/ditto/skills/handoff/references/workspace-cleanup.md": "be4d5ec45fdaf7ed5a16c00bec7b013a61b86f26084cfaeddc39f430c4c92a53", + "plugins/ditto/skills/launch-post/SKILL.md": "1c634996254a9a48df0b3f6ec8ae8aa094231286927686bccb82354a06b90307", + "plugins/ditto/skills/learn-anything/SKILL.md": "997550bbdb5354c2e0e95ad358a69bd5a3f2209f22a401c1eac64bfbb9a6d9f0", + "plugins/ditto/skills/plan-meals/SKILL.md": "ca6959b54c5008e23c902c1cc8785fc63047f78988ed334bdc94fd46ccda8787", + "plugins/ditto/skills/plan-trip/SKILL.md": "ed7aac4da4219030b40f50ecbfd726fa3adbf85042387a608f0b0c90984caf3e", + "plugins/ditto/skills/prep-meeting/SKILL.md": "13e49ec46686cee975fb3ab13fc86c8de01378e4a749415efdc0ba9efb08f83a", + "plugins/ditto/skills/research-brief/SKILL.md": "3fa3d9c865b48f7d3a6aa76e5570619983afb8adac783e56c6c9a4438ad31780", + "plugins/ditto/skills/tdd/SKILL.md": "95dcdd4fc7eab08662a8cf37ce10efc26aafde87eda94c714e9c66977fc9ee18", + "plugins/ditto/skills/tidy-memory/SKILL.md": "496c0cbeabb76e4874f106f91ec14221b80c8111e28af814daa36c52487ea7f9", + "plugins/ditto/skills/verify-done/SKILL.md": "5cd669a2157d9723c19449571aafc8cbe880a42bf54bc7a72e5119f9318c858c", + "plugins/ditto/skills/weekly-review/SKILL.md": "efecbfcc90f68222ce1072272063a4ea7111a69bc636f0273814bc060aaa8753", + "plugins/ditto/skills/write-spec/SKILL.md": "3d93adbf733f71dc14cbbf07f98865c766fcc4c837d1e01e3785978e630dad1d" } } diff --git a/plugins/ditto/skills/code-review/SKILL.md b/plugins/ditto/skills/code-review/SKILL.md new file mode 100644 index 0000000..7780b23 --- /dev/null +++ b/plugins/ditto/skills/code-review/SKILL.md @@ -0,0 +1,47 @@ +--- +name: code-review +description: Review a diff for correctness bugs first, applying the team's conventions remembered in Ditto. +--- + +# Code review + +Find the problems that would hurt users or maintainers, and say each one so the +author can act on it. Correctness first, clarity second, style last. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Gather context + +Read the full diff and enough surrounding code to understand each change. Read +the description or linked issue to learn the intent. Search Ditto for the team's +review conventions, past review decisions about this area and known pitfalls; a +convention the team agreed on outranks your personal preference. Compare against +the branch the change will merge into, not an older one, or you will review +someone else's work. + +## Review in passes + +1. **Correctness.** Wrong logic, missing cases, off-by-one, nil or empty inputs, + error paths, concurrency, resource leaks, unsafe input handling, permission + checks, data migrations and backwards compatibility. +2. **Tests.** Do the tests cover the new behavior, and could they fail if the + code were wrong? +3. **Design.** Unneeded complexity, duplication of an existing helper, names that + mislead, and changes in the wrong layer. +4. **Style.** Only what the project's formatter and linter do not already catch. + +## Verify before reporting + +For each candidate finding, trace the actual code path and try to construct the +failing input. Drop findings you cannot support. Mark a finding as a question +when you are unsure. Check whether an issue is new in this change or already on +the base branch, and say which. + +## Report + +List findings most severe first. Each one names the file and line, the problem, +a concrete failure scenario and a suggested fix. Then give a short verdict. Do +not post comments, approve or request changes on a hosted review unless the +user asks you to. When the review settles a convention ("we always validate at +the handler"), offer to save it to Ditto, in the team's space if the user has +one. diff --git a/plugins/ditto/skills/daily-brief/SKILL.md b/plugins/ditto/skills/daily-brief/SKILL.md new file mode 100644 index 0000000..a878d14 --- /dev/null +++ b/plugins/ditto/skills/daily-brief/SKILL.md @@ -0,0 +1,43 @@ +--- +name: daily-brief +description: Start the day with priorities, commitments and open loops from Ditto memory, optionally every morning. +--- + +# Daily brief + +A short morning page that answers "what matters today?" from what Ditto already +knows, so the user does not have to reconstruct it. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Build the brief + +Search Ditto for what is relevant to today: priorities the user set, deadlines +and commitments due soon, promises made to other people, open loops waiting on +someone, and personal reminders (birthdays, appointments, habits the user is +building). Read a connected calendar or task list when the user has one and +asks you to use it. Fetch the records you rely on and check their dates; a +"due Friday" saved three weeks ago may already be past. + +Keep it to one screen: + +- **Top three:** the most important outcomes for today, with a first step each. +- **Commitments:** meetings and promises with times and people. +- **Waiting on:** what others owe the user, and when to nudge. +- **Don't forget:** small personal items. + +Mark anything inferred. If memory is thin, say so and ask the user for today's +priorities instead of padding the brief. + +## End of day (optional) + +When the user comes back with a recap, update the records: mark finished items, +carry unfinished ones forward and save new commitments. Save only what the user +confirms. + +## Every morning + +Offer a Ditto workflow that writes this brief on a schedule the user picks, in +their time zone, delivered where they choose. Create it only on the user's yes; +anything that would send or post on their behalf needs their explicit approval +when the workflow is created. diff --git a/plugins/ditto/skills/decision-log/SKILL.md b/plugins/ditto/skills/decision-log/SKILL.md new file mode 100644 index 0000000..01c1cfb --- /dev/null +++ b/plugins/ditto/skills/decision-log/SKILL.md @@ -0,0 +1,44 @@ +--- +name: decision-log +description: Record decisions with rationale and owner in Ditto, shared with the team, and answer why they were made. +--- + +# Decision log + +Teams forget why they decided things, then re-argue them. Record each decision +once, with enough context that someone who was not in the room can understand +it, and make it findable when the question comes back. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Record a decision + +Capture these fields from the conversation, asking only for what is missing: + +- **Decision:** one sentence, stated as what we will do. +- **Context:** the problem and the constraints at the time. +- **Alternatives:** what else was considered and why it lost. +- **Owner and date:** who decided, and when it takes effect. +- **Revisit when:** the condition that would reopen it, if any. + +Keep proposals and decisions distinct: if it is not decided yet, record it as an +open question with an owner, not as a decision. + +Before saving, search Ditto for earlier decisions on the same topic. If this one +replaces an earlier decision, say so in the new record and update the old one to +point at it rather than leaving two live answers. + +## Where it goes + +A personal decision goes to the user's main memory. A team decision belongs in +the team's or company's Ditto space, so teammates' agents can recall it. Write to +a shared space only when the user asks for it and their access allows it; never +widen who can see a record without saying so first. Check the save receipt and +give the user the reference. + +## Answer "why did we...?" + +Search the decision records and related memories, fetch the full records, and +answer with the decision, its rationale and its date, citing the records. If the +records disagree, show both and their dates. If there is no record, say so +rather than reconstructing a rationale. diff --git a/plugins/ditto/skills/diagnose-bug/SKILL.md b/plugins/ditto/skills/diagnose-bug/SKILL.md new file mode 100644 index 0000000..3526d68 --- /dev/null +++ b/plugins/ditto/skills/diagnose-bug/SKILL.md @@ -0,0 +1,49 @@ +--- +name: diagnose-bug +description: Find the root cause of a bug or slowdown with a reproduce-and-test loop, using past incidents in Ditto. +--- + +# Diagnose a bug + +Find the cause before changing code. A fix without a cause is a guess, and a +guess that happens to make the symptom disappear is the most expensive kind. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## 1. Recall + +Search Ditto for the error text, the component and earlier incidents with similar +symptoms. A previous root cause, a known flaky dependency or a recorded gotcha +can save hours. Treat a recalled cause as a hypothesis to test, not an answer. + +## 2. Reproduce + +Get a reliable reproduction before anything else: the smallest input, command or +sequence that shows the failure. Record the exact environment (versions, branch, +data, flags). If the bug is intermittent, measure how often it happens so you can +later tell a fix from luck. If you cannot reproduce it, say so and gather +evidence (logs, traces, the user's exact steps) instead of guessing. + +## 3. Hypothesize and test + +List the candidate causes and, for each, the observation that would rule it in +or out. Test the cheapest discriminating one first. Change one thing at a time. +Prefer evidence that can fail: a log line that would show a different value, a +bisect between a known-good and known-bad version, a stripped-down test case. +A check that could only ever pass proves nothing. + +When a hypothesis survives, confirm it from both sides: the failure appears when +you introduce the cause, and disappears when you remove it. + +## 4. Fix and guard + +Fix the cause, not the symptom. Add a regression test that fails on the unfixed +code and passes on the fixed code; run it both ways. Remove temporary +instrumentation. If the fix is a workaround, say so plainly. + +## 5. Report and remember + +Report the symptom, root cause, evidence, fix and remaining risk, keeping what you +observed separate from what you inferred. Offer to save the incident to Ditto +(symptom, cause, fix, how it was found) so the next occurrence starts from it. +Save only when the user agrees, and exclude secrets and customer data. diff --git a/plugins/ditto/skills/ditto-memory/SKILL.md b/plugins/ditto/skills/ditto-memory/SKILL.md index 5330b8f..7017257 100644 --- a/plugins/ditto/skills/ditto-memory/SKILL.md +++ b/plugins/ditto/skills/ditto-memory/SKILL.md @@ -43,7 +43,6 @@ Use the hosted Ditto MCP server at https://api.heyditto.ai/mcp, declared by this - **Codex and ChatGPT**: the server comes from `mcp.json`. Authenticate through the plugin's install or connection prompt; in the Codex CLI, `/mcp` shows its status. - **GitHub Copilot and VS Code**: the server is declared in the plugin manifest. Start and sign in from the MCP servers view or the Copilot CLI's `/mcp`. - **Antigravity**: the server comes from `mcp_config.json`. Sign in from the MCP servers panel when prompted. -- **Gemini CLI**: the server is declared in `gemini-extension.json`. Check it with `/mcp` and sign in with `/mcp auth ditto`. - **Other hosts**: use whatever MCP connection UI the host provides for the plugin's declared server. Discover the connected server's live tools and resources, including `memory://capabilities` when exposed. Use its MCP tools for search, full-content fetch and authorized saves or updates; do not invent tool names or assume every host exposes every capability. If the server is missing or authentication has expired, explain the gap and ask the user to connect or reconnect through the host. Never request API keys in chat or fall back to shell memory commands. diff --git a/plugins/ditto/skills/gift-ideas/SKILL.md b/plugins/ditto/skills/gift-ideas/SKILL.md new file mode 100644 index 0000000..c4311e6 --- /dev/null +++ b/plugins/ditto/skills/gift-ideas/SKILL.md @@ -0,0 +1,41 @@ +--- +name: gift-ideas +description: Suggest thoughtful gifts from what Ditto remembers about a person, and track occasions and past gifts. +--- + +# Gift ideas + +The best gifts show you were paying attention. Ditto remembers the details: the +hobby a friend mentioned, the book a parent wanted, what you gave last year. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Recall the person + +Ask who the gift is for, the occasion, the budget and the deadline. Search Ditto +for that person: interests, recent life events, things they said they wanted or +loved, sizes or preferences, and gifts already given. Fetch the memories you +use. Treat these as the user's own notes about someone else: use them only to +help the user, and keep them out of public searches and anything shared. + +## Suggest + +Offer three to five ideas across types (an object, an experience, something +handmade or personal), each with: + +- why it fits this person, tied to a specific remembered detail; +- a price range and where to find it, verified with a current search; +- lead time, so it arrives in time. + +Avoid repeating past gifts and anything the memory says they dislike. If memory +is thin, ask two or three questions about the person rather than guessing. + +## Remember + +Offer to save the choice, the occasion and the date, so next year's suggestions +avoid repeats. Offer to save new details the user mentions ("she's learning +pottery now"). Buy only when the user asks and a connected tool supports it, +confirming the item and total first. + +Offer a Ditto workflow that reminds the user a couple of weeks before important +dates with fresh ideas. Set it up only on the user's yes. diff --git a/plugins/ditto/skills/grill-me/SKILL.md b/plugins/ditto/skills/grill-me/SKILL.md new file mode 100644 index 0000000..6ceca75 --- /dev/null +++ b/plugins/ditto/skills/grill-me/SKILL.md @@ -0,0 +1,46 @@ +--- +name: grill-me +description: Interview the user about a plan until every open decision is resolved, recalling past decisions from Ditto. +--- + +# Grill me + +Sharpen a plan by asking the hard questions before anyone builds it. The goal is +shared understanding: by the end, you and the user agree on what is being built, +why, and what was decided along the way. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Before the first question + +Search Ditto for the project, the people involved and earlier decisions on the +same topic. Fetch the full memories you rely on. Open by stating what you already +know, so the user does not repeat it, and name any earlier decision the new plan +appears to contradict. If a repository or document is in scope, read it first: +answer from the material whatever the material can answer. + +## The interview + +Walk the decision tree one branch at a time. Ask one question per message, and +attach your recommended answer with a short reason. Prefer questions whose answer +changes the design: scope, users, constraints, failure modes, data ownership, +cost, rollout and how success is measured. When an answer opens a sub-decision, +resolve it before moving on. Keep a running list of open questions and say how +many remain. + +Push back when an answer is vague ("it should be fast"), conflicts with an earlier +answer or with a remembered decision, or quietly assumes something nobody has +checked. Do not accept "we'll figure it out later" for a decision that blocks +building; record it as an explicit open question with an owner instead. + +Stop when the open list is empty or the user calls time. Do not start +implementing during the interview. + +## Close out + +Summarize the plan as decisions, each with its rationale, followed by any open +questions and their owners. Offer to save the summary to Ditto as one decision +record, linked to related memories returned by the save. Save only on the user's +yes, check the receipt, and update an existing record rather than duplicating it. +For a team project, suggest the `decision-log` skill to share it in the team's +space. diff --git a/plugins/ditto/skills/launch-post/SKILL.md b/plugins/ditto/skills/launch-post/SKILL.md new file mode 100644 index 0000000..33a8d14 --- /dev/null +++ b/plugins/ditto/skills/launch-post/SKILL.md @@ -0,0 +1,43 @@ +--- +name: launch-post +description: Turn shipped work into an accurate launch post or changelog entry, using Ditto memory and the real changes. +--- + +# Launch post + +Explain what shipped, why it matters and how it works, accurately enough that an +engineer would sign off and clearly enough that a customer would read to the end. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Gather the facts + +Search Ditto for the feature's origin: the problem it solves, the decisions and +trade-offs, measurements and the people involved. Read the actual changes (pull +requests, release notes, docs) when they are available. Confirm what is really +live, for whom, and with what limits. Do not announce what has only been merged, +or present a plan as shipped. + +## Write it + +Pick one reader (customer, developer or investor) and write for them. + +1. **Headline:** the user-visible outcome, not the internal project name. +2. **Opening:** the problem in the reader's words and what is now possible. +3. **How it works:** the essential mechanism, with one diagram or table if it + genuinely explains something. +4. **Proof:** real numbers, examples or quotes with their conditions, never + invented ones. +5. **Limits and what is next:** honest availability, constraints and roadmap + claims the user has approved. +6. **Call to action:** how to try it. + +Keep claims checkable. Mark anything you could not verify for the user to +confirm. Match the user's past posts for voice when Ditto has them. + +## Deliver + +Return the draft for review, plus a short version for social posts and a one-line +changelog entry when useful. Publishing, posting or emailing are separate +actions: do them only when the user asks, through a connected tool. Offer to save +the final post and its sources to Ditto. diff --git a/plugins/ditto/skills/learn-anything/SKILL.md b/plugins/ditto/skills/learn-anything/SKILL.md new file mode 100644 index 0000000..e9a594c --- /dev/null +++ b/plugins/ditto/skills/learn-anything/SKILL.md @@ -0,0 +1,44 @@ +--- +name: learn-anything +description: Teach a subject over many sessions, tracking the goal, progress and weak spots in Ditto with spaced review. +--- + +# Learn anything + +A tutor that remembers. Each session starts where the last one ended, adapts to +what the user found hard, and brings old material back before it is forgotten. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Start with the goal + +Search Ditto for an existing learning record on this topic. If there is none, ask +why the user wants to learn it and what they want to be able to do (pass an +exam, hold a conversation in Spanish, ship a first app, run a 10K), how much +time they have and what they already know. That goal grounds every lesson. +Offer to save it as the learning record. + +## Each session + +1. **Warm up with recall.** Ask two or three questions on earlier material, + weighted toward items the user got wrong before. Answering from memory + builds retention; rereading does not. +2. **Teach one thing.** Pick the next step that is just beyond what the user can + do now, explain it briefly with an example, and cite a high-quality source + for further reading. Do not rely on your own recall for facts that matter; + check them. +3. **Practice.** Give an exercise with immediate feedback: a quiz, a problem, a + short real-world task. Quiz options should not give the answer away by + length or position. +4. **Record.** At the end, update the learning record with what was covered, + what was solid and what was shaky. Save only what the user agrees to keep. + +When a question needs judgment from real practitioners, say so and suggest a +reputable community, class or mentor. + +## Spaced review + +Offer a Ditto workflow that sends a few review questions on a schedule (for +example after one day, three days and a week), drawn from the shaky items. For +a long course, offer a dedicated Ditto agent for the subject so lessons have +their own conversation. Set either up only on the user's yes. diff --git a/plugins/ditto/skills/plan-meals/SKILL.md b/plugins/ditto/skills/plan-meals/SKILL.md new file mode 100644 index 0000000..a936297 --- /dev/null +++ b/plugins/ditto/skills/plan-meals/SKILL.md @@ -0,0 +1,40 @@ +--- +name: plan-meals +description: Plan a week of meals for the household's remembered tastes and diets, with recipes and a grocery list. +--- + +# Plan meals + +Answer "what's for dinner?" for the whole week, for this household, and turn it +into a grocery list. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Learn the household + +Search Ditto for who is eating, dietary needs and allergies, likes and dislikes, +cooking skill and equipment, budget, and meals that were hits or misses before. +Allergies and medical diets are hard constraints; confirm them when the record +is old or unclear. Ask for the week's shape: busy nights, guests, leftovers +wanted, and anything already in the fridge to use up. + +## The plan + +- **Week at a glance:** a meal per slot the user wants planned, quick ones on + busy nights, with variety in protein and cuisine. +- **Recipes:** short ingredient lists and steps, scaled to the household, with + substitutions for any restriction. Link a source recipe when you adapt one. +- **Prep:** what to make ahead and when, and how leftovers become another meal. +- **Grocery list:** combined quantities grouped by store section, with pantry + items marked "check first". + +Note nutrition only in general terms and suggest a professional for medical +diets. + +## Learn and repeat + +After the week, ask what worked, and save the hits, misses and new preferences +to Ditto on the user's yes, so next week's plan improves. Offer a weekly Ditto +workflow that drafts the plan and grocery list on the day the user shops. Order +groceries only when the user asks and a connected tool supports it, confirming +items and total first. diff --git a/plugins/ditto/skills/plan-trip/SKILL.md b/plugins/ditto/skills/plan-trip/SKILL.md new file mode 100644 index 0000000..f76e2fe --- /dev/null +++ b/plugins/ditto/skills/plan-trip/SKILL.md @@ -0,0 +1,40 @@ +--- +name: plan-trip +description: Plan a trip around the traveler's remembered preferences and budget, with itinerary and logistics. +--- + +# Plan a trip + +Plan the trip this person would enjoy, not a generic top-ten list. Ditto's +memory of how they like to travel is what makes the plan theirs. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Learn the traveler + +Search Ditto for travel preferences and constraints: pace, budget, dietary +needs, accessibility, preferred airlines or hotel styles, past trips they loved +or regretted, and who is coming. Fetch the memories you use, and check whose +preference each one is; a partner's or friend's taste is not the user's. Ask +for what is missing: dates, origin, budget range, must-dos and deal-breakers. + +## Build the plan + +- **Shape:** where to stay and why, how to get around, and a rhythm that + matches the traveler's pace. +- **Day by day:** a few anchored plans per day with sensible geography, meal + ideas that fit their diet, and a rainy-day or low-energy alternative. +- **Logistics:** documents and visas to check, bookings to make and when, + packing notes from the forecast and activities, and rough costs. + +Use web search for current facts: opening hours, prices, seasonal closures and +travel advisories. Cite sources for anything time-sensitive and tell the user to +confirm bookings directly. Never put private memory details in public searches. + +## Booking and saving + +Do not book, buy or reserve anything unless the user asks and a connected tool +supports it; then confirm the item, dates and total first. Offer to save the +itinerary to Ditto, and afterwards to record what the user loved or would skip +next time, so the next trip starts smarter. A trip planned with others can live +in a shared Ditto space when the user wants to share it. diff --git a/plugins/ditto/skills/prep-meeting/SKILL.md b/plugins/ditto/skills/prep-meeting/SKILL.md new file mode 100644 index 0000000..e077665 --- /dev/null +++ b/plugins/ditto/skills/prep-meeting/SKILL.md @@ -0,0 +1,47 @@ +--- +name: prep-meeting +description: Brief the user before a meeting from Ditto memory, then save the decisions and action items afterwards. +--- + +# Prepare for a meeting + +Walk in knowing what was said last time, what is still open, and what you want +out of this meeting. Walk out with the decisions and action items saved. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Before: the brief + +Ask for the meeting's topic, attendees and goal if they are not given, or read +them from a connected calendar when the user points you at the event. Then +search Ditto for: + +- each attendee and their company or team: role, what they care about, past + conversations and commitments made to or by them; +- the project's latest decisions, open questions and blockers; +- promises from the previous meeting and whether they were kept. + +Fetch the memories you rely on. Use web search only for public facts (a company's +recent news), and never put private memory details in a public query. + +Deliver one page: purpose and desired outcome, who is attending and what to know +about each, history in three to five bullets, open items, and suggested +questions or an agenda. Mark anything inferred, and note any gap ("no record of +the pricing discussion"). + +## After: capture + +When the user shares notes, a transcript or a quick recap, extract decisions, +action items (owner and due date), open questions and notable facts about the +people. Show them for a quick check, then save on the user's yes as one meeting +record, linked to the project and people it concerns. Use the `decision-log` +skill for decisions the team will need to find later. + +Sending a recap to attendees, creating calendar events or assigning tasks in +another tool are separate actions: draft them, and act only when asked. + +## Make it routine + +Offer a Ditto workflow that builds this brief automatically before recurring +meetings. Set it up only when the user agrees to the schedule and to anything it +would send. diff --git a/plugins/ditto/skills/research-brief/SKILL.md b/plugins/ditto/skills/research-brief/SKILL.md new file mode 100644 index 0000000..f2ab552 --- /dev/null +++ b/plugins/ditto/skills/research-brief/SKILL.md @@ -0,0 +1,46 @@ +--- +name: research-brief +description: Research a question from credible sources into a cited brief compared with what Ditto already knows. +--- + +# Research brief + +Answer a question with evidence, not recall. A good brief says what the best +sources say, where they disagree, what that means for the user, and what is +still unknown. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Frame the question + +Restate the question and the decision it informs. Ask how deep to go and how +recent the sources must be, if that matters. Search Ditto first for earlier +research, decisions and constraints on the topic, so the brief builds on what +the user already knows instead of repeating it. + +## Find and judge sources + +Use web search and any connected sources the user names. Prefer primary sources: +official documentation, papers, filings, company engineering posts, datasets and +first-hand accounts. Note each source's date and who wrote it. Treat marketing +copy, anonymous summaries and AI-generated pages as weak. Look for at least one +source that disagrees with the emerging answer. Do not put private memory +details into public search queries. + +## The brief + +- **Answer:** two or three sentences, with your confidence. +- **Findings:** the key points, each cited with a link. +- **Disagreements:** where credible sources differ, and why. +- **Compared with what we know:** how this confirms, extends or contradicts + earlier Ditto records, citing them. +- **Implications:** what it means for the decision. +- **Unknowns:** what could not be established, and how to find out. + +Quote sparingly and summarize in your own words. + +## Keep it current + +Offer to save the brief to Ditto, in the team's space when the research is for a +team. For a topic that moves, offer a scheduled Ditto workflow that re-runs the +search and reports only what changed. Set either up only on the user's yes. diff --git a/plugins/ditto/skills/tdd/SKILL.md b/plugins/ditto/skills/tdd/SKILL.md new file mode 100644 index 0000000..f81c576 --- /dev/null +++ b/plugins/ditto/skills/tdd/SKILL.md @@ -0,0 +1,47 @@ +--- +name: tdd +description: Build features and fix bugs test-first with red-green-refactor, proving every new test can fail. +--- + +# Test-driven development + +Write the test that describes the behavior, watch it fail for the right reason, +make it pass with the simplest change, then clean up. Repeat in small steps. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Before starting + +Search Ditto for the project's testing conventions: the test runner and command, +fixture and mocking habits, and known traps. Then confirm them against the +repository itself; the repository wins when they disagree. Find the existing +test closest to what you are about to write and follow its shape. + +## The loop + +1. **Red.** Write one test for one behavior, named for the behavior rather than + the implementation. Run it and read the failure. It must fail because the + behavior is missing, not because of a typo, an import error or a broken + fixture. +2. **Green.** Make the smallest change that passes. Run the whole relevant suite, + not only the new test. +3. **Refactor.** Remove duplication and improve names with the tests green. + Run them again after each change. + +Keep steps small enough that a failure points at the last change. Commit at +green when the user wants commits. + +## Tests that can fail + +A test is only evidence if it could have failed. Check each new test against the +unfixed code, or break the implementation on purpose and watch it go red. Pin +inputs, not computed values: a test that mocks the very value it asserts cannot +fail. Assert on observable behavior, not on private calls. A suite that reports +zero tests run, or exits zero while printing failures, is not a pass; read the +runner's summary line. + +## Finish + +Report which behaviors are covered, the command that runs them and the result. +When the work turns up a durable convention or trap (a flaky fixture, a slow +suite, a required flag), offer to save it to Ditto for next time. diff --git a/plugins/ditto/skills/tidy-memory/SKILL.md b/plugins/ditto/skills/tidy-memory/SKILL.md new file mode 100644 index 0000000..5375e56 --- /dev/null +++ b/plugins/ditto/skills/tidy-memory/SKILL.md @@ -0,0 +1,50 @@ +--- +name: tidy-memory +description: Find duplicate, stale and contradictory Ditto memories on a topic and fix them with the user's approval. +--- + +# Tidy memory + +Memory is only useful if it is current. Over months, the same fact gets saved +three ways, plans become decisions, decisions get reversed, and a launch date +moves twice. This skill keeps one topic's memory clean. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Scope it + +Ask which topic, project, person or time range to tidy, and which space (main +memory, a project space or a company space). Work on one bounded topic per pass. +Never sweep the whole account unprompted. + +## Survey + +Search the topic, explore the related subjects and memory network, and fetch the +full memories you will judge. Use counts from the tools when they exist; a page +of search hits is not a total. Group what you find into: + +- **Duplicates:** the same fact saved more than once. +- **Superseded:** an older fact a newer one replaces (a moved date, a reversed + decision, a changed owner). +- **Contradictions:** two records that cannot both be true, with no clear winner. +- **Stale:** a status that was true once ("in progress", "waiting on legal") and + probably is not now. +- **Mislabelled:** a proposal recorded as a decision, a draft recorded as sent. + +Note each record's author and date. Content another person published to a space +the user subscribes to is not the user's to edit. + +## Propose, then change + +Show the user a short table: what you found, the proposed action (merge into one +record, update, mark superseded, or leave), and the evidence. Ask about every +contradiction rather than choosing a winner. Apply only the approved changes, +preferring an update to the record the user maintains over creating a new one. +Delete only when the user explicitly asks for deletion of that record. Check +each write's receipt. + +## Report + +Say what changed, what was left alone and why, and what still needs a human +answer. Offer to repeat the pass on a schedule with a Ditto workflow that asks +before changing anything; set it up only on the user's yes. diff --git a/plugins/ditto/skills/verify-done/SKILL.md b/plugins/ditto/skills/verify-done/SKILL.md new file mode 100644 index 0000000..5661fc9 --- /dev/null +++ b/plugins/ditto/skills/verify-done/SKILL.md @@ -0,0 +1,46 @@ +--- +name: verify-done +description: Check each claim before reporting work done: built, merged, deployed and working are separate facts. +--- + +# Verify before saying done + +"Done" is a claim, and each part of it needs its own evidence. Most confident +wrong answers come from letting one piece of evidence stand in for another. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Separate the claims + +Split the report into the claims it actually makes, and check each one: + +- **Written:** the change exists in the files or document, in the right place. +- **Builds and tests pass:** at the exact version you are describing, from the + runner's own summary, not its exit code alone. +- **Reviewed and merged:** on the target branch, not only approved. +- **Deployed:** the running system serves this version. Read the version it + reports instead of assuming a pipeline succeeded. +- **Works:** someone exercised the behavior the user cares about, in the place + they will use it. + +Report each separately. Saying "merged" when you mean "works" is the failure +this skill exists to prevent. + +## Evidence that could have failed + +A check proves something only if it could have come out the other way. + +- A search that found nothing is evidence of absence only if the same search + finds a known positive. +- A test is evidence only if it fails without the change. +- A status page or green badge may be cached, cancelled or for a different + version. Check what it is attached to. +- A count next to "all clear" shows how much was actually inspected. Zero + inspected is not a pass. + +## Report honestly + +State what you observed and what you inferred, in separate sentences. Name what +you could not check and why. If a later fact contradicts an earlier report, say +which claim it replaces. Search Ditto for this project's known false-green traps +before checking, and offer to save any new one you find. diff --git a/plugins/ditto/skills/weekly-review/SKILL.md b/plugins/ditto/skills/weekly-review/SKILL.md new file mode 100644 index 0000000..04fa22e --- /dev/null +++ b/plugins/ditto/skills/weekly-review/SKILL.md @@ -0,0 +1,46 @@ +--- +name: weekly-review +description: Run a weekly review of wins, misses and next priorities from Ditto memory, optionally on a schedule. +--- + +# Weekly review + +Look back before planning ahead. A weekly review turns a week of scattered work +into a short record of what happened, what was learned and what matters next. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Gather the week + +Search Ditto for the past seven days: decisions, finished work, meetings, tasks, +blockers and anything the user flagged as important. Fetch the records you will +summarize, and pay attention to dates: a task saved this week may have been +finished last week. Ask the user what Ditto cannot know: how the week felt, what +they are proud of, what drained them. + +## The review + +Keep it to one screen: + +- **Done:** outcomes, not activity. "Shipped the pricing page", not "worked on + pricing". +- **Not done:** what slipped and the honest reason. +- **Learned:** one to three lessons worth keeping. +- **Open loops:** waiting-on items with the person and the date. +- **Next week:** at most three priorities, each with a first concrete step. + +For a team review, group by person or project and keep the same headings. + +## Keep it + +Offer to save the review to Ditto so next week's review and future planning can +build on it, and so trends ("third week the same blocker slipped") become +visible. Point out repeated patterns across past reviews when you find them. + +## Make it automatic + +Offer to create a Ditto workflow that runs every week at a time the user picks: +it gathers the week from memory, asks the user a few questions in a form, and +writes the review. Create it only after the user agrees to the schedule and to +where the result goes. Anything that would post or send on the user's behalf +needs their explicit approval when the workflow is created. diff --git a/plugins/ditto/skills/write-spec/SKILL.md b/plugins/ditto/skills/write-spec/SKILL.md new file mode 100644 index 0000000..55feb70 --- /dev/null +++ b/plugins/ditto/skills/write-spec/SKILL.md @@ -0,0 +1,48 @@ +--- +name: write-spec +description: Turn a conversation or rough idea into a one-page spec and shippable tickets, grounded in Ditto memory. +--- + +# Write a spec and tickets + +Turn what was discussed into something a person or agent can build without the +conversation: a short spec, then tickets that each deliver one testable outcome. + +Use the bundled `ditto-memory` skill for connection, recall and capture rules. + +## Gather + +Search Ditto for the project's goals, users, constraints, earlier decisions and +related specs. Read the current conversation and any linked documents or code. +If key facts are missing (who it is for, what done looks like, hard +constraints), ask for them, up to three questions at a time. Use the `grill-me` +skill when the plan itself is still unsettled. + +## The spec + +Keep it to one page where possible: + +- **Problem:** who has it and how we know. +- **Outcome:** what is true for the user when this ships. +- **Scope:** what is included, and what is explicitly excluded. +- **Design:** the approach and the main alternatives considered, with the reason + for the choice. +- **Risks and open questions:** each with an owner. +- **Acceptance:** observable checks that prove the outcome. + +Mark assumptions as assumptions. Do not invent requirements to fill a heading. + +## The tickets + +Split the work into tickets that each ship and can be verified on their own. +Each ticket has a title stating the outcome, context linking to the spec, +acceptance criteria and its dependencies. Order them so the riskiest unknown is +resolved first. Prefer vertical slices (a thin path through every layer) over +layer-by-layer tickets. + +## Deliver + +Show the spec and tickets in the conversation. Create tickets in a tracker only +through a connected tool and only when the user asks. Offer to save the spec to +Ditto, in the project's or team's space when there is one, so later +conversations build from it. diff --git a/scripts/publish_extension_ref.py b/scripts/publish_extension_ref.py deleted file mode 100644 index 8fb2524..0000000 --- a/scripts/publish_extension_ref.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -"""Publish one plugin folder as the root of a dedicated branch for Gemini CLI. - -Gemini CLI installs a GitHub repository only from its root: the manifest must -be `gemini-extension.json` at the top of the checkout, and skills load only from -`/skills/`. It accepts `--ref` but no subdirectory. A root manifest here -would also make `agy plugin install ` treat the whole repository as a -Gemini extension instead of installing plugins/ditto. - -This script commits the tree of `:plugins/` unchanged as the -next commit of the `gemini-extension` branch, so - - gemini extensions install https://github.com/ditto-assistant/ditto-plugins --ref gemini-extension - -installs plugins/ditto with its manifest, skills and MCP server. Each publish -fast-forwards the branch from its previous head. It never force-pushes and does -nothing when the tree is unchanged. -""" -import argparse -import subprocess -import sys - -MANIFEST = "gemini-extension.json" - - -def git(*args, check=True): - result = subprocess.run(["git", *args], capture_output=True, text=True) - if check and result.returncode: - raise RuntimeError(f"git {' '.join(args)} failed: {result.stderr.strip()}") - return result - - -def publish(source, prefix, branch, remote, dry_run=False): - commit = git("rev-parse", "--verify", f"{source}^{{commit}}").stdout.strip() - tree = git("rev-parse", "--verify", f"{commit}:{prefix}").stdout.strip() - if git("cat-file", "-e", f"{commit}:{prefix}/{MANIFEST}", check=False).returncode: - raise ValueError(f"{prefix}/{MANIFEST} is missing at {commit}") - tracking = f"refs/remotes/{remote}/{branch}" - fetched = git("fetch", "--no-tags", remote, f"+refs/heads/{branch}:{tracking}", check=False) - if fetched.returncode and git("ls-remote", "--exit-code", "--heads", remote, branch, check=False).returncode != 2: - raise RuntimeError(f"could not fetch {branch}: {fetched.stderr.strip()}") - parent = git("rev-parse", "--verify", "--quiet", tracking, check=False).stdout.strip() if not fetched.returncode else "" - if parent and git("rev-parse", f"{parent}^{{tree}}").stdout.strip() == tree: - print(f"{branch}: already {prefix} from {commit[:12]} (tree {tree[:12]}); nothing to publish") - return None - message = f"{prefix} from {commit}\n\nGenerated by scripts/publish_extension_ref.py; do not edit this branch.\n" - new = git("commit-tree", tree, *(["-p", parent] if parent else []), "-m", message).stdout.strip() - if dry_run: - print(f"{branch}: dry run; would push {new[:12]} ({prefix} at {commit[:12]}) on top of {parent[:12] or 'nothing'}") - return new - git("push", remote, f"{new}:refs/heads/{branch}") - print(f"{branch}: published {new} ({prefix} at {commit})") - return new - - -def main(): - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("--source", default="HEAD", help="commit to publish from (default HEAD)") - parser.add_argument("--plugin", default="ditto") - parser.add_argument("--branch", default="gemini-extension") - parser.add_argument("--remote", default="origin") - parser.add_argument("--dry-run", action="store_true") - args = parser.parse_args() - try: - publish(args.source, f"plugins/{args.plugin}", args.branch, args.remote, args.dry_run) - except (RuntimeError, ValueError) as error: - sys.exit(str(error)) - - -if __name__ == "__main__": - main() diff --git a/scripts/shared_skills.py b/scripts/shared_skills.py index bba733b..656225d 100644 --- a/scripts/shared_skills.py +++ b/scripts/shared_skills.py @@ -6,7 +6,7 @@ whose generated SKILL.md is the canonical core plus that distribution's host-connection adapter. The ``plugins`` host is the multi-host monorepo (ditto-assistant/ditto-plugins): one shared skills folder per plugin serves -Claude, Cursor, Grok, Codex, Copilot, Antigravity, Kimi and Gemini CLI manifests, +Claude, Cursor, Grok, Codex, ChatGPT, Copilot, Antigravity and Kimi manifests, so every versioned manifest in the plugin directory is bumped together. """ import argparse @@ -29,18 +29,17 @@ NAME = r"[a-z0-9]+(?:-[a-z0-9]+)*" # Versioned manifests, relative to plugins//. The first entry is required. PLUGIN_MANIFESTS = { - "openai": ["plugin.json"], "claude": [".claude-plugin/plugin.json"], "plugins": [ ".claude-plugin/plugin.json", ".cursor-plugin/plugin.json", ".grok-plugin/plugin.json", ".codex-plugin/plugin.json", - "plugin.json", + # ChatGPT: the portable manifest, written as plugin.json at the root of the upload ZIP. + ".openai-plugin/plugin.json", ".github/plugin/plugin.json", ".antigravity-plugin/plugin.json", ".kimi-plugin/plugin.json", - "gemini-extension.json", ], } MANIFESTS = {host: f"plugins/{DEFAULT_PLUGIN}/{paths[0]}" for host, paths in PLUGIN_MANIFESTS.items()} diff --git a/scripts/validate_package.py b/scripts/validate_package.py index 1765df9..d971511 100644 --- a/scripts/validate_package.py +++ b/scripts/validate_package.py @@ -1,13 +1,19 @@ #!/usr/bin/env python3 """Check the MCP-only, multi-host plugin release boundary without connecting an account. +`--archive ` also writes and byte-verifies the ChatGPT +directory upload: plugins/ditto/.openai-plugin/plugin.json as ditto/plugin.json, +with mcp.json, README, assets and skills. + Every root marketplace must list the same plugins. Each plugin lives in plugins// with one manifest per host, one shared skills/ folder and hosted-MCP configuration only: no launchers, credentials, hooks, commands or agents. """ +import argparse import json import re +import zipfile from pathlib import Path ROOT = Path(__file__).resolve().parents[1] @@ -27,7 +33,10 @@ "copilot": ".github/plugin/plugin.json", "antigravity": ".antigravity-plugin/plugin.json", "kimi": ".kimi-plugin/plugin.json", - "gemini": "gemini-extension.json", + # ChatGPT reads a portable Agent Plugins manifest, with its listing under + # extensions["com.openai"], at the root of its upload. Kept out of the plugin + # root here because Grok prefers a root plugin.json over .grok-plugin/. + "openai": ".openai-plugin/plugin.json", } # Hosts that install a GitHub URL only from the repository root get a root # manifest that points into one plugin. Kimi Code reads /.kimi-plugin/plugin.json. @@ -47,11 +56,7 @@ TEXT = {".md", ".json", ".yaml", ".yml", ".txt"} ASSETS = {".png", ".svg"} SERVER_KEYS = {"type", "transport", "url", "serverUrl"} -# Gemini CLI reads `url` as SSE and `httpUrl` as streamable HTTP, and loads -# commands/, hooks/, policies/ and agents/ from the extension directory itself. -GEMINI_KEYS = {"name", "version", "description", "mcpServers"} -# Only Gemini reads httpUrl; another host given one would install without MCP. -GEMINI_SERVER_KEYS = {"httpUrl"} +REPOSITORY = "https://github.com/ditto-assistant/ditto-plugins" def load(path): @@ -69,12 +74,12 @@ def plugin_dir(root, entry): return root / source -def check_server(name, server, plugin, keys=SERVER_KEYS): +def check_server(name, server, plugin): """A hosted server declaration: URL plus transport only.""" expected = HOSTED_MCP.get(plugin) - if not isinstance(server, dict) or set(server) - keys: + if not isinstance(server, dict) or set(server) - SERVER_KEYS: raise ValueError("Expected hosted Ditto MCP without launcher, headers or credentials") - urls = [server[k] for k in ("url", "serverUrl", "httpUrl") if k in server] + urls = [server[k] for k in ("url", "serverUrl") if k in server] url = urls[0] if len(urls) == 1 else None kind = server.get("type", server.get("transport", "http")) if expected is None or url != expected or name != plugin or kind not in {"http", "streamable-http"}: @@ -99,11 +104,27 @@ def check_manifest(host, manifest, plugin, folder): mcp = manifest.get("mcpServers") if host == "claude" and mcp is not None: raise ValueError("Unexpected runtime component in MCP-only claude manifest") # Claude loads .mcp.json itself - if host == "gemini": - if set(manifest) - GEMINI_KEYS or not isinstance(manifest.get("mcpServers"), dict): - raise ValueError(f"gemini manifest may only set {sorted(GEMINI_KEYS)}") - if any(set(server) != {"httpUrl"} for server in manifest["mcpServers"].values() if isinstance(server, dict)): - raise ValueError("gemini manifest must declare the hosted server with httpUrl (streamable HTTP) only") + if host == "openai": + def obj(value): + return value if isinstance(value, dict) else {} + listing = obj(obj(manifest.get("extensions")).get("com.openai")) + ui = listing.get("interface") + if manifest.get("$schema") != AGENT_PLUGINS_SCHEMA or not isinstance(ui, dict): + raise ValueError("openai manifest must be a portable Agent Plugins manifest with extensions.com.openai.interface") + if manifest.get("repository") != REPOSITORY: + raise ValueError(f"openai manifest repository must be {REPOSITORY}") + # Directory listing limits (docs/openai/listing-fields-and-engineering-handoff.md). + limits = {"displayName": 30, "shortDescription": 30, "longDescription": 4000} + if any(not isinstance(ui.get(k), str) or not ui[k].strip() or len(ui[k]) > n for k, n in limits.items()): + raise ValueError("openai listing needs displayName and shortDescription of at most 30 characters and a longDescription of at most 4000") + prompts = ui.get("defaultPrompt", []) + if not isinstance(prompts, list) or any(not isinstance(x, str) or not x.strip() or len(x) > 128 for x in prompts) or len(prompts) > 3 or len(set(prompts)) != len(prompts): + raise ValueError("openai listing allows up to 3 unique starter prompts of at most 128 characters") + cases = obj(obj(listing.get("review")).get("test_cases")) + for category, required in (("positive", ("description", "prompt", "tools_triggered", "expected_behavior")), ("negative", ("description", "prompt"))): + entries = cases.get(category) + if not isinstance(entries, list) or not entries or any(not isinstance(c, dict) or not all(isinstance(c.get(k), str) and c[k].strip() for k in required) for c in entries): + raise ValueError(f"openai review needs complete {category} test cases") if host == "antigravity": if manifest.get("$schema") != AGENT_PLUGINS_SCHEMA: raise ValueError("antigravity manifest must declare the Agent Plugins $schema (agy --strict warns on other hosts' MCP files otherwise)") @@ -116,10 +137,12 @@ def check_manifest(host, manifest, plugin, folder): if len(mcp) != 1: raise ValueError("Expected hosted Ditto MCP without launcher, headers or credentials") for name, server in mcp.items(): - check_server(name, server, plugin, GEMINI_SERVER_KEYS if host == "gemini" else SERVER_KEYS) + check_server(name, server, plugin) elif mcp is not None: raise ValueError(f"Unexpected mcpServers in {host} manifest") interface = manifest.get("interface") if isinstance(manifest.get("interface"), dict) else {} + if host == "openai": + interface = ui for key in ("icon", "logo"): for value in (manifest.get(key), interface.get(key), interface.get("composerIcon")): if isinstance(value, str) and not (folder / value).is_file(): @@ -205,5 +228,37 @@ def validate(root): return count +def openai_upload(root, plugin="ditto"): + """The files ChatGPT reads; other hosts' manifests stay out of the upload.""" + folder = root / "plugins" / plugin + upload = {"plugin.json": folder / MANIFESTS["openai"]} + for f in [folder / "mcp.json", folder / "README.md", *sorted(p for d in ("assets", "skills") for p in (folder / d).rglob("*") if p.is_file())]: + upload[f.relative_to(folder).as_posix()] = f + return folder, upload + + +def archive(root, output): + validate(root) + folder, upload = openai_upload(root) + output = Path(output).resolve() + if output.is_relative_to(Path(root).resolve()): + raise ValueError("Archive must be written outside the repository") + names = {f"{folder.name}/{name}": f for name, f in upload.items()} + output.parent.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as bundle: + for name, f in names.items(): + bundle.write(f, name) + with zipfile.ZipFile(output) as bundle: + if bundle.testzip() is not None or set(bundle.namelist()) != set(names) or any(bundle.read(n) != f.read_bytes() for n, f in names.items()): + raise ValueError("Archive differs from validated source") + return output, len(names) + + if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--archive", type=Path, help="write the ChatGPT directory upload ZIP here (outside the repository)") + args = parser.parse_args() print(f"MCP-only multi-host package verified: {validate(ROOT)} files") + if args.archive: + path, count = archive(ROOT, args.archive) + print(f"ChatGPT upload verified: {path} ({count} files). Host behavior and submission readiness remain unverified.") diff --git a/tests/test_package.py b/tests/test_package.py index 8c5189a..0d9c06f 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -61,7 +61,8 @@ def test_inline_host_servers_are_checked(self): with self.assertRaisesRegex(ValueError, "Expected hosted Ditto MCP"): package.validate(self.root) - def test_http_url_is_only_accepted_from_gemini(self): + def test_http_url_is_rejected(self): + # Only the retired Gemini CLI read httpUrl; any other host given one installs without MCP. for relative in ("plugins/ditto/.mcp.json", "plugins/ditto/mcp_config.json", "plugins/ditto/.kimi-plugin/plugin.json"): with self.subTest(relative=relative): self.setUp() @@ -121,14 +122,26 @@ def test_root_kimi_manifest_points_into_the_plugin(self): with self.assertRaisesRegex(ValueError, "Linked release input"): package.validate(self.root) - def test_gemini_extension_is_mcp_only_streamable_http(self): - relative = "plugins/ditto/gemini-extension.json" + def test_openai_manifest_is_a_portable_listing_of_this_repository(self): + relative = "plugins/ditto/.openai-plugin/plugin.json" + listing = lambda d: d["extensions"]["com.openai"]["interface"] cases = ( - (lambda d: d["mcpServers"].__setitem__("ditto", {"url": "https://api.heyditto.ai/mcp"}), "httpUrl"), - (lambda d: d["mcpServers"]["ditto"].__setitem__("headers", {"Authorization": "Bearer x"}), "httpUrl"), - (lambda d: d.__setitem__("excludeTools", ["run_shell_command"]), "may only set"), - (lambda d: d.__setitem__("contextFileName", "GEMINI.md"), "may only set"), - (lambda d: d["mcpServers"]["ditto"].__setitem__("httpUrl", "https://example.com/mcp"), "Expected hosted Ditto MCP"), + (lambda d: d.pop("$schema"), "portable Agent Plugins"), + (lambda d: d["extensions"].pop("com.openai"), "portable Agent Plugins"), + (lambda d: d.__setitem__("repository", "https://github.com/ditto-assistant/ditto-openai-plugins"), "repository must be"), + (lambda d: listing(d).__setitem__("shortDescription", "x" * 31), "30"), + (lambda d: listing(d).__setitem__("defaultPrompt", ["a", "b", "c", "d"]), "starter prompts"), + (lambda d: listing(d).__setitem__("defaultPrompt", ["x" * 129]), "starter prompts"), + (lambda d: listing(d).__setitem__("logo", "./assets/ditto-icon.png"), "missing asset"), + (lambda d: d.__setitem__("version", "9.9.9"), "share one version"), + # Malformed types are rejected, never a crash. + (lambda d: d["extensions"].__setitem__("com.openai", []), "portable Agent Plugins"), + (lambda d: listing(d).__setitem__("displayName", 7), "at most 30"), + (lambda d: listing(d).pop("longDescription"), "at most 4000"), + (lambda d: listing(d).__setitem__("defaultPrompt", "one prompt"), "starter prompts"), + (lambda d: listing(d).__setitem__("defaultPrompt", [["nested"]]), "starter prompts"), + (lambda d: d["extensions"]["com.openai"].__setitem__("review", []), "positive test cases"), + (lambda d: d["extensions"]["com.openai"]["review"]["test_cases"].__setitem__("negative", ["text"]), "negative test cases"), ) for change, message in cases: with self.subTest(message=message): @@ -138,10 +151,23 @@ def test_gemini_extension_is_mcp_only_streamable_http(self): package.validate(self.root) self.setUp() (self.root / relative).unlink() - with self.assertRaisesRegex(ValueError, "Missing gemini manifest"): + with self.assertRaisesRegex(ValueError, "Missing openai manifest"): package.validate(self.root) - def test_gemini_extension_directories_are_not_release_files(self): + def test_chatgpt_upload_carries_only_the_openai_package(self): + import zipfile + out = Path(self.tmp.name).parent / f"{Path(self.tmp.name).name}-upload.zip" + self.addCleanup(out.unlink, missing_ok=True) + _, count = package.archive(self.root, out) + names = zipfile.ZipFile(out).namelist() + self.assertEqual(count, len(names)) + self.assertIn("ditto/plugin.json", names) + self.assertIn("ditto/skills/ditto-memory/SKILL.md", names) + self.assertFalse([n for n in names if "/." in n], "other hosts' manifests must stay out of the upload") + with self.assertRaisesRegex(ValueError, "outside the repository"): + package.archive(self.root, self.root / "ditto.zip") + + def test_runtime_directories_are_not_release_files(self): for folder in ("commands", "hooks", "policies", "agents"): with self.subTest(folder=folder): self.setUp() @@ -178,7 +204,7 @@ def test_new_plugin_must_be_listed_in_every_marketplace(self): self.edit(f"plugins/ditto-extra/{relative}", lambda d: d.__setitem__("name", "ditto-extra")) for relative in ("plugins/ditto-extra/.mcp.json", "plugins/ditto-extra/mcp.json", "plugins/ditto-extra/mcp_config.json"): self.edit(relative, lambda d: d.__setitem__("mcpServers", {"ditto-extra": d["mcpServers"]["ditto"]})) - for relative in ("plugins/ditto-extra/.github/plugin/plugin.json", "plugins/ditto-extra/.kimi-plugin/plugin.json", "plugins/ditto-extra/gemini-extension.json"): + for relative in ("plugins/ditto-extra/.github/plugin/plugin.json", "plugins/ditto-extra/.kimi-plugin/plugin.json"): self.edit(relative, lambda d: d.__setitem__("mcpServers", {"ditto-extra": d["mcpServers"]["ditto"]})) with patch.dict(package.HOSTED_MCP, {"ditto-extra": "https://api.heyditto.ai/mcp"}): with self.assertRaisesRegex(ValueError, "listed in every marketplace"): diff --git a/tests/test_publish_extension_ref.py b/tests/test_publish_extension_ref.py deleted file mode 100644 index 97226ae..0000000 --- a/tests/test_publish_extension_ref.py +++ /dev/null @@ -1,99 +0,0 @@ -"""The Gemini extension branch carries plugins/ditto as its root and only fast-forwards.""" -import os -from pathlib import Path -import subprocess -import sys -import tempfile -import unittest - -ROOT = Path(__file__).resolve().parents[1] -SCRIPT = ROOT / "scripts/publish_extension_ref.py" -# Empty global/system config, so a developer's signing, hooks or templates -# cannot reach these throwaway repositories. -ENV = {**os.environ, "GIT_AUTHOR_NAME": "T", "GIT_AUTHOR_EMAIL": "t@example.invalid", - "GIT_COMMITTER_NAME": "T", "GIT_COMMITTER_EMAIL": "t@example.invalid", - "GIT_CONFIG_GLOBAL": os.devnull, "GIT_CONFIG_SYSTEM": os.devnull} - - -class PublishExtensionRefTests(unittest.TestCase): - def setUp(self): - self.tmp = tempfile.TemporaryDirectory() - self.addCleanup(self.tmp.cleanup) - base = Path(self.tmp.name) - self.remote = base / "remote.git" - self.work = base / "work" - self.git(base, "init", "-q", "--bare", str(self.remote)) - self.git(base, "init", "-q", "-b", "main", str(self.work)) - self.git(self.work, "remote", "add", "origin", str(self.remote)) - self.write("plugins/ditto/gemini-extension.json", '{"name": "ditto", "version": "1.0.0"}\n') - self.write("plugins/ditto/skills/a/SKILL.md", "---\nname: a\ndescription: A.\n---\n") - self.write("README.md", "root only\n") - self.commit("one") - - def git(self, cwd, *args): - return subprocess.run(["git", *args], cwd=cwd, env=ENV, check=True, capture_output=True, text=True).stdout.strip() - - def write(self, relative, text): - path = self.work / relative - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(text) - - def commit(self, message): - self.git(self.work, "add", "-A") - self.git(self.work, "commit", "-qm", message) - - def publish(self, *args): - return subprocess.run([sys.executable, str(SCRIPT), *args], cwd=self.work, env=ENV, capture_output=True, text=True) - - def remote_tree(self, ref="gemini-extension"): - return self.git(self.remote, "ls-tree", "-r", "--name-only", ref).splitlines() - - def test_branch_root_is_the_plugin_folder_and_republishing_is_a_fast_forward(self): - self.assertEqual(self.publish().returncode, 0) - self.assertEqual(self.remote_tree(), ["gemini-extension.json", "skills/a/SKILL.md"]) - first = self.git(self.remote, "rev-parse", "gemini-extension") - unchanged = self.publish() - self.assertIn("nothing to publish", unchanged.stdout) - self.assertEqual(self.git(self.remote, "rev-parse", "gemini-extension"), first) - self.write("README.md", "root edits do not republish\n") - self.commit("root") - self.assertIn("nothing to publish", self.publish().stdout) - self.write("plugins/ditto/gemini-extension.json", '{"name": "ditto", "version": "1.0.1"}\n') - self.commit("bump") - self.assertEqual(self.publish().returncode, 0) - second = self.git(self.remote, "rev-parse", "gemini-extension") - self.assertEqual(self.git(self.remote, "rev-parse", f"{second}^"), first) - self.assertIn("1.0.1", self.git(self.remote, "show", "gemini-extension:gemini-extension.json")) - - def test_dry_run_pushes_nothing(self): - result = self.publish("--dry-run") - self.assertIn("dry run", result.stdout) - self.assertEqual(self.git(self.remote, "for-each-ref", "refs/heads"), "") - - def test_missing_manifest_is_refused(self): - self.git(self.work, "rm", "-q", "plugins/ditto/gemini-extension.json") - self.commit("drop") - result = self.publish() - self.assertNotEqual(result.returncode, 0) - self.assertIn("gemini-extension.json is missing", result.stderr) - self.assertEqual(self.git(self.remote, "for-each-ref", "refs/heads"), "") - - def test_a_branch_moved_by_someone_else_is_not_overwritten(self): - self.assertEqual(self.publish().returncode, 0) - other = Path(self.tmp.name) / "other" - self.git(Path(self.tmp.name), "clone", "-q", "--branch", "gemini-extension", str(self.remote), str(other)) - (other / "extra.md").write_text("x\n") - self.git(other, "add", "-A") - self.git(other, "commit", "-qm", "manual") - self.git(other, "push", "-q", "origin", "gemini-extension") - moved = self.git(self.remote, "rev-parse", "gemini-extension") - self.write("plugins/ditto/skills/a/SKILL.md", "---\nname: a\ndescription: A, revised.\n---\n") - self.commit("edit") - # The fetched head is the parent, so the push is a fast-forward over the manual commit. - self.assertEqual(self.publish().returncode, 0) - self.assertEqual(self.git(self.remote, "rev-parse", "gemini-extension^"), moved) - self.assertNotIn("extra.md", self.remote_tree()) - - -if __name__ == "__main__": - unittest.main()