@llodev/pm-tasks-asana 1.0.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @llodev/pm-tasks-asana
2
2
 
3
+ ## 1.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Document recurring gotchas observed during v1.0.x dogfood: Asana subtasks not inheriting custom fields, MCP `get_task` missing activity stories (UI is the source of truth), Trello query-string concat bug class, `create_card` ignoring `idMembers`, and `add_member_to_card` false-error returns.
8
+
9
+ ## 1.1.1
10
+
11
+ ### Patch Changes
12
+
13
+ - Ship pt-BR and es-ES translations of SKILL.md and README.md alongside the existing English originals. Adapters keep en-US as the canonical version; localized files follow the `<basename>.<lang-code>.md` convention. The package tarballs now include the localized files via the `files` field.
14
+
15
+ - Updated dependencies []:
16
+ - @llodev/pm-tasks-core@1.1.1
17
+
18
+ ## 1.1.0
19
+
20
+ ### Minor Changes
21
+
22
+ - Localize init prompts in en-US, pt-BR, es-ES. The first prompt of `npx @llodev/pm-tasks-{asana,trello} init` is now a language picker, and every subsequent prompt is rendered in the chosen locale. The selected locale is recorded as `locale` at the root of the config JSON. New helpers in `@llodev/pm-tasks-core/init-lib`: `promptLocale`, `loadStrings`, `interpolate`, `listLocales`, `registerI18nRoot`. JSON Schemas updated to accept `locale` as an enum. No breaking changes — existing configs without `locale` continue to validate and adapters keep emitting en-US prompts if the strings table is omitted.
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies []:
27
+ - @llodev/pm-tasks-core@1.1.0
28
+
29
+ ## 1.0.2
30
+
31
+ ### Patch Changes
32
+
33
+ - Allow `defaults.escalateToAlias` (and `defaults.assigneeAlias` on Trello) in the config schemas. The v1.0.1 init scripts emit these keys, but the JSON Schemas still had `additionalProperties: false` rejecting them — making `npx @llodev/pm-tasks-{asana,trello} init` fail at the validate step with "must NOT have additional properties". Trello init also now emits `assigneeAlias: "me"` for parity with Asana.
34
+
3
35
  ## 1.0.1
4
36
 
5
37
  ### Patch Changes
