@llodev/pm-tasks-core 1.0.0
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 +17 -0
- package/LICENSE +21 -0
- package/README.md +31 -0
- package/SKILL.md +73 -0
- package/anti-patterns/core.md +55 -0
- package/package.json +44 -0
- package/references/audit-log-format.md +44 -0
- package/references/autonomous-mode.md +61 -0
- package/references/contract.md +90 -0
- package/references/crud-vocabulary.md +63 -0
- package/references/generic-card.md +143 -0
- package/references/init-ux.md +62 -0
- package/scripts/init-lib.mjs +88 -0
- package/scripts/rotate-audit.sh +24 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @llodev/pm-tasks-core
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- [`a571ab1`](https://github.com/llodev/skills/commit/a571ab1537ea7d3fe61c7b89c5be0f08d01f3838) - First stable release of the pm-tasks-\* family.
|
|
8
|
+
|
|
9
|
+
- `@llodev/pm-tasks-core` — Phases 1–3 extraction pipeline (input → sections → generic card), 6 CRUD verbs (`task.create`, `checklist.check`, `task.close`, `task.due-date.set`, `task.assignee.add`, `task.comment.add`), autonomous-mode contract (allowlist + scope + rate-limit + audit log), shared init UX library.
|
|
10
|
+
- `@llodev/pm-tasks-trello` — Trello adapter on the canonical generic card. Paste-friendly output, MCP-driven publish, autonomous mode against a board allowlist.
|
|
11
|
+
- `@llodev/pm-tasks-asana` — Asana adapter with workspace/project/section + custom-field + subtask-inheritance support. Paste, MCP-driven publish, autonomous mode.
|
|
12
|
+
|
|
13
|
+
Architecture, contract, and CRUD vocabulary documented in `docs/specs/2026-06-11-pm-tasks-design.md` and `docs/plans/2026-06-11-pm-tasks-v1.md`.
|
|
14
|
+
|
|
15
|
+
## 0.1.0 (unreleased)
|
|
16
|
+
|
|
17
|
+
- Initial extraction from `plan-to-task-cards` v0.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LLDev Information Solutions
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @llodev/pm-tasks-core
|
|
2
|
+
|
|
3
|
+
Core skill shared by every `@llodev/pm-tasks-<tool>` adapter (Trello, Asana, Jira, Linear, Notion, ClickUp, Monday, Bitrix24, Todoist).
|
|
4
|
+
|
|
5
|
+
This package alone is not useful. Install at least one adapter:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm i @llodev/pm-tasks-core @llodev/pm-tasks-trello
|
|
9
|
+
# or
|
|
10
|
+
npx skills add llodev/skills/pm-tasks-core llodev/skills/pm-tasks-trello
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What lives here
|
|
14
|
+
|
|
15
|
+
- The extraction phases (plan → generic card).
|
|
16
|
+
- The canonical CRUD vocabulary every adapter implements.
|
|
17
|
+
- The autonomous-mode contract (sentinels, allowlist, guardrails).
|
|
18
|
+
- The init UX shared by all adapter `init` commands.
|
|
19
|
+
- The audit log format.
|
|
20
|
+
|
|
21
|
+
## Optional cron — rotate audit log
|
|
22
|
+
|
|
23
|
+
```cron
|
|
24
|
+
# Daily at 04:00, keep 90 days of audit log for Trello + Asana
|
|
25
|
+
0 4 * * * /path/to/pm-tasks-core/scripts/rotate-audit.sh trello
|
|
26
|
+
0 4 * * * /path/to/pm-tasks-core/scripts/rotate-audit.sh asana
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
MIT — see [LICENSE](./LICENSE).
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pm-tasks-core
|
|
3
|
+
description: >-
|
|
4
|
+
Core extraction + vocabulary for the @llodev/pm-tasks-* family. Use when
|
|
5
|
+
working with any pm-tasks-<tool> adapter (Trello, Asana, etc.) — provides
|
|
6
|
+
Phases 1–3 (identify input, extract structure, build the generic card) plus
|
|
7
|
+
the canonical CRUD vocabulary (task.create, checklist.check, task.close,
|
|
8
|
+
task.due-date.set, task.assignee.add, task.comment.add) consumed by adapters.
|
|
9
|
+
Also defines autonomous-mode contract (sentinels, allowlist, scope, audit log)
|
|
10
|
+
and the shared init UX. Triggered indirectly by any prompt that an adapter
|
|
11
|
+
handles (e.g. "create Trello card", "publish plan to Asana", "[autonomous]
|
|
12
|
+
create task"). Do NOT activate alone — it has no tool-specific formatting.
|
|
13
|
+
license: MIT
|
|
14
|
+
metadata:
|
|
15
|
+
version: 1.0.0
|
|
16
|
+
tags:
|
|
17
|
+
- agent-skill
|
|
18
|
+
- plan-to-tasks
|
|
19
|
+
- pm-tools
|
|
20
|
+
family: pm-tasks
|
|
21
|
+
role: core
|
|
22
|
+
compatibility:
|
|
23
|
+
agents:
|
|
24
|
+
- claude-code
|
|
25
|
+
- cursor
|
|
26
|
+
- codex
|
|
27
|
+
- windsurf
|
|
28
|
+
- cline
|
|
29
|
+
- roo-code
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# pm-tasks-core
|
|
33
|
+
|
|
34
|
+
Shared core for all `pm-tasks-<tool>` adapters. Defines the extraction phases, the generic-card structure, the CRUD vocabulary, the autonomous-mode contract, the configuration lookup rules, and the audit-log format. Adapters reference this skill by path — no formal dependency mechanism in the spec.
|
|
35
|
+
|
|
36
|
+
## Routing
|
|
37
|
+
|
|
38
|
+
Adapters invoke this skill BEFORE applying their tool-specific formatting. The exact pointer is documented in [`references/contract.md`](references/contract.md).
|
|
39
|
+
|
|
40
|
+
## Phases
|
|
41
|
+
|
|
42
|
+
| Phase | Purpose | Reference |
|
|
43
|
+
| ----- | ------------------------------------------------------------ | ---------------------------------------------------------- |
|
|
44
|
+
| 1 | Identify the input (plan file vs inline paste vs implicit) | [`references/contract.md`](references/contract.md) § 1 |
|
|
45
|
+
| 2 | Extract sections by intent (goal, prereqs, tasks, done-when) | [`references/contract.md`](references/contract.md) § 2 |
|
|
46
|
+
| 2.5 | Anti-patterns gate | [`anti-patterns/core.md`](anti-patterns/core.md) |
|
|
47
|
+
| 3 | Build the generic card | [`references/generic-card.md`](references/generic-card.md) |
|
|
48
|
+
|
|
49
|
+
Adapters then execute Phases 4+ per their own SKILL.md.
|
|
50
|
+
|
|
51
|
+
## CRUD vocabulary (verbs adapters implement)
|
|
52
|
+
|
|
53
|
+
See [`references/crud-vocabulary.md`](references/crud-vocabulary.md). Six verbs, all idempotent (with `clientToken` rules for non-natural cases).
|
|
54
|
+
|
|
55
|
+
## Autonomous mode
|
|
56
|
+
|
|
57
|
+
See [`references/autonomous-mode.md`](references/autonomous-mode.md). Activated only by sentinel `[autonomous]` / `--auto` / env `LLODEV_PM_TASKS_AUTONOMOUS=1`. Requires explicit allowlist + scope + rate limit in the tool's config. Never inferred.
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
Lookup order: `<git-root>/.<tool>.json` → `~/.config/llodev/pm-tasks/<tool>.json` → abort. Secrets NEVER in JSON (env vars / OS keychain only).
|
|
62
|
+
|
|
63
|
+
## Audit log
|
|
64
|
+
|
|
65
|
+
Append-only JSONL at `~/.local/share/llodev/pm-tasks/<tool>/audit.log`. Schema in [`references/audit-log-format.md`](references/audit-log-format.md). Doubles as the lookup index for `<task-ref>` resolution.
|
|
66
|
+
|
|
67
|
+
## Init helper
|
|
68
|
+
|
|
69
|
+
Adapters expose `npx @llodev/pm-tasks-<tool> init`. Shared UX in [`references/init-ux.md`](references/init-ux.md). Implementation library at `./scripts/init-lib.mjs`.
|
|
70
|
+
|
|
71
|
+
## Standalone fallback
|
|
72
|
+
|
|
73
|
+
This skill is not useful without an adapter. If activated alone, tell the user to install at least one `pm-tasks-<tool>` package.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Card & workflow anti-patterns (core)
|
|
2
|
+
|
|
3
|
+
Expert guardrails — violating these wastes board space, breaks paste targets, or misleads reviewers.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Structure & granularity
|
|
8
|
+
|
|
9
|
+
**NEVER** create one checklist item per micro-step from the plan (e.g. "open file", "run linter", "save"). **Why:** the card mirrors **tasks/outcomes**, not every keystroke; noise hides real progress.
|
|
10
|
+
|
|
11
|
+
**NEVER** duplicate the entire plan verbatim into description or checklist. **Why:** defeats the purpose of a summary; links to plan/spec suffice.
|
|
12
|
+
|
|
13
|
+
**NEVER** merge multiple phases into a single card without explicit user consent. **Why:** timelines, ownership, and "done" blur; violates one-card-one-phase default.
|
|
14
|
+
|
|
15
|
+
**NEVER** bury **Pre-flight / prerequisites** inside a random block. **Why:** skipped baselines cause false "blocked" churn; baseline always tops the checklist.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Honesty about sources
|
|
20
|
+
|
|
21
|
+
**NEVER** present **inferred** goals, prerequisites, verification, or out-of-scope as if they appeared in the plan. **Why:** teammates act on fiction. Tag with `(inferred)` or state in conversational wrapper: "Plan had no Done when — verification derived from tasks."
|
|
22
|
+
|
|
23
|
+
**NEVER** invent file paths or spec links not present in the plan. **Why:** stale links wreck trust; use placeholders only if unavoidable: `[link TBD]` and say why.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Paste-ready output
|
|
28
|
+
|
|
29
|
+
**NEVER** put meta-commentary (**"I'll now…"**, rationale paragraphs) **inside** the titled blocks (`TITLE`, `DESCRIPTION`, checklist sections meant for paste). **Why:** pollutes tooling fields. Keep prose **before or after** the paste blocks only.
|
|
30
|
+
|
|
31
|
+
**NEVER** use vague verification lines like `"tests pass"` without naming **which** command or scope. **Why:** unmeasurable; use concrete commands (`pnpm test`, `pytest apps/api/...`) from the plan or mark `(add command)`.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Estimates & sizing
|
|
36
|
+
|
|
37
|
+
**NEVER** report a single point estimate as certainty (e.g. "4h exactly"). **Why:** AI-assisted variance is wide; stick to optimistic — realistic range and optional buffer narrative.
|
|
38
|
+
|
|
39
|
+
**NEVER** apply the tier table blindly to spikes, migrations, or "unknown-unknown" chunks. **Why:** those need **XL** or explicit `"too uncertain — spike first"` flag in timeline notes.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Scale
|
|
44
|
+
|
|
45
|
+
**NEVER** put **100+** checkbox items on one card when blocks exist. **Why:** defeats tracking; split by block per SKILL.md parent/child guidance.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Conflicting or missing input
|
|
50
|
+
|
|
51
|
+
**NEVER** silently pick a phase when multiple plan files apply. **Why:** wrong scope. Prefer: list candidates, recommend one default, confirm.
|
|
52
|
+
|
|
53
|
+
**NEVER** output an empty checklist because the plan "reads like prose". **Why:** salvage with inferred task bullets or ask one clarifying question before shipping empty.
|
|
54
|
+
|
|
55
|
+
**NEVER** invent a **Goal** or **Done when** for task-only plans (no Goal / verification section) without tagging **`(inferred)`** in the conversational wrapper. **Why:** prose-only plans look complete but aren't; teammates treat invented goals as authoritative. Derive from dominant theme / last tasks and say so explicitly.
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@llodev/pm-tasks-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core skill for the pm-tasks-* family: extracts implementation plans into structured task content, defines the CRUD vocabulary (task.create, checklist.check, task.close, task.due-date.set, task.assignee.add, task.comment.add), specifies the autonomous-mode contract, and provides the shared init UX library for tool adapters. Use when an adapter (pm-tasks-trello, pm-tasks-asana, etc.) needs the canonical generic-card structure or when running the init helper for any tool.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/llodev/skills/tree/main/pm-tasks/pm-tasks-core",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/llodev/skills.git",
|
|
10
|
+
"directory": "pm-tasks/pm-tasks-core"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"SKILL.md",
|
|
14
|
+
"references",
|
|
15
|
+
"anti-patterns",
|
|
16
|
+
"scripts",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"README.md",
|
|
19
|
+
"CHANGELOG.md"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"agent-skill",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"cursor",
|
|
25
|
+
"codex",
|
|
26
|
+
"windsurf",
|
|
27
|
+
"plan-to-tasks",
|
|
28
|
+
"pm-tools"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"exports": {
|
|
32
|
+
"./init-lib": "./scripts/init-lib.mjs"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"ajv": "^8.17.1",
|
|
39
|
+
"ajv-formats": "^3.0.1"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Audit log format
|
|
2
|
+
|
|
3
|
+
Append-only JSONL at `~/.local/share/llodev/pm-tasks/<tool>/audit.log` (configurable via `autonomous.auditLog`).
|
|
4
|
+
|
|
5
|
+
## Required fields (every entry)
|
|
6
|
+
|
|
7
|
+
| Field | Type | Notes |
|
|
8
|
+
| --------- | ------- | ----------------------------------------------------------------------- |
|
|
9
|
+
| `ts` | string | ISO 8601 with timezone |
|
|
10
|
+
| `verb` | string | one of the 6 CRUD verbs |
|
|
11
|
+
| `tool` | string | `trello`, `asana`, etc. |
|
|
12
|
+
| `ok` | boolean | success of the operation |
|
|
13
|
+
| `session` | string | caller-provided session id (defaults to a random short id when missing) |
|
|
14
|
+
|
|
15
|
+
## Verb-dependent fields
|
|
16
|
+
|
|
17
|
+
| Verb | Additional |
|
|
18
|
+
| ------------------- | --------------------------------------------------------- |
|
|
19
|
+
| `task.create` | `id`, `url`, `name`, `clientToken?`, `scope.{board,list}` |
|
|
20
|
+
| `checklist.check` | `id`, `item` |
|
|
21
|
+
| `task.close` | `id` |
|
|
22
|
+
| `task.due-date.set` | `id`, `due` |
|
|
23
|
+
| `task.assignee.add` | `id`, `userAlias` |
|
|
24
|
+
| `task.comment.add` | `id`, `commentId`, `clientToken?` |
|
|
25
|
+
|
|
26
|
+
## Example
|
|
27
|
+
|
|
28
|
+
```jsonl
|
|
29
|
+
{"ts":"2026-06-11T18:00:00Z","verb":"task.create","tool":"trello","ok":true,"id":"abc123","url":"https://trello.com/c/abc123/...","name":"Implementar feature X","clientToken":"ct-xyz","scope":{"board":"proj-x","list":"backlog"},"session":"sess-001"}
|
|
30
|
+
{"ts":"2026-06-11T18:05:00Z","verb":"checklist.check","tool":"trello","ok":true,"id":"abc123","item":"build endpoint","session":"sess-001"}
|
|
31
|
+
{"ts":"2026-06-11T18:10:00Z","verb":"task.close","tool":"trello","ok":true,"id":"abc123","session":"sess-001"}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Concurrency
|
|
35
|
+
|
|
36
|
+
Single-line JSON writes < 4KB are atomic at OS level on Linux/macOS. Multiple agents in parallel do not corrupt. No locks.
|
|
37
|
+
|
|
38
|
+
## Rotation
|
|
39
|
+
|
|
40
|
+
`pm-tasks/pm-tasks-core/scripts/rotate-audit.sh` purges entries older than 90 days. Suggested cron entry in this file's README.
|
|
41
|
+
|
|
42
|
+
## Lookup usage
|
|
43
|
+
|
|
44
|
+
The log doubles as the lookup index for `<task-ref>` resolution steps 4–5. Adapter implementations scan from newest to oldest, filter by `scope.boards`, match `clientToken` or name partial.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Autonomous mode
|
|
2
|
+
|
|
3
|
+
Operations executed without a human reviewing each step. Designed for invocation by AI agents during task execution.
|
|
4
|
+
|
|
5
|
+
## Activation (three signals)
|
|
6
|
+
|
|
7
|
+
1. **Prompt sentinel** — caller includes `[autonomous]` or `--auto` literally.
|
|
8
|
+
2. **Env var** — `LLODEV_PM_TASKS_AUTONOMOUS=1` (for CI / cron).
|
|
9
|
+
3. **Never inferred.** Without sentinel or env, preview + human approval is mandatory.
|
|
10
|
+
|
|
11
|
+
## Allowlist gate (in tool config)
|
|
12
|
+
|
|
13
|
+
Autonomous mode requires an `autonomous` block in `.<tool>.json` (or its global counterpart):
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"autonomous": {
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"allow": ["task.create", "checklist.check", "task.close", "task.due-date.set", "task.assignee.add", "task.comment.add"],
|
|
20
|
+
"scope": {
|
|
21
|
+
"boards": ["<board-or-project-id>"],
|
|
22
|
+
"lists": ["<list-or-section-id>"]
|
|
23
|
+
},
|
|
24
|
+
"rateLimit": { "writesPerMinute": 30, "commentsPerMinute": 10 },
|
|
25
|
+
"auditLog": "~/.local/share/llodev/pm-tasks/<tool>/audit.log"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Block missing OR `enabled: false` → autonomous aborts with `{ ok: false, code: "INVALID_CONFIG" }`.
|
|
31
|
+
Verb not in `allow` → `{ code: "ALLOWLIST_VIOLATION" }`.
|
|
32
|
+
Target outside `scope` → `{ code: "OUT_OF_SCOPE" }`.
|
|
33
|
+
Rate exceeded → `{ code: "RATE_LIMITED" }`.
|
|
34
|
+
|
|
35
|
+
## Hard-coded forbidden verbs (v1)
|
|
36
|
+
|
|
37
|
+
Regardless of allowlist:
|
|
38
|
+
|
|
39
|
+
- `task.delete`, `task.archive`
|
|
40
|
+
- `task.rename`
|
|
41
|
+
- `task.description.replace`
|
|
42
|
+
- `task.assignee.remove`, any `member.remove`
|
|
43
|
+
- Any operation outside `scope`
|
|
44
|
+
|
|
45
|
+
Returns `{ code: "FORBIDDEN_VERB" }` without touching MCP.
|
|
46
|
+
|
|
47
|
+
## Write-through flow
|
|
48
|
+
|
|
49
|
+
- Skip Phase 5.x preview & approval.
|
|
50
|
+
- Run allowlist + scope + rate-limit checks.
|
|
51
|
+
- Call MCP write tool.
|
|
52
|
+
- Emit structured envelope to caller (JSON, not human-formatted).
|
|
53
|
+
- Append entry to audit log per [`audit-log-format.md`](audit-log-format.md).
|
|
54
|
+
|
|
55
|
+
## Failure handling
|
|
56
|
+
|
|
57
|
+
Any failure → structured envelope. Skill does NOT auto-retry. Caller decides retry/abort/escalate. If the verb supports `clientToken`, caller can safely retry the exact same call (will be deduped).
|
|
58
|
+
|
|
59
|
+
## Forensics
|
|
60
|
+
|
|
61
|
+
The audit log is the source of truth for "what happened in this autonomous session". Also serves as the lookup index for `taskRef` step 4–5 in [`crud-vocabulary.md`](crud-vocabulary.md).
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# pm-tasks-core ↔ adapter contract
|
|
2
|
+
|
|
3
|
+
> **Versioning:** Renaming or removing a field below is a **MAJOR** bump for `@llodev/pm-tasks-core`. Adding optional fields is a minor bump. CI `contract-check.yml` enforces this.
|
|
4
|
+
|
|
5
|
+
This document is the single source of truth that every `pm-tasks-<tool>` adapter consumes. Adapters MUST execute Phases 1–3 (defined here) before applying their tool-specific formatting.
|
|
6
|
+
|
|
7
|
+
## Phase 1 — Identify the input
|
|
8
|
+
|
|
9
|
+
Resolve which plan text to analyze:
|
|
10
|
+
|
|
11
|
+
1. `@path` in the prompt → read that file (full file, no range limits).
|
|
12
|
+
2. Inline pasted text → use as-is.
|
|
13
|
+
3. "this plan" / implicit → most recently opened plan file in the workspace.
|
|
14
|
+
|
|
15
|
+
Multiple plausible candidates → list them, recommend one default, confirm before proceeding.
|
|
16
|
+
|
|
17
|
+
## Phase 2 — Extract sections by intent
|
|
18
|
+
|
|
19
|
+
Map sections by intent (labels vary across languages and authoring styles):
|
|
20
|
+
|
|
21
|
+
| Concept | Common labels |
|
|
22
|
+
| ------------- | ---------------------------------------------------------- |
|
|
23
|
+
| Goal | `Goal`, `Objective`, `Purpose` |
|
|
24
|
+
| Spec refs | `Spec:`, `Design doc`, links to spec files |
|
|
25
|
+
| Prerequisites | `Pre-flight`, `Requirements`, `Baseline check` |
|
|
26
|
+
| File map | `File map`, `Files created/modified`, `Deliverables` |
|
|
27
|
+
| Tasks | `## Task N`, `### Task N`, numbered blocks |
|
|
28
|
+
| Task groups | `## Block A`, thematic headers grouping tasks |
|
|
29
|
+
| Done criteria | `Done when`, `Self-review checklist`, `Final verification` |
|
|
30
|
+
| Out of scope | `Out of scope`, `What comes after` |
|
|
31
|
+
| Next step | `What comes after`, `Phase N+1`, `Next phase` |
|
|
32
|
+
|
|
33
|
+
Implicit sections → infer from structure. Tasks-only inputs → derive goal from first block, derive verification from last tasks, tag everything inferred with `(inferred)`.
|
|
34
|
+
|
|
35
|
+
## Phase 3 — Build the generic card
|
|
36
|
+
|
|
37
|
+
Produce blocks in the order defined in [`generic-card.md`](generic-card.md):
|
|
38
|
+
|
|
39
|
+
1. `title` — single line, ≤120 chars by default
|
|
40
|
+
2. `description` — goal + context + spec ref
|
|
41
|
+
3. `implementationChecklist` — task-line verbatim or compressed per `fidelity` decision
|
|
42
|
+
4. `verificationChecklist` — derived from done-when or last tasks
|
|
43
|
+
5. `timeline` — AI-assisted estimate
|
|
44
|
+
6. `labels` — derived from tags in the plan
|
|
45
|
+
|
|
46
|
+
## Decisions emitted by the core
|
|
47
|
+
|
|
48
|
+
The core resolves and exposes these decisions before Phase 4 begins. Adapters reference them by name:
|
|
49
|
+
|
|
50
|
+
| Decision | Values | Default |
|
|
51
|
+
| ---------- | ------------------------------------------------------ | -------------------- |
|
|
52
|
+
| `scope` | `one-card-per-phase`, `parent+children`, `single-card` | `one-card-per-phase` |
|
|
53
|
+
| `audience` | `solo`, `squad`, `dense-board` | `solo` |
|
|
54
|
+
| `fidelity` | `verbatim`, `compressed` | `compressed` |
|
|
55
|
+
| `language` | `pt`, `en`, `mixed` | detected from plan |
|
|
56
|
+
|
|
57
|
+
## CRUD verb vocabulary
|
|
58
|
+
|
|
59
|
+
The 6 verbs of v1, with their semantic invariants and idempotency rules, live in [`crud-vocabulary.md`](crud-vocabulary.md). Adapters map each verb to one or more MCP tool calls.
|
|
60
|
+
|
|
61
|
+
## Result envelope
|
|
62
|
+
|
|
63
|
+
Every CRUD operation returns:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"ok": true,
|
|
68
|
+
"verb": "<verb>",
|
|
69
|
+
"tool": "<tool>",
|
|
70
|
+
"ref": { "id": "<native-id>", "url": "<full-url>", "alias": "<alias-or-null>" },
|
|
71
|
+
"details": { /* verb-specific */ }
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Failures:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{ "ok": false, "code": "<CODE>", "verb": "<verb>", "message": "<human>", "details": { } }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Stable error codes: `FORBIDDEN_VERB`, `OUT_OF_SCOPE`, `ALLOWLIST_VIOLATION`, `RATE_LIMITED`, `REF_NOT_RESOLVED`, `MCP_ERROR`, `INVALID_CONFIG`.
|
|
82
|
+
|
|
83
|
+
## Standalone fallback
|
|
84
|
+
|
|
85
|
+
Adapters MUST include a short fallback section in their own `SKILL.md` (~10 lines) for when this core is not installed: ask user for minimum input (title + checklist), skip extraction.
|
|
86
|
+
|
|
87
|
+
## Compatibility notes for adapters
|
|
88
|
+
|
|
89
|
+
- Reference this file by relative path: `pm-tasks/pm-tasks-core/references/contract.md`.
|
|
90
|
+
- Declare `"@llodev/pm-tasks-core"` in `dependencies` for skillpm + Claude Code marketplace cascade. Vercel CLI users must install core manually — note this in the adapter description.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# CRUD vocabulary (v1)
|
|
2
|
+
|
|
3
|
+
Six verbs. Each adapter maps every verb to one or more MCP tool calls.
|
|
4
|
+
|
|
5
|
+
## task.create
|
|
6
|
+
|
|
7
|
+
Create a new task/card/issue from a generic-card spec.
|
|
8
|
+
|
|
9
|
+
- **Inputs:** `genericCard` (per `generic-card.md`), `targetList` (alias from config), `clientToken?` (opaque idempotency key).
|
|
10
|
+
- **Idempotency:** Before creating, search the target scope for any task with matching `clientToken`. If found, return existing ref. Token persistence per tool documented in adapter `operations.md`.
|
|
11
|
+
- **Returns:** envelope with `ref.{id,url}`.
|
|
12
|
+
|
|
13
|
+
## checklist.check
|
|
14
|
+
|
|
15
|
+
Mark a checklist item / subtask as complete.
|
|
16
|
+
|
|
17
|
+
- **Inputs:** `taskRef`, `item` (text or index), `checklistName?` (when task has multiple).
|
|
18
|
+
- **Idempotency:** natural (no-op if already checked).
|
|
19
|
+
- **Returns:** envelope.
|
|
20
|
+
|
|
21
|
+
## task.close
|
|
22
|
+
|
|
23
|
+
Close / move to done.
|
|
24
|
+
|
|
25
|
+
- **Inputs:** `taskRef`.
|
|
26
|
+
- **Adapter semantics:** Trello → move to `closeListAlias`. Asana → `completed: true`. Linear → state `completed`. Notion → status `Done`. Bitrix24 → status `5`. Todoist → `close`.
|
|
27
|
+
- **Idempotency:** natural.
|
|
28
|
+
|
|
29
|
+
## task.due-date.set
|
|
30
|
+
|
|
31
|
+
Set or change due date.
|
|
32
|
+
|
|
33
|
+
- **Inputs:** `taskRef`, `due` (ISO 8601 date or `null` to clear).
|
|
34
|
+
- **Idempotency:** no-op if value matches.
|
|
35
|
+
|
|
36
|
+
## task.assignee.add
|
|
37
|
+
|
|
38
|
+
Add an assignee/member to a task. v1 NEVER removes.
|
|
39
|
+
|
|
40
|
+
- **Inputs:** `taskRef`, `userAlias` (from config `members`).
|
|
41
|
+
- **Idempotency:** adapter checks current assignees before MCP call.
|
|
42
|
+
|
|
43
|
+
## task.comment.add
|
|
44
|
+
|
|
45
|
+
Post a comment / note / story.
|
|
46
|
+
|
|
47
|
+
- **Inputs:** `taskRef`, `body`, `clientToken?`.
|
|
48
|
+
- **Idempotency:** body is prefixed with `[ct:<clientToken>]` if provided. Adapter scans recent comments for matching token before posting.
|
|
49
|
+
|
|
50
|
+
## Verbs forbidden in autonomous mode (v1, hard-coded)
|
|
51
|
+
|
|
52
|
+
`task.delete`, `task.archive`, `task.rename`, `task.description.replace`, `task.assignee.remove`, any `member.remove`, and any operation targeting a board/project outside the declared autonomous `scope`.
|
|
53
|
+
|
|
54
|
+
## `<task-ref>` resolution order
|
|
55
|
+
|
|
56
|
+
Adapters resolve `taskRef` in this order, stopping at first success:
|
|
57
|
+
|
|
58
|
+
1. Full task URL.
|
|
59
|
+
2. Native ID.
|
|
60
|
+
3. Alias in config `taskAliases`.
|
|
61
|
+
4. `clientToken` match in audit log (most recent).
|
|
62
|
+
5. Name partial match in audit log scoped to `autonomous.scope` (most recent).
|
|
63
|
+
6. Otherwise → `{ ok: false, code: "REF_NOT_RESOLVED", candidates: [...] }`.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Tool-agnostic card blocks (title, description, checklists, timeline, labels) for pm-tasks-core Phase 3. Read entire file before building paste blocks.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Generic card blocks (Phase 3)
|
|
6
|
+
|
|
7
|
+
> This file is referenced by every `pm-tasks-<tool>` adapter. Changes here may affect all adapters — coordinate via Changesets.
|
|
8
|
+
|
|
9
|
+
Produce the following blocks **in order**. Adapt section names in Phase 4 per tool (see the adapter's `references/format.md`).
|
|
10
|
+
|
|
11
|
+
For **paste/rendering** when a named tool is chosen, apply the adapter's paste-health rules during Phase 4 — not while drafting generic blocks.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Title
|
|
16
|
+
|
|
17
|
+
Pattern: `[System/Area] — [Phase N] ([short summary of what's built])`
|
|
18
|
+
|
|
19
|
+
Max 80 characters. Examples:
|
|
20
|
+
|
|
21
|
+
- `API Scaffold — Phase 1 (NestJS + shared wiring)`
|
|
22
|
+
- `Celebrations BC — Phase 3 (domain + contracts + HTTP)`
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Description
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
**Goal:** [one sentence from the plan's goal, translated to plain language]
|
|
30
|
+
|
|
31
|
+
**Spec:** [file or document reference]
|
|
32
|
+
**Plan:** [file or document reference]
|
|
33
|
+
**Prerequisite:** [phase or condition that must be true before starting]
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
**Deliverables**
|
|
38
|
+
• [artifact 1]
|
|
39
|
+
• [artifact 2]
|
|
40
|
+
(group by layer/area if more than 7 items)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
**Out of scope**
|
|
45
|
+
• [explicitly deferred item 1]
|
|
46
|
+
• [item 2]
|
|
47
|
+
|
|
48
|
+
**Next step**
|
|
49
|
+
**[Phase N+1 or follow-up name]** — [one sentence]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- Use `**Section**` labels (not `##`) when formatting for tools that flatten Markdown headings on paste.
|
|
53
|
+
- **Publish flows (Phase 5+):** the adapter decides whether to omit **Out of scope** / **Next step** from `desc` / `html_notes` — see the adapter's `references/format.md`.
|
|
54
|
+
- **Generic paste:** include Out of scope and Next step unless the user asks to drop them.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Implementation checklist
|
|
59
|
+
|
|
60
|
+
Structure:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
### Pre-flight
|
|
64
|
+
- [ ] [prerequisite check 1]
|
|
65
|
+
- [ ] [prerequisite check 2]
|
|
66
|
+
|
|
67
|
+
### [Block or Group name] (Tasks N–M)
|
|
68
|
+
- [ ] Task N — [action verb] + [artifact] [(skill or note if relevant)]
|
|
69
|
+
- [ ] Task N+1 — ...
|
|
70
|
+
|
|
71
|
+
### [Next block]
|
|
72
|
+
...
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Rules:
|
|
76
|
+
|
|
77
|
+
- One checkbox per task, not per step. Steps live inside the plan, not the card.
|
|
78
|
+
- If a task includes a skill invocation, note it: `(skill: ts-ddd-entity)`
|
|
79
|
+
- If a plan has no explicit blocks, group tasks by layer: setup, domain, application, infra, http, docs.
|
|
80
|
+
- Pre-flight (baseline verification) always comes first.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Verification checklist
|
|
85
|
+
|
|
86
|
+
Draw from the plan's `Done when` / `Self-review checklist` / `Final verification` section.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
### Verification
|
|
90
|
+
|
|
91
|
+
- [ ] [test command] — passes
|
|
92
|
+
- [ ] [build command] — exits 0
|
|
93
|
+
- [ ] [runtime check] (e.g. curl localhost:3001/health returns expected payload)
|
|
94
|
+
- [ ] [security/quality check] (e.g. no secrets staged, no forbidden imports)
|
|
95
|
+
- [ ] [git hygiene check] (e.g. N commits, one per task)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Timeline estimate
|
|
101
|
+
|
|
102
|
+
Estimate calendar time for a developer using AI assistance (Claude, Cursor, Copilot, etc.).
|
|
103
|
+
|
|
104
|
+
**Complexity tiers per task:**
|
|
105
|
+
|
|
106
|
+
| Tier | Description | AI-assisted estimate |
|
|
107
|
+
| ------------------ | -------------------------------------------- | -------------------- |
|
|
108
|
+
| S — Setup/config | Files, manifests, workspace wiring | 10–20 min |
|
|
109
|
+
| M — Standard TDD | Write test → implement → pass | 20–45 min |
|
|
110
|
+
| L — Complex domain | Entity/aggregate with invariants, many tests | 45–90 min |
|
|
111
|
+
| XL — Large block | 4+ related tasks sharing the same fixture | 1.5–3 h |
|
|
112
|
+
| Docs | README, inline docs, layout updates | 10–20 min |
|
|
113
|
+
|
|
114
|
+
**Estimation formula:**
|
|
115
|
+
|
|
116
|
+
1. Classify each task by tier.
|
|
117
|
+
2. Sum the estimates.
|
|
118
|
+
3. Add 20% buffer for integration, context-switching, and debugging.
|
|
119
|
+
4. Round to a practical unit (hours or days).
|
|
120
|
+
5. Present a range: `optimistic — realistic`.
|
|
121
|
+
|
|
122
|
+
**Format:**
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
### Estimated timeline (AI-assisted)
|
|
126
|
+
|
|
127
|
+
| Scope | Estimate |
|
|
128
|
+
| ------------------------------------------- | ----------------------- |
|
|
129
|
+
| Optimistic (focused session, no blockers) | Xh |
|
|
130
|
+
| Realistic (normal day, some back-and-forth) | Xh / X days |
|
|
131
|
+
| Task count | N tasks across N blocks |
|
|
132
|
+
|
|
133
|
+
> Timeline assumes a developer with AI assistance (Claude/Cursor/Copilot).
|
|
134
|
+
> Solo (no AI): multiply by 3–5×.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Generic output may keep the timeline **as Markdown table**. Adapters may flatten or relocate the table during Phase 4 per their paste-health rules in the adapter's `references/format.md`.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Labels / Tags
|
|
142
|
+
|
|
143
|
+
Suggest **3–6** from plan context: phase, domain/layer (`api`, `web`, …), status, size tier from timeline tiers. Apply the adapter's label palette and caps (defined in the adapter's `references/format.md`).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Init UX (shared across pm-tasks-<tool> adapters)
|
|
2
|
+
|
|
3
|
+
Every `pm-tasks-<tool>` adapter exposes `npx @llodev/pm-tasks-<tool> init` and follows this four-step flow. Implementation library at `pm-tasks/pm-tasks-core/scripts/init-lib.mjs`.
|
|
4
|
+
|
|
5
|
+
## Step 1 — Scope prompt
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Where should the config live?
|
|
9
|
+
> local → ./.{tool}.json (per-repo override)
|
|
10
|
+
global → ~/.config/llodev/pm-tasks/{tool}.json
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Default highlight: `local` if inside a git repo, `global` otherwise.
|
|
14
|
+
|
|
15
|
+
## Step 2 — MCP auto-detect
|
|
16
|
+
|
|
17
|
+
Adapter probes the tool's MCP with the lightest read operation:
|
|
18
|
+
|
|
19
|
+
- Trello → `list_boards`
|
|
20
|
+
- Asana → `list_workspaces`
|
|
21
|
+
- Linear → `viewer`
|
|
22
|
+
- Notion → `users.me`
|
|
23
|
+
- ...
|
|
24
|
+
|
|
25
|
+
Three outcomes:
|
|
26
|
+
|
|
27
|
+
| Outcome | Action |
|
|
28
|
+
| ------------------------------ | ------------------------------------------------------------------------- |
|
|
29
|
+
| MCP responded | Go to Step 3A (MCP-assisted). |
|
|
30
|
+
| MCP exists but unauthenticated | Print exact env var names (`LLODEV_PM_TASKS_<TOOL>_<NAME>`) to set, exit. |
|
|
31
|
+
| MCP not configured | Print MCP setup instructions for the adapter, go to Step 3B (scaffold). |
|
|
32
|
+
|
|
33
|
+
## Step 3A — MCP-assisted (happy path)
|
|
34
|
+
|
|
35
|
+
Read-only MCP calls enumerate workspace → boards/projects → lists/sections → labels → members. User picks via interactive prompts (multi-select for arrays).
|
|
36
|
+
|
|
37
|
+
Final prompt: *"Enable autonomous mode? (adds an `autonomous` block with conservative defaults: 6 verbs, rate 30/min, no hard-coded scope — you add it afterwards)"* — default `n`.
|
|
38
|
+
|
|
39
|
+
Write the config; validate against `schemas/config.json`; exit.
|
|
40
|
+
|
|
41
|
+
## Step 3B — Scaffold (fallback)
|
|
42
|
+
|
|
43
|
+
Write `.{tool}.json` with placeholders and inline `// comments` showing where to find each ID. Print MCP setup instructions referencing the adapter's `references/mcp-config.md`. Exit.
|
|
44
|
+
|
|
45
|
+
## Step 4 — Confirmation
|
|
46
|
+
|
|
47
|
+
Print:
|
|
48
|
+
|
|
49
|
+
- Path written
|
|
50
|
+
- Schema validation result
|
|
51
|
+
- Sample trigger prompt the user can paste (`"create a card on <tool> from this plan"`)
|
|
52
|
+
- Reminder: secrets go in env vars or OS keychain, NEVER in this JSON.
|
|
53
|
+
|
|
54
|
+
## Implementation API (consumed by adapters)
|
|
55
|
+
|
|
56
|
+
Adapter `scripts/init.mjs` imports from `@llodev/pm-tasks-core/init-lib`:
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
import { promptScope, promptYesNo, multiSelect, writeConfig, validateConfig, probeMCP, printInstructions } from "@llodev/pm-tasks-core/init-lib";
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Each adapter implements its own MCP-probing logic and field-mapping; UX primitives are shared.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// @llodev/pm-tasks-core/init-lib
|
|
2
|
+
// Shared init primitives. Node 20+ built-ins + Ajv for schema validation.
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
5
|
+
import { mkdir, writeFile, access, readFile } from "node:fs/promises";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
const rl = () => createInterface({ input, output });
|
|
10
|
+
|
|
11
|
+
export async function promptScope(toolName) {
|
|
12
|
+
const r = rl();
|
|
13
|
+
try {
|
|
14
|
+
console.log(`\nWhere should the ${toolName} config live?\n 1) local → ./.${toolName}.json\n 2) global → ~/.config/llodev/pm-tasks/${toolName}.json`);
|
|
15
|
+
const a = (await r.question("Choose [1/2] (default 1): ")).trim() || "1";
|
|
16
|
+
if (a === "1") return { scope: "local", path: path.resolve(`.${toolName}.json`) };
|
|
17
|
+
if (a === "2") return { scope: "global", path: path.join(homedir(), ".config", "llodev", "pm-tasks", `${toolName}.json`) };
|
|
18
|
+
throw new Error(`invalid choice: ${a}`);
|
|
19
|
+
} finally { r.close(); }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function promptYesNo(question, defaultNo = true) {
|
|
23
|
+
const r = rl();
|
|
24
|
+
try {
|
|
25
|
+
const a = (await r.question(`${question} [y/N]: `)).trim().toLowerCase();
|
|
26
|
+
if (a === "y" || a === "yes") return true;
|
|
27
|
+
return false;
|
|
28
|
+
} finally { r.close(); }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function multiSelect(label, choices) {
|
|
32
|
+
const r = rl();
|
|
33
|
+
try {
|
|
34
|
+
console.log(`\n${label}`);
|
|
35
|
+
choices.forEach((c, i) => console.log(` ${i + 1}) ${c.label}`));
|
|
36
|
+
const a = (await r.question("Select (comma-separated, e.g. 1,3,5): ")).trim();
|
|
37
|
+
const picks = a.split(",").map((x) => parseInt(x.trim(), 10) - 1).filter((i) => i >= 0 && i < choices.length);
|
|
38
|
+
return picks.map((i) => choices[i].value);
|
|
39
|
+
} finally { r.close(); }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function writeConfig(targetPath, data) {
|
|
43
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
44
|
+
try { await access(targetPath); throw new Error(`config already exists at ${targetPath}, aborting`); } catch (e) {
|
|
45
|
+
if (e.code !== "ENOENT") throw e;
|
|
46
|
+
}
|
|
47
|
+
await writeFile(targetPath, JSON.stringify(data, null, 2) + "\n");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function validateConfig(data, schema) {
|
|
51
|
+
// Adapter loads its own schemas/config.json and passes it here for validation.
|
|
52
|
+
const { default: Ajv2020 } = await import("ajv/dist/2020.js");
|
|
53
|
+
const { default: addFormats } = await import("ajv-formats");
|
|
54
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
55
|
+
addFormats(ajv);
|
|
56
|
+
const validate = ajv.compile(schema);
|
|
57
|
+
if (!validate(data)) {
|
|
58
|
+
return { ok: false, errors: validate.errors };
|
|
59
|
+
}
|
|
60
|
+
return { ok: true };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function probeMCP({ tool, probeCommand }) {
|
|
64
|
+
// Caller-supplied async probe function; library only sequences UX.
|
|
65
|
+
try {
|
|
66
|
+
const result = await probeCommand();
|
|
67
|
+
return { mcpAvailable: true, result };
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (/^auth\b|\b(unauthorized|forbidden)\b|\b40[13]\b/i.test(e.message)) {
|
|
70
|
+
return { mcpAvailable: true, unauthenticated: true, error: e.message };
|
|
71
|
+
}
|
|
72
|
+
return { mcpAvailable: false, error: e.message };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function printInstructions(lines) {
|
|
77
|
+
console.log(`\n${lines.join("\n")}\n`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function readJsonIfExists(p) {
|
|
81
|
+
try {
|
|
82
|
+
const src = await readFile(p, "utf8");
|
|
83
|
+
return JSON.parse(src);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
if (e.code === "ENOENT") return null;
|
|
86
|
+
throw e;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Rotate audit log for a given tool. Keeps last 90 days.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
TOOL="${1:-}"
|
|
6
|
+
if [[ -z "$TOOL" ]]; then
|
|
7
|
+
echo "usage: rotate-audit.sh <tool>"
|
|
8
|
+
echo "example: rotate-audit.sh trello"
|
|
9
|
+
exit 2
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
LOG_DIR="${LLODEV_PM_TASKS_LOG_DIR:-$HOME/.local/share/llodev/pm-tasks}/$TOOL"
|
|
13
|
+
LOG="$LOG_DIR/audit.log"
|
|
14
|
+
KEEP_DAYS="${KEEP_DAYS:-90}"
|
|
15
|
+
|
|
16
|
+
[[ ! -f "$LOG" ]] && { echo "(no log at $LOG, nothing to rotate)"; exit 0; }
|
|
17
|
+
|
|
18
|
+
CUTOFF=$(date -u -v -"${KEEP_DAYS}"d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "${KEEP_DAYS} days ago" +%Y-%m-%dT%H:%M:%SZ)
|
|
19
|
+
TMP="$(mktemp)"
|
|
20
|
+
|
|
21
|
+
awk -v cutoff="$CUTOFF" 'match($0, /"ts":"([^"]+)"/, m) { if (m[1] >= cutoff) print }' "$LOG" > "$TMP"
|
|
22
|
+
|
|
23
|
+
mv "$TMP" "$LOG"
|
|
24
|
+
echo "rotated $LOG (kept entries >= $CUTOFF)"
|