Skip to content

Commit db0208f

Browse files
danielmeppielDaniel MeppielCopilot
authored
feat(experimental): copilot-app target deploys scheduled prompts to App DB (#1405)
* feat(experimental): copilot-app target deploys scheduled prompts to App DB Dark-shipped under the new `copilot_app` experimental flag (off by default). When enabled, `apm install --target copilot-app --global` writes prompts that carry a `schedule:` frontmatter block as rows in the GitHub Copilot desktop App's SQLite store at `~/.copilot/data.db`. No new CLI surface; `install` / `update` / `uninstall` / `list` all flow through unchanged. Hard contracts: - `enabled = 0` on every insert -- user opts in from the App. - Namespaced ids (`apm--<owner>--<pkg>--<prompt>`) so uninstall never touches user-authored rows. - `PRAGMA user_version` guard (13 currently); refuse to write on unknown. - WAL-safe SQLite with retry on `database is locked`. - Update path preserves user state (`enabled`, `last_run_at`, overrides). - Lockfile URIs use `copilot-app-db://workflows/<id>` (cowork precedent). Tests: 53 new (DB module, schedule parser, target gating, install E2E). Full unit suite: 8787 passed (one pre-existing macOS shlex failure unrelated to this change). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(copilot-app): integration page, apm-usage skill, error UX + lockfile tests Wave 4 + Wave 6a of the copilot-app dark-ship: - docs/src/content/docs/integrations/copilot-app.md mirrors the copilot-cowork page: enable flag, lifecycle, DB resolution, 'auth' model, schema guard, concurrency, lockfile URI scheme, out-of-scope. - apm-usage skill: commands.md notes copilot-app under experimental; package-authoring.md documents the optional schedule: frontmatter block. - tests/unit/integration/test_copilot_app_error_ux.py (5 tests) exercises CopilotAppDbMissingError, CopilotAppDbSchemaError, CopilotAppDbLockedError mid-deploy: each surfaces as an actionable per-prompt diagnostic; one failing prompt does not block the next; resolver returning None mid-run is defensive (no crash). - tests/unit/install/test_services.py adds a round-trip test for copilot-app-db:// URI generation through _deployed_path_entry. Full unit suite: 8794 passed (1 pre-existing unrelated macOS skip). Lint contract green. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(copilot-app): preserve URI scheme for user-scope local installs When 'apm install <local-pkg> --target copilot-app --global' was invoked, the lockfile stored 'workflows/apm--...' without the 'copilot-app-db://' scheme prefix. As a result, the subsequent uninstall could not detect the copilot-app entry and the DB row was orphaned in the Copilot App. Root cause: _deployed_path_entry tried 'target_path.relative_to(project_root)' first. For --global installs, project_root is the user home and the synthetic copilot-app root (~/.copilot/workflows) sits inside it, so the relative_to() succeeded and skipped the dynamic-root URI branch entirely. Fix: detect dynamic-root target match (cowork, copilot-app) before attempting the project_root-relative encoding. The cowork PathTraversalError behavior is preserved for the legacy out-of-tree case. Adds 'test_install_local_pkg_then_uninstall_deletes_db_row' end-to-end regression covering the install -> lockfile URI -> uninstall -> DB row deletion roundtrip. Also extends partition_managed_files dynamic-root branch with the 'prompts_copilot-app' bucket and adds a copilot-app scan in uninstall engine so user-scope DB-backed targets are cleaned even when the local apm.yml does not enumerate them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(changelog): add experimental copilot-app target entry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(copilot-app): drop autopilot, reset enabled on content change, ship docs Address apm-review-panel CEO synthesis for PR #1405: Security (supply-chain-security-expert blocking): - Remove 'autopilot' from _VALID_MODES (copilot_app_db.py) and _VALID_SCHEDULE_MODES (prompt_integrator.py). Earlier docstring claimed third-party autopilot was policy-blocked but no code enforced it -- this lands the actual enforcement at the writer. - deploy_workflow UPDATE branch now compares prompt body, mode, interval, schedule, model, and reasoning_effort against the existing row; when any execution-affecting field changes the user's prior opt-in is revoked (enabled = 0, next_run_at = NULL). Display-only changes (e.g. just the name) still preserve enabled, last_run_at, next_run_at. Closes the silent-malicious-update vector the panel flagged. Test coverage (test-coverage-expert): - Split the prior 'preserves enabled across updates' test into two scenarios that match the new semantics and add a third test covering schedule changes and a regression test that pins mode='autopilot' as rejected. Docs (doc-writer blocking): - Register copilot-app in the Starlight sidebar. - Add copilot-app row to experimental flag table and update the targets-matrix experimental note + auto-detection callout. - Strip false 'apm list' lifecycle row; replace the 'autopilot policy-blocked' paragraph with the secure-by-default rationale; expand the lifecycle table so the content-change reset is documented; fix two 'copilot_app flag' -> 'copilot-app flag' kebab-case drifts. CHANGELOG (devx-ux nit): - Replace 'apm config set experimental.copilot_app true' with the canonical 'apm experimental enable copilot-app'. Tests: 62/62 copilot-app suite green; 1970/1970 integration+install suite green; lint and format silent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * iter-3: force enabled=0 at INSERT writer + truthful docs - copilot_app_db.deploy_workflow INSERT now hardcodes enabled=0 in the SQL (was: row.enabled passthrough). Defence in depth: a future caller cannot bootstrap an auto-running APM-deployed row even if the row dataclass carries enabled=1. The user opt-in path stays the same: enable from the App UI after install. - New test: test_insert_forces_enabled_zero_even_if_caller_passes_one. - Docs (copilot-app.md): lifecycle table row 3 now lists all 7 execution-affecting fields (prompt, schedule, mode, model, reasoning effort), matching deploy_workflow comparison semantics. - Docs (copilot-app.md): error wording for locked-DB paraphrased instead of quoting a string the code never emits. - Docs (package-authoring.md): YAML example drops the autopilot comment; rationale aligned with the integrations/copilot-app.md framing (intentionally not accepted via this target). Closes iter-2 panel feedback. No blocking findings from any of 8 panelists; this iteration converges the residual recommended items. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(copilot-app): allow project-scope install (lift --global gate) A team-shared scheduled prompt declared in a project's apm.yml now deploys to the developer's ~/.copilot/data.db on 'apm install', without requiring '--global' user-scope install. The previous gate forced every contributor to repeat the install at user scope to receive workflows the team had already declared in the manifest. Architectural change: - Add TargetProfile.scope_invariant_resolver (default False). - copilot-app sets scope_invariant_resolver=True because its deploy root (~/.copilot/data.db) is a user-machine resource that exists regardless of install intent. - TargetProfile.for_scope(user_scope=False) now runs user_root_resolver for scope-invariant targets, populating resolved_deploy_root so the lockfile enrichment can map the synthetic 'workflows/<id>' path to the copilot-app-db://workflows/<id> URI. - Cowork remains scope-sensitive (project-scope cowork still rejected). Security envelope: the experimental copilot_app flag remains the single opt-in gate. Removing the --global gate folds two consent layers (flag + user-scope) into one (flag), which matches v1's stated 'apm install just works' UX promise. The DB row is still INSERTed with enabled=0, the namespaced 'apm--<owner>--<pkg>--<prompt>' ID is preserved, and the lockfile URI keeps uninstall surgical. Tests: - 8801 unit tests pass (full sweep). - 64 copilot-app tests pass (was 63). - New test_install_project_scope_then_uninstall_deletes_db_row exercises the full roundtrip via project apm.yml + chdir; rewrites the prior test_project_scope_requires_global which asserted the inverse. - Manual verification in /tmp: install -> DB row appears with enabled=0 -> uninstall -> DB row gone. Docs: - integrations/copilot-app.md install incantation updated. - apm-usage skill commands.md + package-authoring.md mention both project and user scope. - CHANGELOG entry rewritten. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(copilot-app): address devx-ux follow-ups on gate-lift Two recommended findings from devx-ux-expert re-panel (Opus 4.7, agent_id devx-on-gate-lift): 1. Install output is silent about the 'enable in Copilot App' step. Added one-line trailing hint after the 'N prompts integrated -> copilot-app/workflows/' line, only when copilot-app actually wrote rows in this run: [+] /pkg (local) |-- 1 prompts integrated -> copilot-app/workflows/ |-- workflows arrive disabled; enable from the Copilot App's Workflows tab This closes the first-contributor failure mode that the gate-lift surfaces (someone runs plain 'apm install' on a project that declares copilot-app in targets, sees the integrated line, doesn't realise the row landed enabled=0 and needs a Copilot App toggle to fire). 2. targets-matrix.md docs row understated project-scope ride-along for the three never-auto-detected targets. Reworded to call out that a project apm.yml 'targets:' field lets contributors pick them up via plain 'apm install'. Plus the test-coverage nit: pinned verbatim install output shape in the new project-scope roundtrip test (asserts 'prompts integrated' AND 'enable from the Copilot App' appear). Verification: - 64 copilot-app tests pass - Full unit sweep 8800 pass (1 pre-existing flake on test_runtime_windows.py unrelated to gate-lift -- fails on fc40650d too because local 'codex' binary is installed) - Lint+format silent - Manual e2e: [+] /pkg (local) |-- 1 prompts integrated -> copilot-app/workflows/ |-- workflows arrive disabled; enable from the Copilot App's Workflows tab Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(copilot-app): narrow workflow-shape predicate to 3 keys Option B refinement: distinguish workflow-shape prompts from plain .prompt.md unambiguously. Only {interval, schedule_hour, schedule_day} mark a prompt as a Copilot App workflow row; `mode` and `reasoning_effort` are valid OPTIONAL fields on a workflow but cannot flip the shape because plain VSCode prompts use `mode: agent|ask|edit` legitimately. Without this narrow, any plain prompt that set `mode:` would silently land as a (broken) workflow when the user passed --target copilot-app, or a workflow row could be lossy when a writer set only `mode:`. Live e2e verified: - Single-target copilot: workflow-shape SKIPPED, plain ships to .github/prompts/ correctly. - Single-target copilot-app: workflow row in ~/.copilot/data.db with enabled=0; plain prompt warns then skips. - Multi-target copilot,copilot-app (comma-separated): both dispatch paths fire; no leak between them. - Update preserves user-side enabled=1 across re-install. - Lockfile records copilot-app-db:// URIs cleanly; apm audit clean. Warning text narrowed to actually-mandatory keys so the hint is truthful and reproducible. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address panel follow-ups: devx-ux, test-coverage, doc-writer - install.py --target help: mention copilot-app + warn that repeated flag (-t a -t b) silently honors only the last value; use commas (devx-ux #1, #2) - copilot-app.md: bump sidebar order 5 -> 6 (collision with github-rulesets.md), cross-link to reference/experimental/ and reference/targets-matrix/, rephrase WAL ownership to reflect that the App owns WAL and APM coexists via BEGIN IMMEDIATE + bounded retry, surface accepted schema range [13, 13], split lifecycle table cell with rationale below the table, add :::note callout clarifying the shape predicate, document source-deletion orphan case (doc-writer #1-5, devx-ux #4, #5) - tests: add test_workflow_shape_skipped_by_copilot_prompt_integrator regression test asserting workflow-shape .prompt.md does NOT leak into .github/prompts/ when --target includes copilot (test-coverage #1) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Daniel Meppiel <copilot-rework@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9d1ae4e commit db0208f

27 files changed

Lines changed: 2892 additions & 15 deletions

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- **Experimental:** `copilot-app` target deploys prompts (with optional workflow frontmatter) directly into the GitHub Copilot desktop App's `~/.copilot/data.db` workflows table. Gated behind `apm experimental enable copilot-app`; works in both project scope (`apm install --target copilot-app` from a project's `apm.yml`) and user scope (`--global`). Workflows always install disabled (`enabled = 0`); user opts in from the App. No new CLI surface — `apm install / update / uninstall / list` cover the lifecycle. See [Copilot App integration](https://microsoft.github.io/apm/integrations/copilot-app/).
13+
14+
### Changed
15+
16+
- **Experimental (`copilot-app`):** workflow prompts now use flat top-level frontmatter keys. Only `interval`, `schedule_hour`, `schedule_day` mark a `.prompt.md` as a workflow (dispatched to the Copilot App SQLite store); `mode`, `model`, `reasoning_effort` remain optional fields on a workflow but are NOT shape markers because they overload with plain VSCode / Copilot slash-command prompts. Plain prompts (no workflow keys) continue to deploy to slash-command targets only. `interval` defaults to `manual` when omitted. The redesign collapses what looked like two primitives (prompt vs scheduled prompt) into one shape-dispatched `.prompt.md`. Breaking only for users of the unreleased experimental target.
17+
1018
### Fixed
1119

20+
- **Experimental (`copilot-app`):** workflow-shape `.prompt.md` files no longer leak to slash-command targets. Previously a single scheduled prompt would deploy to the Copilot App DB row AND to `.claude/commands/`, `.cursor/commands/`, `.copilot/prompts/`, and `.gemini/commands/`. Now each prompt belongs to exactly one surface based on frontmatter shape.
21+
- **Experimental (`copilot-app`):** pointing a plain `.prompt.md` (no workflow frontmatter) at `--target copilot-app` is now a hard error with an actionable diagnostic telling the author to add `interval: manual` or unset the target, rather than silently skipping.
1222
- `apm install` honors the SSH user portion of dependency URLs (`ssh://user@host/...` and scp shorthand `user@host:org/repo`) instead of hardcoding `git@`; unblocks EMU accounts and other non-`git` SSH identities. User values are validated against a strict allowlist before composing the clone URL. (#1385, closes #1383)
1323

1424
## [0.14.0] - 2026-05-18

docs/astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ export default defineConfig({
211211
{ label: 'CI/CD pipelines', slug: 'integrations/ci-cd' },
212212
{ label: 'GitHub Agentic Workflows', slug: 'integrations/gh-aw' },
213213
{ label: 'Microsoft 365 Copilot Cowork (Experimental)', slug: 'integrations/copilot-cowork' },
214+
{ label: 'GitHub Copilot App workflows (Experimental)', slug: 'integrations/copilot-app' },
214215
{ label: 'AI runtime compatibility', slug: 'integrations/runtime-compatibility' },
215216
{ label: 'GitHub rulesets', slug: 'integrations/github-rulesets' },
216217
],
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
---
2+
title: "GitHub Copilot App workflows (Experimental)"
3+
description: "Deploy APM prompts with schedule frontmatter as Copilot App workflows backed by the desktop SQLite store."
4+
sidebar:
5+
order: 6
6+
---
7+
8+
See the [Targets matrix](../../reference/targets-matrix/) for where `copilot-app` fits alongside the other deploy targets.
9+
10+
:::caution[Frontier preview]
11+
This integration is experimental and off by default. You must enable the `copilot-app` flag before using it.
12+
13+
```bash
14+
apm experimental enable copilot-app
15+
```
16+
17+
See the [Experimental flags reference](../../reference/experimental/) for the full `apm experimental` subcommand surface (enable / disable / list).
18+
19+
Until the flag is enabled, the `copilot-app` target stays inert: it is hidden from auto-detection, and explicit `--target copilot-app` installs fail cleanly with the enable hint instead of touching the App's database.
20+
:::
21+
22+
## What it does
23+
24+
When `copilot-app` is enabled and a package ships a prompt with workflow frontmatter (any of `interval`, `schedule_hour`, `schedule_day` at the top level), `apm install --target copilot-app` inserts the prompt as a row in the GitHub Copilot desktop App's SQLite store at `~/.copilot/data.db`. Add `--global` to install from a user-scope `~/.apm/apm.yml`, or omit it to install from a project's `apm.yml` (typical for team-shared scheduled prompts). The App reads new rows on next launch (or refresh) and lists them under Workflows.
25+
26+
Prompts that do not carry workflow frontmatter are plain slash commands: they deploy to file-based targets (`copilot`, `vscode`, `claude`, ...) and APM hard-errors with an actionable diagnostic if you point them at `copilot-app` directly. A single `.prompt.md` belongs to exactly ONE surface — whichever its frontmatter shape selects.
27+
28+
## Why a new target
29+
30+
The `copilot` target writes prompts as files (`.github/prompts/<name>.prompt.md`) for Copilot in IDEs. The desktop App stores workflows in a SQLite database, not on disk. They are different surfaces; `copilot-app` exists so that one APM install can serve both without leakage.
31+
32+
## Authoring a workflow prompt
33+
34+
:::note[Shape predicate]
35+
Only `interval`, `schedule_hour`, and `schedule_day` at the top level mark a `.prompt.md` as a workflow. Setting `mode:`, `model:`, or `reasoning_effort:` alone keeps it a plain VSCode-style prompt (deploys to `copilot`, `claude`, etc.) -- those keys are accepted on workflows but never trigger workflow routing on their own.
36+
:::
37+
38+
Add workflow frontmatter (flat top-level keys) to any `.prompt.md` file in your package's `.apm/prompts/` folder:
39+
40+
```markdown
41+
---
42+
name: Daily Digest
43+
interval: daily # one of: manual, hourly, daily, weekly
44+
schedule_hour: 9 # 0-23, UTC; ignored for manual / hourly
45+
schedule_day: 1 # 0-6 (weekly only)
46+
mode: interactive # one of: interactive, plan
47+
model: claude-opus-4.7 # optional
48+
reasoning_effort: high # optional
49+
---
50+
51+
Summarise yesterday's commits across all open PRs ...
52+
```
53+
54+
Manual-only workflows omit `schedule_hour` / `schedule_day` and set
55+
`interval: manual` (the default when any other execution-shape key is
56+
present). The Copilot App provides a "run now" affordance for every
57+
workflow, so manual-only is a useful shape — no schedule, just a
58+
named, parameterised prompt the user can fire from the App UI.
59+
60+
The Copilot App also defines an `autopilot` mode, but APM intentionally
61+
does NOT accept it via this target. Until package signing ships, a
62+
third-party package could declare `mode: autopilot` and have the App
63+
auto-run the prompt the moment you flip the in-App enable toggle.
64+
Refusing autopilot at the writer is the secure-by-default behaviour;
65+
you can still set autopilot yourself on a per-row basis from the App
66+
UI after install.
67+
68+
## Lifecycle
69+
70+
| `apm` action | Effect on `~/.copilot/data.db` |
71+
|---|---|
72+
| `apm install` | INSERT row with `enabled = 0` (always disabled on install — you opt in). |
73+
| `apm install` (already installed, content unchanged) | UPDATE display fields only. `enabled`, `last_run_at`, `next_run_at` are preserved. |
74+
| `apm install` (already installed, any execution-affecting field changed) | UPDATE row; reset `enabled = 0`; clear `next_run_at`. |
75+
| `apm uninstall` | DELETE only APM-namespaced rows (`apm--<owner>--<pkg>--<prompt>`). User-authored rows are never touched. |
76+
77+
Execution-affecting fields are the prompt body, schedule (`interval` / `schedule_hour` / `schedule_day`), `mode`, `model`, and `reasoning_effort`. The reset is by design: you opted in to a specific prompt, so any change to what runs or when is a new consent surface.
78+
79+
Removing the source `.prompt.md` from a package and re-syncing drops the lockfile entry but does NOT delete the corresponding row from `~/.copilot/data.db` -- it merely orphans it. Run `apm uninstall <pkg>` to remove the row.
80+
81+
## Enable and check
82+
83+
Use `apm experimental enable copilot-app` to turn the target on, `apm experimental list` to see all flags, and `apm experimental disable copilot-app` to turn it off again. See the [Experimental flags reference](../../reference/experimental/) for the complete subcommand surface.
84+
85+
## Database resolution
86+
87+
| Order | Source |
88+
|---|---|
89+
| 1 | `APM_COPILOT_APP_DB` environment variable (absolute path; used as-is). |
90+
| 2 | `~/.copilot/data.db` if it exists. |
91+
92+
If neither resolves, the install fails with `[x] GitHub Copilot desktop App not detected. Expected ~/.copilot/data.db ...` and the command exits 1.
93+
94+
## "Auth" model
95+
96+
There is none. The DB file is local; access is governed by your filesystem permissions. APM never sends credentials or syncs the DB anywhere. Treat the DB as user-scope state.
97+
98+
## Schema compatibility
99+
100+
APM guards writes with `PRAGMA user_version` and accepts the closed range `[13, 13]` today. If the App ships a newer schema, APM refuses to write and asks you to update APM rather than risk corruption.
101+
102+
## Concurrency
103+
104+
The Copilot App owns the DB and keeps it in WAL mode while running. APM coexists with the App's writer connection by issuing `BEGIN IMMEDIATE` with a bounded retry; if a lock cannot be acquired after the retry window, the install fails with a `[!]` warning noting that the Copilot App DB stayed locked and asking you to close the GitHub Copilot app momentarily and retry.
105+
106+
## Lockfile entries
107+
108+
Deployed rows are tracked in the project / user lockfile under the `copilot-app-db://workflows/<namespaced-id>` URI scheme. Standard sync semantics apply: lockfile drift triggers redeploy; removal from lockfile triggers row delete on next install.
109+
110+
## Out of scope (today)
111+
112+
- Package signing (would unlock additional trust-gated capabilities such as `mode: autopilot`).
113+
- Scheduled-execution-on-install (deliberately not implemented — first-run is always manual).
114+
- `gh-aw` outer-loop target (separate roadmap).

docs/src/content/docs/reference/experimental.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ apm experimental reset verbose-version
171171
|-----------------------|----------------------------------------------------------------------------------|
172172
| `verbose-version` | Show Python version, platform, and install path in `apm --version`. |
173173
| `copilot-cowork` | Deploy APM skills to Microsoft 365 Copilot Cowork via OneDrive. |
174+
| `copilot-app` | Deploy APM prompts that carry workflow frontmatter (any of `interval`, `schedule_hour`, `schedule_day`) as workflows in the GitHub Copilot desktop App (`~/.copilot/data.db`). See [Copilot App integration](../integrations/copilot-app/). |
174175

175176
New flags are proposed via [CONTRIBUTING.md](https://github.com/microsoft/apm/blob/main/CONTRIBUTING.md#how-to-add-an-experimental-feature-flag) and graduate to default when stable. See the contributor recipe for the full lifecycle.
176177
See also: [Cowork integration](../integrations/copilot-cowork/).

docs/src/content/docs/reference/targets-matrix.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ Skills always deploy to the cross-tool `.agents/skills/` directory by
3232
default (see [Skills convergence](#skills-convergence) below). All other
3333
primitives land under each target's own root.
3434

35-
`copilot-cowork` (Microsoft 365 Copilot) is gated behind an experimental
36-
flag and not listed above. See [Experimental](../experimental/).
35+
`copilot-cowork` (Microsoft 365 Copilot) and `copilot-app` (GitHub
36+
Copilot desktop App) are gated behind experimental flags and not listed
37+
above. See [Experimental](../experimental/).
3738

3839
## Detection and resolution
3940

@@ -60,8 +61,10 @@ list before `compile` or `install`.
6061
| opencode | `.opencode/` directory |
6162
| windsurf | `.windsurf/` directory |
6263

63-
`agent-skills` and `copilot-cowork` are never auto-detected. Select them
64-
explicitly with `--target`.
64+
`agent-skills`, `copilot-cowork`, and `copilot-app` are never
65+
auto-detected. Select them explicitly with `--target`, or list them in
66+
a project's `apm.yml` `targets:` field so contributors running plain
67+
`apm install` pick them up automatically.
6568

6669
## copilot
6770

packages/apm-guide/.apm/skills/apm-usage/commands.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ Set `MCP_REGISTRY_URL` (default `https://api.mcp.github.com`) to point all `apm
141141

142142
Use `apm experimental enable copilot-cowork` to turn on Microsoft 365 Copilot Cowork skill deployment. Once enabled, deploy skills with `apm install --target copilot-cowork --global`.
143143

144+
Use `apm experimental enable copilot-app` to turn on GitHub Copilot desktop App workflow deployment. Once enabled, prompts that carry workflow frontmatter -- any flat top-level key of `interval`, `schedule_hour`, `schedule_day` -- can be deployed to the App's SQLite store at `~/.copilot/data.db` with `apm install --target copilot-app` (project scope) or `--target copilot-app --global` (user scope). A `.prompt.md` belongs to exactly ONE surface: workflow-shape prompts go to the App DB, plain prompts go to slash-command targets. Rows always start `enabled = 0` -- you opt in from the App. `apm install / update / uninstall` preserve user state (`enabled`, `last_run_at`, schedule overrides). Override the database path with `APM_COPILOT_APP_DB=<abs-path>`.
145+
144146
### Cross-client skills (`agent-skills`)
145147

146148
Use `--target agent-skills` to deploy skills to `.agents/skills/` -- the cross-tool standard directory. This is useful when multiple clients (Codex, future tools) read from `.agents/skills/`. Unlike `--target all`, `agent-skills` must be requested explicitly: `apm install --target agent-skills` or `apm install --target all,agent-skills` for both. `apm compile --target agent-skills` is a no-op (skills-only target).

packages/apm-guide/.apm/skills/apm-usage/package-authoring.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,44 @@ When installed as a Claude Code slash command, APM maps `input:` to
180180
Claude's `arguments:` frontmatter and converts `${input:name}` to `$name`
181181
placeholders. An `argument-hint` is auto-generated unless one is already set.
182182

183+
#### Optional workflow frontmatter (GitHub Copilot App, experimental)
184+
185+
When the `copilot_app` experimental flag is enabled and the package is
186+
installed with `apm install --target copilot-app` (project scope) or
187+
`apm install --target copilot-app --global` (user scope), prompts that
188+
carry workflow frontmatter -- any flat top-level key of `interval`,
189+
`schedule_hour`, `schedule_day` -- are deployed as rows in the desktop
190+
App's SQLite store at `~/.copilot/data.db`. ``mode``, ``model``, and
191+
``reasoning_effort`` are optional fields on a workflow but do NOT mark
192+
a plain prompt as a workflow (they overload with plain VSCode / Copilot
193+
slash-command prompts); declare ``interval: manual`` to opt a no-schedule
194+
prompt into the App.
195+
196+
```yaml
197+
---
198+
name: "Daily Digest"
199+
interval: daily # manual | hourly | daily | weekly
200+
schedule_hour: 9 # 0-23 (UTC); ignored for manual / hourly
201+
schedule_day: 1 # 0-6 (weekly only)
202+
mode: interactive # interactive | plan
203+
model: claude-opus-4.7 # optional
204+
reasoning_effort: high # optional
205+
---
206+
```
207+
208+
Rows are always inserted with `enabled = 0`; the user opts in from the
209+
App. A `.prompt.md` belongs to exactly ONE surface: workflow-frontmatter
210+
prompts go ONLY to the App DB, plain prompts go ONLY to file-based
211+
slash-command targets (`copilot`, `claude`, `cursor`, ...). Pointing a
212+
plain prompt at `--target copilot-app` is a hard error with an
213+
actionable diagnostic. `interval` is optional and defaults to `manual`
214+
when any other execution-shape key is present, so a parameterised
215+
prompt with no schedule still works as a manually-fired App workflow.
216+
The App also defines an `autopilot` mode, but APM intentionally does
217+
not accept it via this target -- a third-party package could otherwise
218+
auto-run the moment the user enables the row. Users who want autopilot
219+
can still set it themselves per-row from the App UI after install.
220+
183221
### 5. Agent (`*.agent.md`)
184222

185223
Agent persona and behavior definition.

src/apm_cli/commands/install.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ def _handle_mcp_install(
927927
"target",
928928
type=TargetParamType(),
929929
default=None,
930-
help="Target harness(es) to deploy to. Comma-separated for multiple: --target claude,cursor. Highest-priority entry in the resolution chain (--target > apm.yml targets: > auto-detect). Values: copilot, claude, cursor, opencode, codex, gemini, windsurf, agent-skills, all. 'agent-skills' deploys to .agents/skills/ (cross-client). 'all' = copilot+claude+cursor+opencode+codex+gemini+windsurf (excludes agent-skills); combine with 'agent-skills' for both. 'copilot-cowork' is also accepted when the copilot-cowork experimental flag is enabled (run 'apm experimental enable copilot-cowork'). Note: '--target all' on 'apm compile' is deprecated; use 'apm compile --all' instead.",
930+
help="Target harness(es) to deploy to. Comma-separated for multiple: --target claude,cursor. Repeating the flag (e.g. '-t a -t b') is NOT supported -- only the last value wins; use commas. Highest-priority entry in the resolution chain (--target > apm.yml targets: > auto-detect). Values: copilot, claude, cursor, opencode, codex, gemini, windsurf, agent-skills, all. 'agent-skills' deploys to .agents/skills/ (cross-client). 'all' = copilot+claude+cursor+opencode+codex+gemini+windsurf (excludes agent-skills); combine with 'agent-skills' for both. 'copilot-cowork' is also accepted when the copilot-cowork experimental flag is enabled (run 'apm experimental enable copilot-cowork'). 'copilot-app' is also accepted when the copilot-app experimental flag is enabled (run 'apm experimental enable copilot-app'). Note: '--target all' on 'apm compile' is deprecated; use 'apm compile --all' instead.",
931931
)
932932
@click.option(
933933
"--allow-insecure",

src/apm_cli/commands/uninstall/engine.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,11 @@ def _sync_integrations_after_uninstall(
515515
continue
516516
_effective_root = _mapping.deploy_root or _target.root_dir
517517
_deploy_dir = project_root / _effective_root / _mapping.subdir
518-
if not _deploy_dir.exists():
518+
# Dynamic-root targets (e.g. copilot-app) have no filesystem
519+
# deploy dir; their managed files are URIs that the integrator
520+
# resolves internally. Skip the dir-exists guard for them.
521+
_is_dynamic = _target.resolved_deploy_root is not None
522+
if not _is_dynamic and not _deploy_dir.exists():
519523
continue
520524
_managed_subset = None
521525
if _buckets is not None:
@@ -579,6 +583,37 @@ def _sync_integrations_after_uninstall(
579583
)
580584
counts["skills"] = result.get("files_removed", 0)
581585

586+
# Scan sync_managed DIRECTLY for copilot-app-db:// entries.
587+
# The copilot-app target is opt-in: resolve_targets() excludes it from the
588+
# default user-scope set unless --target copilot-app was passed at install
589+
# time and recorded on apm_package.target. Without this scan, prompts
590+
# deployed to ~/.copilot/data.db would never be deleted on uninstall
591+
# because the per-target loop above does not iterate copilot-app.
592+
if sync_managed:
593+
from ...integration.copilot_app_db import COPILOT_APP_LOCKFILE_PREFIX
594+
595+
_copilot_app_files = {p for p in sync_managed if p.startswith(COPILOT_APP_LOCKFILE_PREFIX)}
596+
if _copilot_app_files:
597+
# Find or synthesise a user-scope copilot-app TargetProfile.
598+
from ...integration.targets import KNOWN_TARGETS
599+
600+
_ca_target = next(
601+
(t for t in _resolved_targets if t.name == "copilot-app"),
602+
None,
603+
)
604+
if _ca_target is None:
605+
_ca_static = KNOWN_TARGETS.get("copilot-app")
606+
if _ca_static is not None:
607+
_ca_target = _ca_static.for_scope(user_scope=True)
608+
if _ca_target is not None:
609+
result = _integrators["prompts"].sync_for_target(
610+
_ca_target,
611+
apm_package,
612+
project_root,
613+
managed_files=_copilot_app_files,
614+
)
615+
counts["prompts"] += result.get("files_removed", 0)
616+
582617
# Hooks (multi-target sync_integration handles all targets)
583618
result = _integrators["hooks"].sync_integration(
584619
apm_package,

0 commit comments

Comments
 (0)