@@ -0,0 +1,100 @@
1
+ <!-- readme-selector:start -->
2
+ <p align="center">
3
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/README.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/usa.svg" width="30" alt="English"></a>&nbsp;&nbsp;
4
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/README.pt-BR.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/brazil.svg" width="30" alt="Português"></a>&nbsp;&nbsp;
5
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/README.es-ES.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/spain.svg" width="30" alt="Español"></a>
6
+ </p>
7
+ <!-- readme-selector:end -->
8
+
9
+ # @llodev/pm-tasks-asana
10
+
11
+ > Adapter para Asana de la familia `@llodev/pm-tasks-*` — convierte planes de implementación en parent tasks + subtasks de Asana y los opera mediante paste, MCP publish o autonomous write-through.
12
+
13
+ [![npm](https://img.shields.io/npm/v/@llodev/pm-tasks-asana?label=npm&color=cb3837&logo=npm)](https://www.npmjs.com/package/@llodev/pm-tasks-asana)
14
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
15
+ [![Node.js >= 20](https://img.shields.io/badge/node-%3E%3D20-43853d?logo=node.js&logoColor=white)](https://nodejs.org)
16
+ [![Agent Skills spec](https://img.shields.io/badge/Agent_Skills-spec-7c5cff)](https://agentskills.io)
17
+
18
+ Lo que obtienes:
19
+
20
+ - **Output paste-ready** — el agente renderiza un generic card; tú lo pegas en Asana manualmente.
21
+ - **MCP publish** mediante el servidor MCP `claude.ai Asana` (OAuth, sin PATs en JSON). Parent task + subtasks planas + custom fields + colocación en section en un único batch.
22
+ - **Operaciones CRUD** sobre tasks existentes: `checklist.check`, `task.close`, `task.comment.add`, `task.due-date.set`, `task.assignee.add`.
23
+ - **Autonomous mode** — centinela `[autonomous]` / `--auto` para write-through bajo allowlist + scope + rate limits. Los loops multi-task replican el estado a Asana en tiempo real.
24
+
25
+ ## Instalación
26
+
27
+ ```bash
28
+ # npm (con skillpm o marketplace de Claude Code)
29
+ npm i @llodev/pm-tasks-core @llodev/pm-tasks-asana
30
+
31
+ # Vercel CLI (instala el core manualmente también)
32
+ npx skills add llodev/skills/pm-tasks/pm-tasks-core
33
+ npx skills add llodev/skills/pm-tasks/pm-tasks-asana
34
+ ```
35
+
36
+ ## Setup del MCP
37
+
38
+ Asana usa OAuth mediante el MCP `claude.ai Asana`. Si ya conectaste tu cuenta de Asana en los ajustes de Cursor o Claude Code, ya estás listo.
39
+
40
+ Para cualquier agente compatible con MCP (Claude Code, Cursor, Codex, Windsurf, Cline, Roo Code):
41
+
42
+ 1. Abre los ajustes de MCP del agente.
43
+ 2. Activa / registra `claude.ai Asana` (o el MCP de Asana equivalente de tu agente).
44
+ 3. Aprueba el flujo OAuth en tu navegador.
45
+
46
+ En Claude Code, verifica con `claude mcp list` — `claude.ai Asana` debe aparecer como autenticado.
47
+
48
+ ## Setup de la config
49
+
50
+ El script `init` se ejecuta **fuera** del MCP, así que necesita un Personal Access Token para enumerar tus workspaces / projects / sections / custom fields. Genera uno en https://app.asana.com/0/my-apps, después:
51
+
52
+ ```bash
53
+ export LLODEV_PM_TASKS_ASANA_PAT=...
54
+ npx @llodev/pm-tasks-asana init
55
+ ```
56
+
57
+ Sigue los prompts. Elige dónde debe vivir la config:
58
+
59
+ - **local** → `./.asana.json` (recomendado para configs con scope de proyecto, se puede commitear).
60
+ - **global** → un default de la plataforma, personalizable. Defaults:
61
+ - macOS / Linux (XDG): `$XDG_CONFIG_HOME/llodev/pm-tasks/asana.json`, con fallback a `~/.config/llodev/pm-tasks/asana.json`.
62
+ - Windows: `%APPDATA%\llodev\pm-tasks\asana.json`.
63
+ - Sobrescribe en cualquier OS con `LLODEV_PM_TASKS_CONFIG_HOME=/your/path` — el archivo acaba en `$LLODEV_PM_TASKS_CONFIG_HOME/asana.json`.
64
+
65
+ El prompt de `init` imprime la ruta absoluta donde va a escribir, así siempre ves exactamente adónde va el archivo.
66
+
67
+ > [!IMPORTANT]
68
+ > El PAT solo lo usa el `init`. El MCP en sí usa OAuth — nunca pongas tokens en el JSON.
69
+
70
+ ## Uso
71
+
72
+ | Prompt example | Qué hace el agente |
73
+ | ---------------------------------------------------------------- | --------------------------------------------------------------------------- |
74
+ | `"publish this plan as Asana tasks"` | Phase 5 publish — parent + subtasks + custom fields en un único batch |
75
+ | `"check subtask 3 on task X in Asana"` | Phase 6 CRUD — `checklist.check` sobre la subtask |
76
+ | `"close task Y"` | Phase 6 CRUD — `task.close` (mueve a la section de close + marca completed) |
77
+ | `"comment on task X: shipped"` | Phase 6 CRUD — `task.comment.add` |
78
+ | `"[autonomous] create task in asana from plan @docs/plans/X.md"` | Phase 5b autonomous (requiere `autonomous.enabled: true`) |
79
+
80
+ ## Notas específicas de Asana
81
+
82
+ > [!NOTE]
83
+ > **Las subtasks tienen un único nivel** — el adapter aplana checklists anidadas en una sola capa de subtask.
84
+
85
+ > [!WARNING]
86
+ > **Los custom fields NO se heredan por defecto** — lista los IDs de field en `subtaskDefaults.inheritParentFields` para que el adapter los copie del parent a las subtasks en el momento del create.
87
+
88
+ - **Assignee es un campo único** — usa `task.assignee.add` para añadir followers; el assignee principal se reemplaza en caso de conflicto.
89
+ - **El `get_task` del MCP no devuelve activity stories** — el activity feed de la UI de Asana es la fuente de verdad para auditorías de atribución. Mira [`anti-patterns/asana.md`](./anti-patterns/asana.md).
90
+
91
+ ## Documentación
92
+
93
+ - [`SKILL.md`](./SKILL.md) — enrutado de phases + vocabulario CRUD.
94
+ - [`schemas/config.json`](./schemas/config.json) — JSON Schema de `.asana.json`.
95
+ - [`anti-patterns/asana.md`](./anti-patterns/asana.md) — gotchas recurrentes.
96
+ - [`../pm-tasks-core/references/autonomous-mode.md`](../pm-tasks-core/references/autonomous-mode.md) — contrato de autonomous mode.
97
+
98
+ ## Licencia
99
+
100
+ MIT — mira [LICENSE](./LICENSE).
package/README.md CHANGED
@@ -1,6 +1,26 @@
1
+ <!-- readme-selector:start -->
2
+ <p align="center">
3
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/README.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/usa.svg" width="30" alt="English"></a>&nbsp;&nbsp;
4
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/README.pt-BR.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/brazil.svg" width="30" alt="Português"></a>&nbsp;&nbsp;
5
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/README.es-ES.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/spain.svg" width="30" alt="Español"></a>
6
+ </p>
7
+ <!-- readme-selector:end -->
8
+
1
9
  # @llodev/pm-tasks-asana
2
10
 
3
- Asana adapter for the `@llodev/pm-tasks-*` family. Convert implementation plans into Asana parent tasks + subtasks (paste-ready or published via MCP) and operate them (`checklist.check`, `task.close`, `task.comment.add`, etc.).
11
+ > Asana adapter for the `@llodev/pm-tasks-*` family turn implementation plans into Asana parent tasks + subtasks and operate them via paste, MCP publish, or autonomous write-through.
12
+
13
+ [![npm](https://img.shields.io/npm/v/@llodev/pm-tasks-asana?label=npm&color=cb3837&logo=npm)](https://www.npmjs.com/package/@llodev/pm-tasks-asana)
14
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
15
+ [![Node.js >= 20](https://img.shields.io/badge/node-%3E%3D20-43853d?logo=node.js&logoColor=white)](https://nodejs.org)
16
+ [![Agent Skills spec](https://img.shields.io/badge/Agent_Skills-spec-7c5cff)](https://agentskills.io)
17
+
18
+ What you get:
19
+
20
+ - **Paste-ready output** — agent renders a generic card; you paste into Asana manually.
21
+ - **MCP publish** via the `claude.ai Asana` MCP server (OAuth, no PATs in JSON). Parent task + flat subtasks + custom fields + section placement in one batch.
22
+ - **CRUD operations** on existing tasks: `checklist.check`, `task.close`, `task.comment.add`, `task.due-date.set`, `task.assignee.add`.
23
+ - **Autonomous mode** — `[autonomous]` / `--auto` sentinel for write-through under allowlist + scope + rate limits. Multi-task loops mirror state to Asana in real time.
4
24
 
5
25
  ## Install
6
26
 
@@ -9,13 +29,13 @@ Asana adapter for the `@llodev/pm-tasks-*` family. Convert implementation plans
9
29
  npm i @llodev/pm-tasks-core @llodev/pm-tasks-asana
10
30
 
11
31
  # Vercel CLI (install core manually too)
12
- npx skills add llodev/skills/pm-tasks-core
13
- npx skills add llodev/skills/pm-tasks-asana
32
+ npx skills add llodev/skills/pm-tasks/pm-tasks-core
33
+ npx skills add llodev/skills/pm-tasks/pm-tasks-asana
14
34
  ```
15
35
 
16
36
  ## Setup the MCP
17
37
 
18
- Asana uses OAuth via the `claude.ai Asana` MCP. If you've already connected your Asana account in Cursor or Claude Code settings, you're done — no extra step.
38
+ Asana uses OAuth via the `claude.ai Asana` MCP. If you've already connected your Asana account in Cursor or Claude Code settings, you're done.
19
39
 
20
40
  For any MCP-capable agent (Claude Code, Cursor, Codex, Windsurf, Cline, Roo Code):
21
41
 
@@ -23,7 +43,7 @@ For any MCP-capable agent (Claude Code, Cursor, Codex, Windsurf, Cline, Roo Code
23
43
  2. Enable / register `claude.ai Asana` (or your agent's equivalent Asana MCP).
24
44
  3. Approve the OAuth flow in your browser.
25
45
 
26
- In Claude Code, verify with `claude mcp list` — `claude.ai Asana` should appear as authenticated. Other agents have their own listing commands; see your agent's MCP docs.
46
+ In Claude Code, verify with `claude mcp list` — `claude.ai Asana` should appear as authenticated.
27
47
 
28
48
  ## Setup the config
29
49
 
@@ -44,21 +64,36 @@ Walk through the prompts. Pick where the config should live:
44
64
 
45
65
  The `init` prompt prints the absolute path it will write to, so you always see exactly where the file goes.
46
66
 
47
- The PAT is **only** used by `init`. The MCP itself uses OAuth — never put tokens in the JSON.
67
+ > [!IMPORTANT]
68
+ > The PAT is **only** used by `init`. The MCP itself uses OAuth — never put tokens in the JSON.
48
69
 
49
70
  ## Use
50
71
 
51
- - `"publish this plan as Asana tasks"` → publish flow (parent + subtasks)
52
- - `"check subtask 3 on task X in Asana"` → CRUD op
53
- - `"close task Y"` close
54
- - `"comment on task X: ..."` comment
55
- - `"[autonomous] create task in asana from plan @docs/plans/X.md"` autonomous (requires `autonomous.enabled: true` in config)
72
+ | Prompt example | What the agent does |
73
+ | ---------------------------------------------------------------- | --------------------------------------------------------------------- |
74
+ | `"publish this plan as Asana tasks"` | Phase 5 publish — parent + subtasks + custom fields in one batch |
75
+ | `"check subtask 3 on task X in Asana"` | Phase 6 CRUD — `checklist.check` on the subtask |
76
+ | `"close task Y"` | Phase 6 CRUD — `task.close` (moves to close section + sets completed) |
77
+ | `"comment on task X: shipped"` | Phase 6 CRUD — `task.comment.add` |
78
+ | `"[autonomous] create task in asana from plan @docs/plans/X.md"` | Phase 5b autonomous (requires `autonomous.enabled: true`) |
56
79
 
57
80
  ## Asana-specific notes
58
81
 
59
- - **Subtasks are one level deep** — the adapter flattens nested checklists into a single subtask layer.
60
- - **Custom fields do NOT inherit by default** — list field IDs in `subtaskDefaults.inheritParentFields` so the adapter copies them from parent to subtasks at create time.
82
+ > [!NOTE]
83
+ > **Subtasks are one level deep** — the adapter flattens nested checklists into a single subtask layer.
84
+
85
+ > [!WARNING]
86
+ > **Custom fields do NOT inherit by default** — list field IDs in `subtaskDefaults.inheritParentFields` so the adapter copies them from parent to subtasks at create time.
87
+
61
88
  - **Assignee is a single field** — use `task.assignee.add` to add followers; the primary assignee replaces on conflict.
89
+ - **MCP `get_task` doesn't return activity stories** — the Asana UI activity feed is the source of truth for attribution audits. See [`anti-patterns/asana.md`](./anti-patterns/asana.md).
90
+
91
+ ## Documentation
92
+
93
+ - [`SKILL.md`](./SKILL.md) — phase routing + CRUD vocabulary.
94
+ - [`schemas/config.json`](./schemas/config.json) — `.asana.json` JSON Schema.
95
+ - [`anti-patterns/asana.md`](./anti-patterns/asana.md) — recurring gotchas.
96
+ - [`../pm-tasks-core/references/autonomous-mode.md`](../pm-tasks-core/references/autonomous-mode.md) — autonomous-mode contract.
62
97
 
63
98
  ## License
64
99
 
@@ -0,0 +1,100 @@
1
+ <!-- readme-selector:start -->
2
+ <p align="center">
3
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/README.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/usa.svg" width="30" alt="English"></a>&nbsp;&nbsp;
4
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/README.pt-BR.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/brazil.svg" width="30" alt="Português"></a>&nbsp;&nbsp;
5
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/README.es-ES.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/spain.svg" width="30" alt="Español"></a>
6
+ </p>
7
+ <!-- readme-selector:end -->
8
+
9
+ # @llodev/pm-tasks-asana
10
+
11
+ > Adapter para Asana da família `@llodev/pm-tasks-*` — transforma planos de implementação em parent tasks + subtasks no Asana e opera via paste, MCP publish ou autonomous write-through.
12
+
13
+ [![npm](https://img.shields.io/npm/v/@llodev/pm-tasks-asana?label=npm&color=cb3837&logo=npm)](https://www.npmjs.com/package/@llodev/pm-tasks-asana)
14
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
15
+ [![Node.js >= 20](https://img.shields.io/badge/node-%3E%3D20-43853d?logo=node.js&logoColor=white)](https://nodejs.org)
16
+ [![Agent Skills spec](https://img.shields.io/badge/Agent_Skills-spec-7c5cff)](https://agentskills.io)
17
+
18
+ O que você ganha:
19
+
20
+ - **Output paste-ready** — o agente renderiza um generic card; você cola no Asana manualmente.
21
+ - **MCP publish** via servidor MCP `claude.ai Asana` (OAuth, sem PATs no JSON). Parent task + subtasks flat + custom fields + placement em section, tudo num batch só.
22
+ - **Operações CRUD** em tasks existentes: `checklist.check`, `task.close`, `task.comment.add`, `task.due-date.set`, `task.assignee.add`.
23
+ - **Autonomous mode** — sentinela `[autonomous]` / `--auto` para write-through sob allowlist + scope + rate limits. Loops multi-task espelham o estado para o Asana em tempo real.
24
+
25
+ ## Instalação
26
+
27
+ ```bash
28
+ # npm (com skillpm ou marketplace do Claude Code)
29
+ npm i @llodev/pm-tasks-core @llodev/pm-tasks-asana
30
+
31
+ # Vercel CLI (instale o core manualmente também)
32
+ npx skills add llodev/skills/pm-tasks/pm-tasks-core
33
+ npx skills add llodev/skills/pm-tasks/pm-tasks-asana
34
+ ```
35
+
36
+ ## Setup do MCP
37
+
38
+ O Asana usa OAuth via o MCP `claude.ai Asana`. Se você já conectou sua conta do Asana nas configurações do Cursor ou Claude Code, está pronto.
39
+
40
+ Para qualquer agente compatível com MCP (Claude Code, Cursor, Codex, Windsurf, Cline, Roo Code):
41
+
42
+ 1. Abra as configurações de MCP do agente.
43
+ 2. Habilite / registre `claude.ai Asana` (ou o MCP de Asana equivalente do seu agente).
44
+ 3. Aprove o fluxo OAuth no navegador.
45
+
46
+ No Claude Code, verifique com `claude mcp list` — `claude.ai Asana` deve aparecer como autenticado.
47
+
48
+ ## Setup da config
49
+
50
+ O script `init` roda **fora** do MCP, então precisa de um Personal Access Token para enumerar seus workspaces / projects / sections / custom fields. Gere um em https://app.asana.com/0/my-apps, depois:
51
+
52
+ ```bash
53
+ export LLODEV_PM_TASKS_ASANA_PAT=...
54
+ npx @llodev/pm-tasks-asana init
55
+ ```
56
+
57
+ Siga os prompts. Escolha onde a config deve ficar:
58
+
59
+ - **local** → `./.asana.json` (recomendado para configs com escopo de projeto, pode ser commitado).
60
+ - **global** → um default da plataforma, customizável. Defaults:
61
+ - macOS / Linux (XDG): `$XDG_CONFIG_HOME/llodev/pm-tasks/asana.json`, com fallback para `~/.config/llodev/pm-tasks/asana.json`.
62
+ - Windows: `%APPDATA%\llodev\pm-tasks\asana.json`.
63
+ - Sobrescreva em qualquer OS com `LLODEV_PM_TASKS_CONFIG_HOME=/your/path` — o arquivo vai parar em `$LLODEV_PM_TASKS_CONFIG_HOME/asana.json`.
64
+
65
+ O prompt de `init` imprime o caminho absoluto onde vai escrever, então você sempre vê exatamente para onde o arquivo vai.
66
+
67
+ > [!IMPORTANT]
68
+ > O PAT é usado **somente** pelo `init`. O MCP em si usa OAuth — nunca coloque tokens no JSON.
69
+
70
+ ## Uso
71
+
72
+ | Prompt example | O que o agente faz |
73
+ | ---------------------------------------------------------------- | ---------------------------------------------------------------------------- |
74
+ | `"publish this plan as Asana tasks"` | Phase 5 publish — parent + subtasks + custom fields num batch só |
75
+ | `"check subtask 3 on task X in Asana"` | Phase 6 CRUD — `checklist.check` na subtask |
76
+ | `"close task Y"` | Phase 6 CRUD — `task.close` (move para a section de close + marca completed) |
77
+ | `"comment on task X: shipped"` | Phase 6 CRUD — `task.comment.add` |
78
+ | `"[autonomous] create task in asana from plan @docs/plans/X.md"` | Phase 5b autonomous (requer `autonomous.enabled: true`) |
79
+
80
+ ## Notas específicas do Asana
81
+
82
+ > [!NOTE]
83
+ > **Subtasks têm um nível só** — o adapter achata checklists aninhadas em uma única camada de subtask.
84
+
85
+ > [!WARNING]
86
+ > **Custom fields NÃO são herdados por default** — liste os IDs de field em `subtaskDefaults.inheritParentFields` para que o adapter copie do parent para as subtasks no momento do create.
87
+
88
+ - **Assignee é um campo único** — use `task.assignee.add` para adicionar followers; o assignee primário é substituído em caso de conflito.
89
+ - **O `get_task` do MCP não retorna activity stories** — o activity feed da UI do Asana é a fonte de verdade para auditorias de atribuição. Veja [`anti-patterns/asana.md`](./anti-patterns/asana.md).
90
+
91
+ ## Documentação
92
+
93
+ - [`SKILL.md`](./SKILL.md) — roteamento de phases + vocabulário CRUD.
94
+ - [`schemas/config.json`](./schemas/config.json) — JSON Schema do `.asana.json`.
95
+ - [`anti-patterns/asana.md`](./anti-patterns/asana.md) — pegadinhas recorrentes.
96
+ - [`../pm-tasks-core/references/autonomous-mode.md`](../pm-tasks-core/references/autonomous-mode.md) — contrato do autonomous mode.
97
+
98
+ ## Licença
99
+
100
+ MIT — veja [LICENSE](./LICENSE).
package/SKILL.es-ES.md ADDED
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: pm-tasks-asana
3
+ description: >-
4
+ Asana adapter for the @llodev/pm-tasks-* family. Use when the user mentions
5
+ Asana, asks to "create Asana task", "publish to Asana", "post to Asana",
6
+ "publish", "add comment in Asana", or uses --publish-asana; OR for CRUD on
7
+ existing tasks (check subtask, close task, change due-date, assign person,
8
+ comment); OR when invoked autonomously by another agent with [autonomous] /
9
+ --auto sentinel. Asana hierarchy: workspace > project > section > parent task
10
+ > subtasks (one level), with custom fields and multi-assignee support. Modes:
11
+ paste-ready (no MCP needed), MCP publish (via claude.ai Asana MCP), autonomous
12
+ (write-through with allowlist). Implements 6 CRUD verbs (task.create,
13
+ checklist.check, task.close, task.due-date.set, task.assignee.add,
14
+ task.comment.add) from pm-tasks/pm-tasks-core/references/contract.md. Requires
15
+ @llodev/pm-tasks-core installed.
16
+ license: MIT
17
+ metadata:
18
+ version: 1.1.2
19
+ tags:
20
+ - agent-skill
21
+ - asana
22
+ - plan-to-tasks
23
+ - pm-tools
24
+ family: pm-tasks
25
+ role: adapter
26
+ tool: asana
27
+ compatibility:
28
+ agents:
29
+ - claude-code
30
+ - cursor
31
+ - codex
32
+ - windsurf
33
+ - cline
34
+ - roo-code
35
+ ---
36
+
37
+ <!-- readme-selector:start -->
38
+ <p align="center">
39
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/SKILL.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/usa.svg" width="30" alt="English"></a>&nbsp;&nbsp;
40
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/SKILL.pt-BR.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/brazil.svg" width="30" alt="Português"></a>&nbsp;&nbsp;
41
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/SKILL.es-ES.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/spain.svg" width="30" alt="Español"></a>
42
+ </p>
43
+ <!-- readme-selector:end -->
44
+
45
+ # pm-tasks-asana
46
+
47
+ Adaptador para Asana dentro de la familia `@llodev/pm-tasks-*`. Usa las fases de extracción de la skill núcleo, luego aplica el formato de Asana y, opcionalmente, publica/opera vía el servidor MCP `claude.ai Asana`.
48
+
49
+ ## Enrutamiento
50
+
51
+ | Modo | Disparador | Ruta |
52
+ | ----------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
53
+ | Paste-only | "formatear como tarea de Asana" sin intención de MCP | Fase 3 (core) → Fase 4 (esta skill, solo formato) → bloques paste de salida |
54
+ | MCP publish | "publish to Asana", "create on Asana", "--publish-asana" | Fase 3 → Fase 4 → Fase 5 (publish vía MCP) |
55
+ | Autónomo | `[autonomous]` o `--auto` en el prompt O `LLODEV_PM_TASKS_AUTONOMOUS=1` | Fase 3 → Fase 4 → Fase 5b (write-through, sin preview) |
56
+ | Ops CRUD | "marcar subtarea N en tarea X", "cerrar tarea Y", "asignar Alice a la tarea Z", "comentar en tarea X" | Fase 6 (operaciones, dispatch directo del verbo) |
57
+
58
+ ## Modelo de Asana
59
+
60
+ Las tareas de Asana tienen:
61
+
62
+ - **Nombre** (título, ≤80 chars para la vista de tablero).
63
+ - **Descripción** (rich text; prefiere etiquetas `**Section**` en negrita — los headings `##` se renderizan de forma inconsistente).
64
+ - **Subtareas** — un nivel de profundidad. Los custom fields y el assignee NO se propagan automáticamente desde el parent; el adaptador los establece explícitamente según `subtaskDefaults.inheritParentFields` en `.asana.json`.
65
+ - **Secciones** — agrupan tareas dentro de un project.
66
+ - **Custom fields** — por project; la API siempre usa GIDs de opción, nunca nombres visibles.
67
+ - **Multi-assignee** — Asana permite múltiples followers; el assignee primario es un campo único. Usa `task.assignee.add` para añadir followers.
68
+
69
+ ## Fase 4 — Formato de Asana
70
+
71
+ Aplica el generic card del core [`../pm-tasks-core/references/generic-card.md`](../pm-tasks-core/references/generic-card.md). Luego mapea a Asana:
72
+
73
+ - Título → `name` de la tarea.
74
+ - Secciones del generic card → etiquetas `**Section**` en negrita dentro de `description` (no `##`).
75
+ - "Implementation Checklist" + "Verification Checklist" → subtareas (aplana cualquier bullet anidado; Asana solo admite un nivel).
76
+ - Labels → opciones de custom field (resueltas vía `customFields[]` de `.asana.json`).
77
+ - Due date → `due_on` (YYYY-MM-DD).
78
+ - Assignee → GID de `assignee` resuelto desde `members[]` de `.asana.json` o `me` al momento del publish.
79
+
80
+ ## Fase 5 — MCP publish
81
+
82
+ **Prerrequisitos:** servidor MCP de Asana (`claude.ai Asana`) conectado en tu agente. El MCP gestiona OAuth; el adaptador nunca ve tokens. Los pasos de configuración varían por agente — registra el mismo endpoint MCP de Asana que tu agente admita:
83
+
84
+ - **Claude Code**: `claude mcp add asana -s project -- npx -y claude-ai-asana-mcp` (o sigue el setup de Anthropic para el conector hospedado `claude.ai Asana`).
85
+ - **Cursor / Windsurf / Cline / Roo Code**: añade una entrada en el JSON de settings MCP de ese agente apuntando al mismo comando `claude-ai-asana-mcp` (envelope idéntico al ejemplo de Trello en `pm-tasks-trello/references/mcp-config.md`).
86
+ - **Codex**: entrada TOML en `[mcp_servers.asana]` dentro de `~/.codex/config.toml`.
87
+ - **Otros agentes capaces de MCP**: consulta la doc MCP de ese agente; el comando del servidor y el flujo OAuth son constantes.
88
+
89
+ Orden estricto: 5.1 lee `.asana.json` (archivo completo) → 5.2.5 resuelve assignee + custom fields + mapa de fields por subtarea → 5.2 preview & aprobación → 5.3 publish vía MCP → 5.4 manejo de errores.
90
+
91
+ Secuencia del MCP publish:
92
+
93
+ 1. **Tarea padre** — `create_tasks` con `name`, `notes` (descripción), `projects: [projectGid]`, `memberships: [{ project, section }]`, `assignee` (GID resuelto), `due_on`, `custom_fields` (string JSON de `{fieldGid: optionGid}`).
94
+ 2. **Subtareas** — `create_tasks` por subtarea con `parent: parentGid`, `name`, `assignee` (heredado o por subtarea), `custom_fields` según `subtaskDefaults.inheritParentFields`.
95
+ 3. **Tags** (opcional) — `addTag` por GID de tag.
96
+ 4. **Confirmación** — lista parent + subtareas con permalinks.
97
+
98
+ ## Fase 5b — Autónomo
99
+
100
+ Omite 5.2 preview & aprobación. Aplica el contrato de modo autónomo de [`../pm-tasks-core/references/autonomous-mode.md`](../pm-tasks-core/references/autonomous-mode.md). Entradas del log de auditoría según [`../pm-tasks-core/references/audit-log-format.md`](../pm-tasks-core/references/audit-log-format.md).
101
+
102
+ Scope autónomo específico de Asana: `autonomous.scope.projects[]` + `autonomous.scope.sections[]` deben incluir los GIDs objetivo. Cualquier escritura en custom field debe estar en `autonomous.allow` (`task.create` cubre el set de campos al crear; los cambios continuos de fields están fuera del scope de v1.x).
103
+
104
+ ## Fase 6 — Operaciones CRUD (tareas existentes)
105
+
106
+ Para verbos distintos de `task.create`, salta directo a la operación. Mapeo verbo → tool MCP:
107
+
108
+ | Verbo del core | Tool MCP de Asana | Notas |
109
+ | ------------------- | ------------------------------ | ------------------------------------------------------------------------- |
110
+ | `task.create` | `create_tasks` | parent + subtareas según Fase 5 |
111
+ | `checklist.check` | `update_tasks` | para subtareas: `completed: true`; emula checklist vía modelo de subtarea |
112
+ | `task.close` | `update_tasks` | `completed: true` en el parent |
113
+ | `task.due-date.set` | `update_tasks` | `due_on: "YYYY-MM-DD"` |
114
+ | `task.assignee.add` | `update_tasks` + `addFollower` | el assignee primario reemplaza; los adicionales se vuelven followers |
115
+ | `task.comment.add` | `add_comment` (story) | añade una comment story a la tarea |
116
+
117
+ Resolución de `<task-ref>`: acepta permalinks de Asana (`https://app.asana.com/0/<project>/<task>`), GIDs desnudos, o aliases de `taskAliases[]` en `.asana.json`.
118
+
119
+ ## Envelope de resultado
120
+
121
+ Cada verbo retorna la shape del contrato del core (consulta [`../pm-tasks-core/references/contract.md`](../pm-tasks-core/references/contract.md) §Result envelope):
122
+
123
+ ```json
124
+ {
125
+ "ok": true,
126
+ "verb": "task.create",
127
+ "tool": "asana",
128
+ "ref": { "id": "<gid>", "url": "https://app.asana.com/0/<project>/<gid>", "alias": "<optional>" },
129
+ "details": {
130
+ /* específico de Asana (ver tabla abajo) */
131
+ }
132
+ }
133
+ ```
134
+
135
+ `details` específicos de Asana por verbo:
136
+
137
+ | Verbo | Campos de `details` |
138
+ | ------------------- | ------------------------------------------------------------------------ |
139
+ | `task.create` | `{ parentGid, subtaskGids[], projectGid, sectionGid?, customFields[]? }` |
140
+ | `checklist.check` | `{ subtaskGid, completed: true }` |
141
+ | `task.close` | `{ parentGid, completed: true }` |
142
+ | `task.due-date.set` | `{ taskGid, due_on }` |
143
+ | `task.assignee.add` | `{ taskGid, assignee, followers[]? }` (split entre primario y follower) |
144
+ | `task.comment.add` | `{ taskGid, storyGid }` |
145
+
146
+ Ante fallo: `{ ok: false, verb, tool, error: { code, message, retriable } }`. Códigos comunes: `FORBIDDEN_VERB`, `OUT_OF_SCOPE`, `NOT_FOUND`, `RATE_LIMITED`, `PARTIAL_CREATE` (la subtarea falló a mitad del create — consulta [`../pm-tasks-core/references/contract.md`](../pm-tasks-core/references/contract.md) §Partial-create recovery).
147
+
148
+ ## Anti-patterns
149
+
150
+ Consulta [`anti-patterns/asana.md`](anti-patterns/asana.md) — paste health, reglas de custom field, requisitos de GID, manejo de partial-create.
151
+
152
+ ## Fallback standalone
153
+
154
+ Si `@llodev/pm-tasks-core` no está instalado: pide al usuario la entrada mínima (título + nombres de subtareas) y produce un cuerpo de tarea de Asana listo para pegar a partir de ese contenido solo. La calidad se degrada — sin inferencia de scope/audience/fidelity. Imprime: _"Install `@llodev/pm-tasks-core` for the full flow."_
155
+
156
+ ## Config
157
+
158
+ Orden de búsqueda: `<git-root>/.asana.json` → `~/.config/llodev/pm-tasks/asana.json` → aborta con instrucciones de init. Schema: [`schemas/config.json`](schemas/config.json). Los secretos NUNCA en JSON — el MCP de Asana sostiene el OAuth; el `init` usa únicamente la env var `LLODEV_PM_TASKS_ASANA_PAT`.
159
+
160
+ ## Init
161
+
162
+ ```
163
+ npx @llodev/pm-tasks-asana init
164
+ ```
165
+
166
+ Consulta [`../pm-tasks-core/references/init-ux.md`](../pm-tasks-core/references/init-ux.md) para el flujo compartido. El init de Asana lee workspaces / projects / sections / custom fields vía la REST API de Asana usando un Personal Access Token (env `LLODEV_PM_TASKS_ASANA_PAT`).
package/SKILL.md CHANGED
@@ -7,16 +7,15 @@ description: >-
7
7
  existing tasks (check subtask, close task, change due-date, assign person,
8
8
  comment); OR when invoked autonomously by another agent with [autonomous] /
9
9
  --auto sentinel. Asana hierarchy: workspace > project > section > parent task
10
- > subtasks (one level), with custom fields and multi-assignee support.
11
- Modes: paste-ready (no MCP needed), MCP publish (via claude.ai Asana MCP),
12
- autonomous (write-through with allowlist). Implements 6 CRUD verbs
13
- (task.create, checklist.check, task.close, task.due-date.set,
14
- task.assignee.add, task.comment.add) from
15
- pm-tasks/pm-tasks-core/references/contract.md. Requires @llodev/pm-tasks-core
16
- installed.
10
+ > subtasks (one level), with custom fields and multi-assignee support. Modes:
11
+ paste-ready (no MCP needed), MCP publish (via claude.ai Asana MCP), autonomous
12
+ (write-through with allowlist). Implements 6 CRUD verbs (task.create,
13
+ checklist.check, task.close, task.due-date.set, task.assignee.add,
14
+ task.comment.add) from pm-tasks/pm-tasks-core/references/contract.md. Requires
15
+ @llodev/pm-tasks-core installed.
17
16
  license: MIT
18
17
  metadata:
19
- version: 1.0.0
18
+ version: 1.1.2
20
19
  tags:
21
20
  - agent-skill
22
21
  - asana
@@ -35,6 +34,14 @@ compatibility:
35
34
  - roo-code
36
35
  ---
37
36
 
37
+ <!-- readme-selector:start -->
38
+ <p align="center">
39
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/SKILL.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/usa.svg" width="30" alt="English"></a>&nbsp;&nbsp;
40
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/SKILL.pt-BR.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/brazil.svg" width="30" alt="Português"></a>&nbsp;&nbsp;
41
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/SKILL.es-ES.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/spain.svg" width="30" alt="Español"></a>
42
+ </p>
43
+ <!-- readme-selector:end -->
44
+
38
45
  # pm-tasks-asana
39
46
 
40
47
  Adapter for Asana within the `@llodev/pm-tasks-*` family. Use the core skill's extraction phases, then apply Asana formatting and optionally publish/operate via the `claude.ai Asana` MCP server.
@@ -94,6 +101,23 @@ Skip 5.2 preview & approval. Apply autonomous-mode contract from [`../pm-tasks-c
94
101
 
95
102
  Asana-specific autonomous scope: `autonomous.scope.projects[]` + `autonomous.scope.sections[]` must include the target GIDs. Any custom-field write must be in `autonomous.allow` (`task.create` covers create-time field set; ongoing field changes are out of scope for v1.x).
96
103
 
104
+ ### Continuous operation in multi-task loops
105
+
106
+ Mandatory when the controller is executing a plan with multiple tasks autonomously. Mirror each task transition on the corresponding Asana task in real time — NEVER batch state updates at end-of-loop. See [`../pm-tasks-core/references/autonomous-mode.md`](../pm-tasks-core/references/autonomous-mode.md) § **Continuous operation across multi-task loops** for the full contract and anti-patterns.
107
+
108
+ Asana-specific verb mapping:
109
+
110
+ | Transition | MCP call(s) |
111
+ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
112
+ | Task start | `mcp__asana__update_tasks { task: <gid>, add_projects: [{ project_id, section_id: <wipSectionGid> }] }` |
113
+ | Step complete (subtask) | `mcp__asana__update_tasks { task: <subtaskGid>, completed: true }` |
114
+ | Task complete (full) | `mcp__asana__add_comment { task_id: <parentGid>, text: "🤖 [agent] Task complete. Commit: <SHA>. <branch>." }` then `mcp__asana__update_tasks { task: <parentGid>, completed: true, add_projects: [{ project_id, section_id: <doneSectionGid> }] }` |
115
+ | Task failed | `mcp__asana__add_comment` with failure mode + `mcp__asana__update_tasks { task: <gid>, add_followers: ["<escalateToAliasGid>"] }` for human escalation. Do NOT close. |
116
+
117
+ Resolve `<wipSectionGid>` / `<doneSectionGid>` from `.asana.json` `sections[]` by alias (`wip`, `done`, or `closeSectionAlias` from `defaults`). Both must already be in `autonomous.scope.sections` — otherwise the verb returns `OUT_OF_SCOPE`.
118
+
119
+ **Asana caveat (per [`anti-patterns/asana.md`](./anti-patterns/asana.md)):** the MCP `get_task` doesn't return activity stories, so verifying the lifecycle programmatically is incomplete. The UI activity feed IS the human's audit log — keep it dense and accurate.
120
+
97
121
  ## Phase 6 — CRUD operations (existing tasks)
98
122
 
99
123
  For verbs other than `task.create`, jump directly to the operation. Verb → MCP tool mapping:
package/SKILL.pt-BR.md ADDED
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: pm-tasks-asana
3
+ description: >-
4
+ Asana adapter for the @llodev/pm-tasks-* family. Use when the user mentions
5
+ Asana, asks to "create Asana task", "publish to Asana", "post to Asana",
6
+ "publish", "add comment in Asana", or uses --publish-asana; OR for CRUD on
7
+ existing tasks (check subtask, close task, change due-date, assign person,
8
+ comment); OR when invoked autonomously by another agent with [autonomous] /
9
+ --auto sentinel. Asana hierarchy: workspace > project > section > parent task
10
+ > subtasks (one level), with custom fields and multi-assignee support. Modes:
11
+ paste-ready (no MCP needed), MCP publish (via claude.ai Asana MCP), autonomous
12
+ (write-through with allowlist). Implements 6 CRUD verbs (task.create,
13
+ checklist.check, task.close, task.due-date.set, task.assignee.add,
14
+ task.comment.add) from pm-tasks/pm-tasks-core/references/contract.md. Requires
15
+ @llodev/pm-tasks-core installed.
16
+ license: MIT
17
+ metadata:
18
+ version: 1.1.2
19
+ tags:
20
+ - agent-skill
21
+ - asana
22
+ - plan-to-tasks
23
+ - pm-tools
24
+ family: pm-tasks
25
+ role: adapter
26
+ tool: asana
27
+ compatibility:
28
+ agents:
29
+ - claude-code
30
+ - cursor
31
+ - codex
32
+ - windsurf
33
+ - cline
34
+ - roo-code
35
+ ---
36
+
37
+ <!-- readme-selector:start -->
38
+ <p align="center">
39
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/SKILL.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/usa.svg" width="30" alt="English"></a>&nbsp;&nbsp;
40
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/SKILL.pt-BR.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/brazil.svg" width="30" alt="Português"></a>&nbsp;&nbsp;
41
+ <a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/SKILL.es-ES.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/spain.svg" width="30" alt="Español"></a>
42
+ </p>
43
+ <!-- readme-selector:end -->
44
+
45
+ # pm-tasks-asana
46
+
47
+ Adapter para Asana dentro da família `@llodev/pm-tasks-*`. Use as fases de extração da skill núcleo, depois aplique a formatação do Asana e opcionalmente publique/opere via o servidor MCP `claude.ai Asana`.
48
+
49
+ ## Roteamento
50
+
51
+ | Modo | Gatilho | Caminho |
52
+ | ----------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
53
+ | Paste-only | "formatar como task do Asana" sem intenção de MCP | Fase 3 (core) → Fase 4 (essa skill, só formato) → emite blocos de paste |
54
+ | MCP publish | "publish to Asana", "create on Asana", "--publish-asana" | Fase 3 → Fase 4 → Fase 5 (publish via MCP) |
55
+ | Autônomo | `[autonomous]` ou `--auto` no prompt OU `LLODEV_PM_TASKS_AUTONOMOUS=1` | Fase 3 → Fase 4 → Fase 5b (write-through, sem preview) |
56
+ | Ops CRUD | "marcar subtask N da task X", "fechar task Y", "atribuir Alice à task Z", "comentar na task X" | Fase 6 (operações, dispatch direto do verbo) |
57
+
58
+ ## Modelo do Asana
59
+
60
+ Tasks do Asana têm:
61
+
62
+ - **Nome** (título, ≤80 chars para a board view).
63
+ - **Descrição** (rich text; prefira rótulos `**Section**` em negrito — headings `##` renderizam de forma inconsistente).
64
+ - **Subtasks** — um nível de profundidade. Custom fields e assignee NÃO propagam automaticamente do parent; o adapter os define explicitamente conforme `subtaskDefaults.inheritParentFields` em `.asana.json`.
65
+ - **Sections** — agrupam tasks dentro de um project.
66
+ - **Custom fields** — por project; a API sempre usa option GIDs, nunca display names.
67
+ - **Multi-assignee** — o Asana permite múltiplos followers; o assignee primário é um campo único. Use `task.assignee.add` para adicionar followers.
68
+
69
+ ## Fase 4 — Formatação do Asana
70
+
71
+ Aplique o generic card do core [`../pm-tasks-core/references/generic-card.md`](../pm-tasks-core/references/generic-card.md). Depois mapeie para o Asana:
72
+
73
+ - Título → `name` da task.
74
+ - Seções do generic card → rótulos `**Section**` em negrito dentro de `description` (não `##`).
75
+ - "Implementation Checklist" + "Verification Checklist" → subtasks (achate qualquer bullet aninhado; o Asana suporta apenas um nível).
76
+ - Labels → opções de custom field (resolvidas via `customFields[]` do `.asana.json`).
77
+ - Due date → `due_on` (YYYY-MM-DD).
78
+ - Assignee → GID de `assignee` resolvido a partir de `members[]` do `.asana.json` ou `me` no momento do publish.
79
+
80
+ ## Fase 5 — MCP publish
81
+
82
+ **Pré-requisitos:** servidor MCP do Asana (`claude.ai Asana`) conectado no seu agente. O MCP lida com OAuth; o adapter nunca vê tokens. As etapas de configuração diferem por agente — registre o mesmo endpoint MCP do Asana que seu agente suporta:
83
+
84
+ - **Claude Code**: `claude mcp add asana -s project -- npx -y claude-ai-asana-mcp` (ou siga o setup da Anthropic para o conector hosted `claude.ai Asana`).
85
+ - **Cursor / Windsurf / Cline / Roo Code**: adicione uma entrada no JSON de settings MCP daquele agente apontando para o mesmo comando `claude-ai-asana-mcp` (envelope idêntico ao exemplo do Trello em `pm-tasks-trello/references/mcp-config.md`).
86
+ - **Codex**: entrada TOML em `[mcp_servers.asana]` no `~/.codex/config.toml`.
87
+ - **Outros agentes capazes de MCP**: consulte a doc de MCP do agente; o comando do servidor e o fluxo OAuth são constantes.
88
+
89
+ Ordem estrita: 5.1 lê `.asana.json` (arquivo inteiro) → 5.2.5 resolve assignee + custom fields + mapa de fields por subtask → 5.2 preview & aprovação → 5.3 publish via MCP → 5.4 tratamento de erro.
90
+
91
+ Sequência do MCP publish:
92
+
93
+ 1. **Task pai** — `create_tasks` com `name`, `notes` (descrição), `projects: [projectGid]`, `memberships: [{ project, section }]`, `assignee` (GID resolvido), `due_on`, `custom_fields` (string JSON de `{fieldGid: optionGid}`).
94
+ 2. **Subtasks** — `create_tasks` por subtask com `parent: parentGid`, `name`, `assignee` (herdado ou por subtask), `custom_fields` conforme `subtaskDefaults.inheritParentFields`.
95
+ 3. **Tags** (opcional) — `addTag` por GID de tag.
96
+ 4. **Confirmação** — lista parent + subtasks com permalinks.
97
+
98
+ ## Fase 5b — Autônomo
99
+
100
+ Pule 5.2 preview & aprovação. Aplique o contrato de modo autônomo de [`../pm-tasks-core/references/autonomous-mode.md`](../pm-tasks-core/references/autonomous-mode.md). Entradas no log de auditoria conforme [`../pm-tasks-core/references/audit-log-format.md`](../pm-tasks-core/references/audit-log-format.md).
101
+
102
+ Scope autônomo específico do Asana: `autonomous.scope.projects[]` + `autonomous.scope.sections[]` devem incluir os GIDs alvo. Qualquer escrita em custom field precisa estar em `autonomous.allow` (`task.create` cobre o set de campos no momento da criação; mudanças contínuas em campos estão fora do escopo de v1.x).
103
+
104
+ ## Fase 6 — Operações CRUD (tasks existentes)
105
+
106
+ Para verbos diferentes de `task.create`, vá direto para a operação. Mapeamento verbo → tool MCP:
107
+
108
+ | Verbo do core | Tool MCP do Asana | Notas |
109
+ | ------------------- | ------------------------------ | ----------------------------------------------------------------------- |
110
+ | `task.create` | `create_tasks` | parent + subtasks conforme Fase 5 |
111
+ | `checklist.check` | `update_tasks` | para subtasks: `completed: true`; emula checklist via modelo de subtask |
112
+ | `task.close` | `update_tasks` | `completed: true` no parent |
113
+ | `task.due-date.set` | `update_tasks` | `due_on: "YYYY-MM-DD"` |
114
+ | `task.assignee.add` | `update_tasks` + `addFollower` | assignee primário substitui; adicionais viram followers |
115
+ | `task.comment.add` | `add_comment` (story) | adiciona uma comment story na task |
116
+
117
+ Resolução de `<task-ref>`: aceita permalinks do Asana (`https://app.asana.com/0/<project>/<task>`), GIDs nus, ou aliases de `taskAliases[]` em `.asana.json`.
118
+
119
+ ## Envelope de resultado
120
+
121
+ Todo verbo retorna a shape do contrato core (veja [`../pm-tasks-core/references/contract.md`](../pm-tasks-core/references/contract.md) §Result envelope):
122
+
123
+ ```json
124
+ {
125
+ "ok": true,
126
+ "verb": "task.create",
127
+ "tool": "asana",
128
+ "ref": { "id": "<gid>", "url": "https://app.asana.com/0/<project>/<gid>", "alias": "<optional>" },
129
+ "details": {
130
+ /* específico do Asana (veja tabela abaixo) */
131
+ }
132
+ }
133
+ ```
134
+
135
+ `details` específicos do Asana por verbo:
136
+
137
+ | Verbo | Campos de `details` |
138
+ | ------------------- | ------------------------------------------------------------------------ |
139
+ | `task.create` | `{ parentGid, subtaskGids[], projectGid, sectionGid?, customFields[]? }` |
140
+ | `checklist.check` | `{ subtaskGid, completed: true }` |
141
+ | `task.close` | `{ parentGid, completed: true }` |
142
+ | `task.due-date.set` | `{ taskGid, due_on }` |
143
+ | `task.assignee.add` | `{ taskGid, assignee, followers[]? }` (split entre primário e follower) |
144
+ | `task.comment.add` | `{ taskGid, storyGid }` |
145
+
146
+ Em caso de falha: `{ ok: false, verb, tool, error: { code, message, retriable } }`. Códigos comuns: `FORBIDDEN_VERB`, `OUT_OF_SCOPE`, `NOT_FOUND`, `RATE_LIMITED`, `PARTIAL_CREATE` (subtask falhou no meio do create — veja [`../pm-tasks-core/references/contract.md`](../pm-tasks-core/references/contract.md) §Partial-create recovery).
147
+
148
+ ## Anti-patterns
149
+
150
+ Veja [`anti-patterns/asana.md`](anti-patterns/asana.md) — paste health, regras de custom field, requisitos de GID, tratamento de partial-create.
151
+
152
+ ## Fallback standalone
153
+
154
+ Se `@llodev/pm-tasks-core` não estiver instalado: peça ao usuário a entrada mínima (título + nomes das subtasks) e produza um corpo de task do Asana pronto para colar usando apenas esse conteúdo. A qualidade é degradada — sem inferência de scope/audience/fidelity. Imprima: _"Install `@llodev/pm-tasks-core` for the full flow."_
155
+
156
+ ## Config
157
+
158
+ Ordem de lookup: `<git-root>/.asana.json` → `~/.config/llodev/pm-tasks/asana.json` → aborta com instruções de init. Schema: [`schemas/config.json`](schemas/config.json). Secrets NUNCA em JSON — o MCP do Asana cuida do OAuth; o `init` usa apenas a env var `LLODEV_PM_TASKS_ASANA_PAT`.
159
+
160
+ ## Init
161
+
162
+ ```
163
+ npx @llodev/pm-tasks-asana init
164
+ ```
165
+
166
+ Veja [`../pm-tasks-core/references/init-ux.md`](../pm-tasks-core/references/init-ux.md) para o fluxo compartilhado. O init do Asana lê workspaces / projects / sections / custom fields via a REST API do Asana usando um Personal Access Token (env `LLODEV_PM_TASKS_ASANA_PAT`).
@@ -39,3 +39,24 @@ Apply when Phase 4 / Phase 5 / Phase 5b target Asana. Authoritative formatting +
39
39
  ## Cross-tool
40
40
 
41
41
  **NEVER** apply Asana's quirks to another adapter after switching targets mid-chat. **Why:** the user said "actually use Trello" — re-load that adapter and apply only its rules.
42
+
43
+ ---
44
+
45
+ ## UI is the source of truth for activity attribution
46
+
47
+ The MCP's `mcp__asana__get_task` returns user-authored comments but does NOT return activity stories (creation, section moves, assignee changes, custom-field edits, follower changes). Programmatic verification of attribution is therefore incomplete by design.
48
+
49
+ When auditing whether an agent action was correctly attributed to the agent account (not the human account), open the task in the Asana UI and inspect the activity feed. Do not infer attribution from the MCP response.
50
+
51
+ ## Subtasks do NOT inherit custom fields automatically
52
+
53
+ Asana itself does not propagate `custom_fields` from a parent task to its subtasks at create time, regardless of project-level defaults. The adapter MUST replicate the parent's selected `inheritParentFields` (declared in `.asana.json` under `subtaskDefaults`) when creating each subtask:
54
+
55
+ ```javascript
56
+ await createSubtasks({
57
+ parent: parentGid,
58
+ custom_fields: pick(parent.custom_fields, config.subtaskDefaults.inheritParentFields),
59
+ });
60
+ ```
61
+
62
+ Failing to do this leaves subtasks with empty custom fields even when the parent had them set.
@@ -0,0 +1,24 @@
1
+ {
2
+ "header": "@llodev/pm-tasks-asana init",
3
+ "patMissingTitle": "Asana MCP detected, but credentials missing for init probe.",
4
+ "patMissingBody": "Generate a Personal Access Token at https://app.asana.com/0/my-apps,\nthen set it in your shell and re-run:\n export LLODEV_PM_TASKS_ASANA_PAT=...\n(The token is used only by this init script; the MCP itself uses OAuth.)",
5
+ "mcpMissingTitle": "Asana MCP not available. Connect your Asana account first:",
6
+ "mcpMissingBody": " In Cursor / Claude Code settings → MCP → enable 'claude.ai Asana'.\nThen re-run this init.",
7
+ "workspacePrompt": "Available workspaces (select 1):",
8
+ "noWorkspace": "no workspace selected, aborting",
9
+ "projectsPrompt": "Projects in \"{workspace}\" (select 1+):",
10
+ "noProject": "no project selected, aborting",
11
+ "sectionsPrompt": "Sections in \"{project}\":",
12
+ "customFieldsPrompt": "Custom fields in \"{project}\" (select fields to expose to the adapter):",
13
+ "inheritPrompt": "Of those, which should subtasks inherit from the parent?",
14
+ "openSectionPrompt": "Which section is the default for newly-created tasks?",
15
+ "closeSectionPrompt": "Which section means 'closed / done'?",
16
+ "escalationPrompt": "Pick the escalation contact (will receive escalation comments + add_member on critical cards):",
17
+ "noMembersPrompt": "Project membership listing returned nothing (PAT scope or solo project). Add an escalation contact manually?",
18
+ "manualMemberGid": "Escalation member gid (Asana user ID): ",
19
+ "manualMemberName": "Display name: ",
20
+ "manualMemberAlias": "Alias (default \"owner\"): ",
21
+ "tryItTitle": "Try it in Claude Code: 'create an Asana task from this plan'.",
22
+ "tokenReminderTitle": "Reminder: the Asana MCP holds OAuth — never put tokens in this JSON.",
23
+ "tokenReminderBody": "The LLODEV_PM_TASKS_ASANA_PAT env var is only used by this init script."
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "header": "@llodev/pm-tasks-asana init",
3
+ "patMissingTitle": "MCP de Asana detectado, pero faltan credenciales para el probe del init.",
4
+ "patMissingBody": "Genera un Personal Access Token en https://app.asana.com/0/my-apps,\nluego expórtalo en tu shell y vuelve a ejecutar:\n export LLODEV_PM_TASKS_ASANA_PAT=...\n(El token solo lo usa este script de init; el MCP en sí usa OAuth.)",
5
+ "mcpMissingTitle": "MCP de Asana no disponible. Conecta tu cuenta Asana primero:",
6
+ "mcpMissingBody": " En Cursor / Claude Code settings → MCP → activa 'claude.ai Asana'.\nLuego ejecuta este init nuevamente.",
7
+ "workspacePrompt": "Workspaces disponibles (selecciona 1):",
8
+ "noWorkspace": "ningún workspace seleccionado, abortando",
9
+ "projectsPrompt": "Proyectos en \"{workspace}\" (selecciona 1+):",
10
+ "noProject": "ningún proyecto seleccionado, abortando",
11
+ "sectionsPrompt": "Secciones en \"{project}\":",
12
+ "customFieldsPrompt": "Custom fields en \"{project}\" (selecciona cuáles exponer al adapter):",
13
+ "inheritPrompt": "De esos, ¿cuáles deben heredar las subtareas del padre?",
14
+ "openSectionPrompt": "¿Qué sección es el default para tareas nuevas?",
15
+ "closeSectionPrompt": "¿Qué sección significa 'cerrada / hecha'?",
16
+ "escalationPrompt": "Elige el contacto de escalation (recibe comentarios de escalation + add_member en tarjetas críticas):",
17
+ "noMembersPrompt": "El listado de miembros del proyecto vino vacío (alcance del PAT o proyecto en solitario). ¿Agregar un contacto de escalation manualmente?",
18
+ "manualMemberGid": "Gid del miembro de escalation (Asana user ID): ",
19
+ "manualMemberName": "Nombre a mostrar: ",
20
+ "manualMemberAlias": "Alias (default \"owner\"): ",
21
+ "tryItTitle": "Pruébalo en Claude Code: 'crea una tarea de Asana desde este plan'.",
22
+ "tokenReminderTitle": "Recordatorio: el MCP de Asana usa OAuth — nunca pongas tokens en este JSON.",
23
+ "tokenReminderBody": "La variable LLODEV_PM_TASKS_ASANA_PAT solo la usa este script de init."
24
+ }
@@ -0,0 +1,33 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFile, readdir } from "node:fs/promises";
4
+ import path from "node:path";
5
+
6
+ const HERE = import.meta.dirname;
7
+
8
+ async function loadAll() {
9
+ const files = (await readdir(HERE)).filter((f) => f.endsWith(".json"));
10
+ const out = {};
11
+ for (const f of files) out[f] = JSON.parse(await readFile(path.join(HERE, f), "utf8"));
12
+ return out;
13
+ }
14
+
15
+ test("all locale files share the same key set", async () => {
16
+ const all = await loadAll();
17
+ const ref = Object.keys(all["en-US.json"]).sort();
18
+ for (const [name, data] of Object.entries(all)) {
19
+ if (name === "en-US.json") continue;
20
+ const keys = Object.keys(data).sort();
21
+ assert.deepEqual(keys, ref, `locale ${name} differs from en-US`);
22
+ }
23
+ });
24
+
25
+ test("all locale values are non-empty strings", async () => {
26
+ const all = await loadAll();
27
+ for (const [name, data] of Object.entries(all)) {
28
+ for (const [k, v] of Object.entries(data)) {
29
+ assert.equal(typeof v, "string", `${name}/${k} is not a string`);
30
+ assert.ok(v.length > 0, `${name}/${k} is empty`);
31
+ }
32
+ }
33
+ });
@@ -0,0 +1,24 @@
1
+ {
2
+ "header": "@llodev/pm-tasks-asana init",
3
+ "patMissingTitle": "MCP do Asana detectado, mas faltam credenciais para o probe do init.",
4
+ "patMissingBody": "Gere um Personal Access Token em https://app.asana.com/0/my-apps,\ndepois exporte no shell e rode de novo:\n export LLODEV_PM_TASKS_ASANA_PAT=...\n(O token é usado SÓ por este script de init; o MCP em si usa OAuth.)",
5
+ "mcpMissingTitle": "MCP do Asana não disponível. Conecte sua conta Asana primeiro:",
6
+ "mcpMissingBody": " No Cursor / Claude Code settings → MCP → habilite 'claude.ai Asana'.\nDepois rode esse init novamente.",
7
+ "workspacePrompt": "Workspaces disponíveis (selecione 1):",
8
+ "noWorkspace": "nenhum workspace selecionado, abortando",
9
+ "projectsPrompt": "Projetos em \"{workspace}\" (selecione 1+):",
10
+ "noProject": "nenhum projeto selecionado, abortando",
11
+ "sectionsPrompt": "Seções em \"{project}\":",
12
+ "customFieldsPrompt": "Custom fields em \"{project}\" (selecione quais expor ao adapter):",
13
+ "inheritPrompt": "Desses, quais subtasks devem herdar do parent?",
14
+ "openSectionPrompt": "Qual seção é o default para novas tasks?",
15
+ "closeSectionPrompt": "Qual seção significa 'fechada / concluída'?",
16
+ "escalationPrompt": "Escolha o contato de escalation (recebe comentários de escalation + add_member em cards críticos):",
17
+ "noMembersPrompt": "Listagem de membros do projeto retornou vazia (escopo do PAT ou projeto solo). Adicionar um contato de escalation manualmente?",
18
+ "manualMemberGid": "Gid do membro de escalation (Asana user ID): ",
19
+ "manualMemberName": "Nome de exibição: ",
20
+ "manualMemberAlias": "Alias (default \"owner\"): ",
21
+ "tryItTitle": "Teste no Claude Code: 'crie uma task no Asana a partir desse plano'.",
22
+ "tokenReminderTitle": "Lembrete: o MCP do Asana usa OAuth — nunca coloque tokens neste JSON.",
23
+ "tokenReminderBody": "A env LLODEV_PM_TASKS_ASANA_PAT é usada SÓ por este script de init."
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llodev/pm-tasks-asana",
3
- "version": "1.0.1",
3
+ "version": "1.1.2",
4
4
  "description": "Asana adapter for the @llodev/pm-tasks-* family. Use when the user mentions Asana (create Asana task, publish to Asana, post to Asana, add comment in Asana, --publish-asana, close task, check subtask) or wants to publish a plan as Asana tasks with subtasks. Modes: paste-ready (no MCP needed), MCP publish (via claude.ai Asana MCP), autonomous (sentinel [autonomous] / --auto). Implements 6 CRUD verbs from @llodev/pm-tasks-core/references/contract.md mapped to Asana (parent task + subtasks, custom fields, sections, multi-assignee). REQUIRES: @llodev/pm-tasks-core installed (skillpm / Claude Code marketplace cascade auto; Vercel CLI users install manually).",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/llodev/skills/tree/main/pm-tasks/pm-tasks-asana",
@@ -11,12 +11,17 @@
11
11
  },
12
12
  "files": [
13
13
  "SKILL.md",
14
+ "SKILL.pt-BR.md",
15
+ "SKILL.es-ES.md",
16
+ "README.md",
17
+ "README.pt-BR.md",
18
+ "README.es-ES.md",
14
19
  "schemas",
15
20
  "references",
16
21
  "anti-patterns",
17
22
  "scripts",
23
+ "i18n",
18
24
  "LICENSE",
19
- "README.md",
20
25
  "CHANGELOG.md"
21
26
  ],
22
27
  "keywords": [
@@ -31,7 +36,7 @@
31
36
  "pm-tasks-asana": "./scripts/init.mjs"
32
37
  },
33
38
  "dependencies": {
34
- "@llodev/pm-tasks-core": "^1.0.1"
39
+ "@llodev/pm-tasks-core": "^1.1.1"
35
40
  },
36
41
  "publishConfig": {
37
42
  "access": "public"
@@ -8,6 +8,10 @@
8
8
  "properties": {
9
9
  "$schema": { "type": "string" },
10
10
  "version": { "const": "1" },
11
+ "locale": {
12
+ "type": "string",
13
+ "enum": ["en-US", "pt-BR", "es-ES"]
14
+ },
11
15
  "workspace": {
12
16
  "type": "object",
13
17
  "additionalProperties": false,
@@ -109,7 +113,8 @@
109
113
  "projectAlias": { "type": "string" },
110
114
  "sectionAlias": { "type": "string" },
111
115
  "closeSectionAlias": { "type": "string" },
112
- "assigneeAlias": { "type": "string" }
116
+ "assigneeAlias": { "type": "string" },
117
+ "escalateToAlias": { "type": "string" }
113
118
  }
114
119
  },
115
120
  "taskAliases": {
package/scripts/init.mjs CHANGED
@@ -13,6 +13,10 @@ import {
13
13
  validateConfig,
14
14
  probeMCP,
15
15
  printInstructions,
16
+ promptLocale,
17
+ loadStrings,
18
+ registerI18nRoot,
19
+ interpolate,
16
20
  } from "@llodev/pm-tasks-core/init-lib";
17
21
 
18
22
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
@@ -51,30 +55,30 @@ async function asanaProbe() {
51
55
  };
52
56
  }
53
57
 
54
- async function promptManualMember() {
58
+ async function promptManualMember(asanaStrings) {
55
59
  const { createInterface } = await import("node:readline/promises");
56
60
  const { stdin: input, stdout: output } = await import("node:process");
57
61
  const r = createInterface({ input, output });
58
62
  try {
59
- const gid = (await r.question("Escalation member gid (Asana user ID): ")).trim();
63
+ const gid = (await r.question(asanaStrings.manualMemberGid)).trim();
60
64
  if (!gid) return null;
61
- const name = (await r.question("Display name: ")).trim() || "owner";
62
- const alias = (await r.question(`Alias (default "owner"): `)).trim() || "owner";
65
+ const name = (await r.question(asanaStrings.manualMemberName)).trim() || "owner";
66
+ const alias = (await r.question(asanaStrings.manualMemberAlias)).trim() || "owner";
63
67
  return { id: gid, name, alias };
64
68
  } finally {
65
69
  r.close();
66
70
  }
67
71
  }
68
72
 
69
- async function collectEscalationMember(out) {
73
+ async function collectEscalationMember(out, { coreStrings, asanaStrings }) {
70
74
  const candidates = out.members.filter((m) => m.alias !== "me");
71
75
  if (candidates.length) {
72
76
  const choices = candidates.map((m) => ({ label: `${m.name} (${m.alias})`, value: m }));
73
- const picked = await promptPick(
74
- "Pick the escalation contact (will receive escalation comments + add_member on critical cards):",
75
- choices,
76
- { defaultIndex: 0, allowSkip: true },
77
- );
77
+ const picked = await promptPick(asanaStrings.escalationPrompt, choices, {
78
+ defaultIndex: 0,
79
+ allowSkip: true,
80
+ strings: coreStrings,
81
+ });
78
82
  if (picked) {
79
83
  if (picked.alias !== "owner") {
80
84
  if (!out.members.find((m) => m.alias === "owner")) picked.alias = "owner";
@@ -83,11 +87,9 @@ async function collectEscalationMember(out) {
83
87
  }
84
88
  return;
85
89
  }
86
- const add = await promptYesNo(
87
- "Project membership listing returned nothing (PAT scope or solo project). Add an escalation contact manually?",
88
- );
90
+ const add = await promptYesNo(asanaStrings.noMembersPrompt, { strings: coreStrings });
89
91
  if (!add) return;
90
- const manual = await promptManualMember();
92
+ const manual = await promptManualMember(asanaStrings);
91
93
  if (!manual) return;
92
94
  out.members.push(manual);
93
95
  out.defaults.escalateToAlias = manual.alias;
@@ -111,9 +113,13 @@ function mapResourceType(subtype) {
111
113
  }
112
114
 
113
115
  async function run() {
114
- console.log("\n@llodev/pm-tasks-asana init\n");
116
+ registerI18nRoot("asana", path.join(ROOT, "i18n"));
117
+ const locale = await promptLocale("core", { defaultLocale: "en-US" });
118
+ const coreStrings = await loadStrings("core", locale);
119
+ const asanaStrings = await loadStrings("asana", locale);
120
+ console.log(`\n${asanaStrings.header}\n`);
115
121
 
116
- const { path: outPath } = await promptScope("asana");
122
+ const { path: outPath } = await promptScope("asana", { strings: coreStrings });
117
123
 
118
124
  const probe = await probeMCP({
119
125
  tool: "asana",
@@ -125,22 +131,12 @@ async function run() {
125
131
  });
126
132
 
127
133
  if (probe.unauthenticated) {
128
- printInstructions([
129
- "Asana MCP detected, but credentials missing for init probe.",
130
- "Generate a Personal Access Token at https://app.asana.com/0/my-apps,",
131
- "then set it in your shell and re-run:",
132
- " export LLODEV_PM_TASKS_ASANA_PAT=...",
133
- "(The token is used only by this init script; the MCP itself uses OAuth.)",
134
- ]);
134
+ printInstructions([asanaStrings.patMissingTitle, asanaStrings.patMissingBody]);
135
135
  return;
136
136
  }
137
137
 
138
138
  if (!probe.mcpAvailable) {
139
- printInstructions([
140
- "Asana MCP not available. Connect your Asana account first:",
141
- " In Cursor / Claude Code settings → MCP → enable 'claude.ai Asana'.",
142
- "Then re-run this init.",
143
- ]);
139
+ printInstructions([asanaStrings.mcpMissingTitle, asanaStrings.mcpMissingBody]);
144
140
  return;
145
141
  }
146
142
 
@@ -149,28 +145,31 @@ async function run() {
149
145
  const workspaces = await api.getWorkspaces();
150
146
 
151
147
  const pickedWorkspaces = await multiSelect(
152
- "Available workspaces (select 1):",
148
+ asanaStrings.workspacePrompt,
153
149
  workspaces.map((w) => ({ label: `${w.name} (${w.gid})`, value: w })),
150
+ { strings: coreStrings },
154
151
  );
155
152
  if (!pickedWorkspaces.length) {
156
- console.error("no workspace selected, aborting");
153
+ console.error(asanaStrings.noWorkspace);
157
154
  process.exit(1);
158
155
  }
159
156
  const workspace = pickedWorkspaces[0];
160
157
 
161
158
  const projects = await api.getProjects(workspace.gid);
162
159
  const pickedProjects = await multiSelect(
163
- `Projects in "${workspace.name}" (select 1+):`,
160
+ interpolate(asanaStrings.projectsPrompt, { workspace: workspace.name }),
164
161
  projects.map((p) => ({ label: `${p.name} (${p.gid})`, value: p })),
162
+ { strings: coreStrings },
165
163
  );
166
164
  if (!pickedProjects.length) {
167
- console.error("no project selected, aborting");
165
+ console.error(asanaStrings.noProject);
168
166
  process.exit(1);
169
167
  }
170
168
 
171
169
  const out = {
172
170
  $schema: "https://llodev.github.io/skills/schemas/pm-tasks-asana.json",
173
171
  version: "1",
172
+ locale,
174
173
  workspace: { id: workspace.gid, name: workspace.name },
175
174
  projects: [],
176
175
  sections: [],
@@ -187,8 +186,9 @@ async function run() {
187
186
 
188
187
  const sections = await api.getSections(p.gid);
189
188
  const pickedSections = await multiSelect(
190
- `Sections in "${p.name}":`,
189
+ interpolate(asanaStrings.sectionsPrompt, { project: p.name }),
191
190
  sections.map((s) => ({ label: s.name, value: s })),
191
+ { strings: coreStrings },
192
192
  );
193
193
  for (const s of pickedSections) {
194
194
  out.sections.push({
@@ -203,8 +203,9 @@ async function run() {
203
203
  const fields = cfSettings.map((cs) => cs.custom_field).filter(Boolean);
204
204
  if (fields.length) {
205
205
  const pickedFields = await multiSelect(
206
- `Custom fields in "${p.name}" (select fields to expose to the adapter):`,
206
+ interpolate(asanaStrings.customFieldsPrompt, { project: p.name }),
207
207
  fields.map((f) => ({ label: `${f.name} [${f.resource_subtype}]`, value: f })),
208
+ { strings: coreStrings },
208
209
  );
209
210
  for (const f of pickedFields) {
210
211
  const entry = {
@@ -226,8 +227,9 @@ async function run() {
226
227
 
227
228
  if (pickedFields.length) {
228
229
  const inheritPicked = await multiSelect(
229
- `Of those, which should subtasks inherit from the parent?`,
230
+ asanaStrings.inheritPrompt,
230
231
  pickedFields.map((f) => ({ label: f.name, value: f })),
232
+ { strings: coreStrings },
231
233
  );
232
234
  for (const f of inheritPicked) inheritFieldIds.add(f.gid);
233
235
  }
@@ -254,21 +256,22 @@ async function run() {
254
256
  value: s,
255
257
  }));
256
258
  if (sectionChoices.length) {
257
- const open = await promptPick(
258
- "Which section is the default for newly-created tasks?",
259
- sectionChoices,
260
- { defaultIndex: 0, allowSkip: true },
261
- );
259
+ const open = await promptPick(asanaStrings.openSectionPrompt, sectionChoices, {
260
+ defaultIndex: 0,
261
+ allowSkip: true,
262
+ strings: coreStrings,
263
+ });
262
264
  if (open) out.defaults.sectionAlias = open.alias;
263
- const close = await promptPick("Which section means 'closed / done'?", sectionChoices, {
265
+ const close = await promptPick(asanaStrings.closeSectionPrompt, sectionChoices, {
264
266
  defaultIndex: sectionChoices.length - 1,
265
267
  allowSkip: true,
268
+ strings: coreStrings,
266
269
  });
267
270
  if (close) out.defaults.closeSectionAlias = close.alias;
268
271
  }
269
272
  }
270
273
 
271
- await collectEscalationMember(out);
274
+ await collectEscalationMember(out, { coreStrings, asanaStrings });
272
275
 
273
276
  if (inheritFieldIds.size) {
274
277
  out.subtaskDefaults = {
@@ -277,9 +280,9 @@ async function run() {
277
280
  };
278
281
  }
279
282
 
280
- const wantAuto = await promptYesNo(
281
- "Enable autonomous mode? (adds an autonomous block with conservative defaults)",
282
- );
283
+ const wantAuto = await promptYesNo(coreStrings.autonomousPromptQuestion, {
284
+ strings: coreStrings,
285
+ });
283
286
  if (wantAuto) {
284
287
  out.autonomous = {
285
288
  enabled: false,
@@ -291,25 +294,22 @@ async function run() {
291
294
  rateLimit: { writesPerMinute: 30, commentsPerMinute: 10 },
292
295
  auditLog: "~/.local/share/llodev/pm-tasks/asana/audit.log",
293
296
  };
294
- printInstructions([
295
- "autonomous block added with enabled:false.",
296
- "Review scope.projects and scope.sections in the JSON before enabling.",
297
- ]);
297
+ printInstructions([coreStrings.autonomousAddedTitle, coreStrings.autonomousReviewBody]);
298
298
  }
299
299
 
300
300
  const schema = await loadSchema();
301
301
  const valid = await validateConfig(out, schema);
302
302
  if (!valid.ok) {
303
- console.error("invalid config:", JSON.stringify(valid.errors, null, 2));
303
+ console.error(`${coreStrings.errInvalidConfig}:`, JSON.stringify(valid.errors, null, 2));
304
304
  process.exit(1);
305
305
  }
306
306
 
307
307
  await writeConfig(outPath, out);
308
308
  printInstructions([
309
- `Config written to ${outPath}.`,
310
- "Try it in Claude Code: 'create an Asana task from this plan'.",
311
- "Reminder: the Asana MCP holds OAuth — never put tokens in this JSON.",
312
- "The LLODEV_PM_TASKS_ASANA_PAT env var is only used by this init script.",
309
+ interpolate(coreStrings.configWritten, { path: outPath }),
310
+ asanaStrings.tryItTitle,
311
+ asanaStrings.tokenReminderTitle,
312
+ asanaStrings.tokenReminderBody,
313
313
  ]);
314
314
  }
315
315