toga-ai 1.0.293 → 1.0.295
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.
|
@@ -6,12 +6,13 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
10
|
-
owners: ["bala"]
|
|
9
|
+
updated: 2026-07-09
|
|
10
|
+
owners: ["bala", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
13
13
|
related:
|
|
14
14
|
- ../architecture.md
|
|
15
|
+
- ../../../clients/aig/features/entitlement-intake.md
|
|
15
16
|
---
|
|
16
17
|
|
|
17
18
|
## Summary
|
|
@@ -57,6 +58,37 @@ rows on every write.
|
|
|
57
58
|
If you need to link by a business key (like an employee XID), **resolve that key to a uuid in your
|
|
58
59
|
caller first** (e.g. build a `key → uuid` map), then send `{uuid}`.
|
|
59
60
|
|
|
61
|
+
## Per-API overrides (`Apis_RecordFields`)
|
|
62
|
+
|
|
63
|
+
The base identifier flags above (`Core.RecordFields.isIdentifier`) can be **overridden per API**
|
|
64
|
+
via rows in a tenant's `Apis_RecordFields` table, read by the V2 resolver's override block
|
|
65
|
+
(`V2.php` ~6926–6936; child policy in `getChildPolicy()` ~8019–8041; loader ~8526–8538). Two
|
|
66
|
+
columns matter:
|
|
67
|
+
|
|
68
|
+
- **`overrideIsIdentifier`** — overrides whether the field is a searchable identifier for that API.
|
|
69
|
+
- **`overrideChildPolicy`** — overrides link-vs-create for the nested record (e.g. `MATCH_CREATE`
|
|
70
|
+
= match an existing child by identifier, else create it).
|
|
71
|
+
|
|
72
|
+
> **Trap: a `NULL` `overrideIsIdentifier` is read as "not an identifier", not "no opinion".**
|
|
73
|
+
> The override block evaluates `!$row->overrideIsIdentifier`, so a `NULL` **unsets** the field
|
|
74
|
+
> from the API's searchable identifiers even when the base `Core.RecordFields` row has
|
|
75
|
+
> `isIdentifier=1`. Any `Apis_RecordFields` row created **only** to set a child policy
|
|
76
|
+
> (leaving `overrideIsIdentifier` NULL) will silently strip that field as an identifier. The
|
|
77
|
+
> nested object then has no searchable identifier and the write fails **EV-12 VALIDATION**
|
|
78
|
+
> (`V2.php:7094`) — the `MATCH_CREATE` that should have saved it never runs on that path.
|
|
79
|
+
> Correct the data with an explicit `overrideIsIdentifier = 1`, or (systemic fix, not yet done)
|
|
80
|
+
> change the code so only an explicit `0` removes an identifier and `NULL` means "no override".
|
|
81
|
+
|
|
82
|
+
**Build divergence warning.** The `Apis_RecordFields` override feature (reading
|
|
83
|
+
`overrideIsIdentifier`/`overrideChildPolicy`) was introduced 2026-03-20 (api2 commit `624146d`
|
|
84
|
+
"Cache custom fields and API override lookups"). Environments on **older** builds ignore the
|
|
85
|
+
override and keep the base identifier, so a tenant with identical Core metadata + override rows +
|
|
86
|
+
data can behave differently across environments purely by build version. A NULL-`overrideIsIdentifier`
|
|
87
|
+
override row that works on prod (older build) will start throwing EV-12 once prod gets the newer
|
|
88
|
+
build — audit such rows before/with the deploy. First seen in AIG (TRUE-79978): prod resolved,
|
|
89
|
+
beta/QA threw EV-12 on the injected `entitlementFulfillmentType` — see
|
|
90
|
+
[AIG entitlement intake](../../../clients/aig/features/entitlement-intake.md).
|
|
91
|
+
|
|
60
92
|
## Gotcha
|
|
61
93
|
|
|
62
94
|
- **Nested write with only a non-identifier field silently creates duplicates.** This is a
|
|
@@ -68,6 +100,11 @@ caller first** (e.g. build a `key → uuid` map), then send `{uuid}`.
|
|
|
68
100
|
|
|
69
101
|
## Change history
|
|
70
102
|
|
|
103
|
+
- 2026-07-09 — Documented the per-API `Apis_RecordFields` override layer (`overrideIsIdentifier`,
|
|
104
|
+
`overrideChildPolicy`) and the trap that a `NULL` `overrideIsIdentifier` is read as "not an
|
|
105
|
+
identifier" (`!$row->overrideIsIdentifier`) — an override row set only for a child policy
|
|
106
|
+
silently strips the field as an identifier → EV-12 at `V2.php:7094`. Added the build-divergence
|
|
107
|
+
warning (override feature added 2026-03-20, commit `624146d`). Surfaced by AIG TRUE-79978. (mhammontree)
|
|
71
108
|
- 2026-07-07 — Documented that api2 nested-relationship writes match an existing child **only** by
|
|
72
109
|
identifier (`uuid`); a non-unique custom field is not a match key and forces a new-record insert.
|
|
73
110
|
Correct pattern is `contact: {uuid}` (resolve business keys to uuid in the caller). Surfaced by
|
|
@@ -78,3 +115,5 @@ caller first** (e.g. build a `key → uuid` map), then send `{uuid}`.
|
|
|
78
115
|
- [api2 architecture](../architecture.md) — the V2 CRUD engine (`processRoutePairs`).
|
|
79
116
|
- [Prudential device import + contact linking](../../../clients/prudential/features/device-information-import-and-contact-linking.md)
|
|
80
117
|
— the production incident that exposed this behavior.
|
|
118
|
+
- [AIG entitlement intake](../../../clients/aig/features/entitlement-intake.md) — the TRUE-79978
|
|
119
|
+
EV-12 that exposed the `Apis_RecordFields` NULL-`overrideIsIdentifier` trap + build divergence.
|
|
@@ -5,12 +5,13 @@ project: API
|
|
|
5
5
|
client: aig
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-
|
|
8
|
+
updated: 2026-07-09
|
|
9
9
|
owners: ["mhammontree"]
|
|
10
10
|
files:
|
|
11
11
|
- _underscore/Model/Aig/Entitlement.php
|
|
12
12
|
- dbchanges2/Client_Aig/2026-06-18a - TRUE-79534 AIG SaleItem codes.sql
|
|
13
13
|
related:
|
|
14
|
+
- 2.0/apps/api2/features/nested-relationship-writes.md
|
|
14
15
|
- 2.0/apps/api2/architecture.md
|
|
15
16
|
- 2.0/apps/dbchanges2/architecture.md
|
|
16
17
|
- 2.0/apps/_underscore/architecture.md
|
|
@@ -42,7 +43,9 @@ the FK can't resolve and intake fails with **"Missing AIG item ID."** Keeping
|
|
|
42
43
|
- reads `partNumber = payload->saleItem->partNumber` and runs
|
|
43
44
|
`SELECT id FROM Items WHERE partNumber = '{partNumber}' LIMIT 1` to get `$itemId`;
|
|
44
45
|
- uses `$itemId` to load fulfillment **types** (`Items_EntitlementFulfillmentTypes`) and
|
|
45
|
-
**methods** (`Items_EntitlementFulfillmentMethods`) and injects them into the payload
|
|
46
|
+
**methods** (`Items_EntitlementFulfillmentMethods`) and injects them into the payload as
|
|
47
|
+
**name-only** related objects (e.g. `entitlementFulfillmentType: {name: "Repair"}`). AIG
|
|
48
|
+
never sends `entitlementFulfillmentType`; it is populated entirely by this interceptor.
|
|
46
49
|
3. The V2 engine resolves `Entitlement.saleItemId` (FK → `_Model_Client_Item`) from that same
|
|
47
50
|
`saleItem.partNumber`. **No matching `Items` row → unresolvable FK → "Missing AIG item ID".**
|
|
48
51
|
4. `postPost` then emails the contact a Staples Protection Plan registration link
|
|
@@ -118,9 +121,28 @@ this interceptor or use this dual-purpose Items pattern.
|
|
|
118
121
|
codes are **not** in the SaleItemID spreadsheet and must be sourced separately if missing.
|
|
119
122
|
- **No unique key on `Items.partNumber`** — use an anti-join for idempotent loads, not
|
|
120
123
|
`INSERT IGNORE`.
|
|
124
|
+
- **An `entitlementFulfillmentType` EV-12 VALIDATION error is NOT a bad client payload —
|
|
125
|
+
it's this interceptor's injected field failing to resolve.** AIG never sends that field;
|
|
126
|
+
the interceptor injects it as `{name: "Repair"}`. The error means the V2 resolver could not
|
|
127
|
+
find a searchable identifier for the injected object. Root cause (TRUE-79978) was an
|
|
128
|
+
`Apis_RecordFields` override row that stripped `name` as an identifier — see
|
|
129
|
+
[api2 nested-relationship writes → Per-API overrides](../../../2.0/apps/api2/features/nested-relationship-writes.md).
|
|
130
|
+
- **Empty `saleItem.partNumber` silently resolves to the wrong item.** The lookup is
|
|
131
|
+
`WHERE partNumber = '{$partNumber}' LIMIT 1`; when `partNumber` is `""`, it matches item
|
|
132
|
+
`id=10` ("2YR Tablet") — the first row with an empty part number — which carries a "Repair"
|
|
133
|
+
fulfillment type. So a blank part number does not fail loudly; it injects a spurious
|
|
134
|
+
name-only fulfillment type. The interceptor should guard an empty/failed lookup before
|
|
135
|
+
injecting (not yet fixed).
|
|
121
136
|
|
|
122
137
|
## Change history
|
|
123
138
|
|
|
139
|
+
- 2026-07-09 — TRUE-79978: root-caused an `entitlementFulfillmentType` EV-12 on beta/QA. The
|
|
140
|
+
field is injected (name-only) by `prePost`; the beta `Client_Aig.Apis_RecordFields` override
|
|
141
|
+
row (apiId=2, recordFieldId=1202) had `overrideIsIdentifier=NULL` + `overrideChildPolicy=MATCH_CREATE`,
|
|
142
|
+
which the newer V2 build reads as "not an identifier", stripping `name` → no searchable id →
|
|
143
|
+
EV-12. Unblocked with a manual one-row beta data fix (`SET overrideIsIdentifier=1`); prod runs
|
|
144
|
+
an older build without the override feature so it still resolves. See the api2 feature doc for
|
|
145
|
+
the shared resolver behavior. (mhammontree)
|
|
124
146
|
- 2026-06-19 — Documented intake flow; loaded 2,200 new SaleItem codes (1,700 STS `ASI-*`/`SM-*`
|
|
125
147
|
+ 500 unique numeric `SA`) into `Client_Aig.Items` via TRUE-79534, fixing "Missing AIG item ID"
|
|
126
148
|
on the new `ASI-*` scheme. Follow-up to TRUE-79441. (mhammontree)
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: work-ticket
|
|
3
|
-
description: Work an APPROVED ClickUp ticket plan end-to-end. Invoke as `/work-ticket <TICKET-ID>` (e.g. `/work-ticket TRUE-79868`) AFTER `/plan-ticket` has pushed an approved plan to the ticket's Pseudocode field. Loads the plan from the ticket's Pseudocode field, primes framework context via /kickoff (self-answered), implements the plan phase-by-phase with TOGA reviewers, then
|
|
3
|
+
description: Work an APPROVED ClickUp ticket plan end-to-end. Invoke as `/work-ticket <TICKET-ID>` (e.g. `/work-ticket TRUE-79868`) AFTER `/plan-ticket` has pushed an approved plan to the ticket's Pseudocode field. Loads the plan from the ticket's Pseudocode field, primes framework context via /kickoff (self-answered), per repo creates and checks out a bare ticket-ID branch from the fresh remote default BEFORE any edits, implements the plan phase-by-phase with TOGA reviewers, then commits, pushes, opens a PR, and posts the PR links as a comment on the ticket. Stops after PRs are open for your review — never changes ClickUp ticket status. Trigger on "/work-ticket", "work the ticket", "execute the plan for <ticket>", "build and PR <ticket>", "ship <ticket>".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# work-ticket — approved plan →
|
|
6
|
+
# work-ticket — approved plan → branch → code → commit → PR → ClickUp GitHub tab
|
|
7
7
|
|
|
8
8
|
The downstream companion to **`/plan-ticket`**. Given a ticket whose plan has already been
|
|
9
9
|
approved and pushed to its **📝 Pseudocode** field, this skill executes that plan: it primes
|
|
10
|
-
context,
|
|
11
|
-
**bare ticket id
|
|
10
|
+
context, then for **each repo the plan touches** creates and checks out a branch named the
|
|
11
|
+
**bare ticket id** (cut from the freshly-fetched remote default) **BEFORE writing any code**,
|
|
12
|
+
writes the code on that branch, commits, pushes, opens a PR, and posts the PR links as a
|
|
13
|
+
ticket comment.
|
|
12
14
|
ClickUp's native GitHub integration *may* surface those PRs in the ticket's **GitHub tab** when
|
|
13
|
-
the repo is connected in ClickUp's GitHub settings (see Step
|
|
15
|
+
the repo is connected in ClickUp's GitHub settings (see Step 6b — not guaranteed), because the
|
|
14
16
|
ticket id is in the
|
|
15
17
|
branch name (and PR title/body).
|
|
16
18
|
|
|
@@ -62,14 +64,52 @@ Pass them as the trailing argument so kickoff goes straight to preflight + primi
|
|
|
62
64
|
/kickoff <framework> <layer>, repos: <repos>, client: <client> — execute <TICKET> <title>
|
|
63
65
|
```
|
|
64
66
|
|
|
65
|
-
Carry the `context-primer` briefing (framework rules, gotchas, client variations) into Step
|
|
67
|
+
Carry the `context-primer` briefing (framework rules, gotchas, client variations) into Step 4 —
|
|
66
68
|
it directly informs how each phase is implemented. Only stop to ask the developer if a kickoff
|
|
67
69
|
gate answer is genuinely ambiguous after reading the plan header.
|
|
68
70
|
|
|
69
|
-
## Step 3 —
|
|
71
|
+
## Step 3 — Per repo: create & checkout the ticket branch BEFORE any edits
|
|
72
|
+
|
|
73
|
+
**Branch FIRST, edit SECOND — never write a single line while sitting on `_main`/`_production`
|
|
74
|
+
or on another ticket's branch.** Two reasons this ordering is mandatory:
|
|
75
|
+
|
|
76
|
+
1. **Safety** — if work-in-progress gets committed (by you, a hook, or the developer), it lands
|
|
77
|
+
on the ticket branch, never on a default branch that deploys to prod.
|
|
78
|
+
2. **Fresh base** — edits are made against the CURRENT remote default, not a stale local
|
|
79
|
+
checkout. (Real incident: a local `_production` was 12 commits behind and the target file had
|
|
80
|
+
changed upstream — editing before branching would have based the PR on stale code and
|
|
81
|
+
silently reverted the upstream changes.)
|
|
82
|
+
|
|
83
|
+
For **each repo in the plan's `Repos:` header**, run inside that repo's path:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# 0. Working tree must be clean before starting — surface any stray edits to the developer
|
|
87
|
+
git -C "<repo-path>" status --porcelain
|
|
88
|
+
# 1. Resolve the repo's REAL default branch (e.g. _production for app repos, _main for dbchanges2)
|
|
89
|
+
DEFAULT=$(gh repo view <owner/repo> --json defaultBranchRef -q .defaultBranchRef.name)
|
|
90
|
+
# 2. Fetch so the base is current
|
|
91
|
+
git -C "<repo-path>" fetch origin --quiet
|
|
92
|
+
# 3. Reuse an existing TRUE-XXXX branch if present; otherwise cut a fresh one from origin/<default>
|
|
93
|
+
git -C "<repo-path>" rev-parse --verify TRUE-XXXX 2>/dev/null \
|
|
94
|
+
&& git -C "<repo-path>" checkout TRUE-XXXX \
|
|
95
|
+
|| git -C "<repo-path>" checkout -b TRUE-XXXX "origin/$DEFAULT"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- Branch name = **bare ticket id** (team convention; what ClickUp's GitHub integration links on).
|
|
99
|
+
- If `git status` shows uncommitted changes from a prior task, **stop and ask the developer**
|
|
100
|
+
before touching that repo — do not stash someone's work silently.
|
|
101
|
+
- If reusing an existing `TRUE-XXXX` branch, still fetch and consider rebasing onto
|
|
102
|
+
`origin/<default>` so the PR diff stays current.
|
|
103
|
+
- **Never branch off / commit to `_main`/`_production` directly**, and **never force-push**
|
|
104
|
+
(per `git-workflow.md`).
|
|
105
|
+
|
|
106
|
+
Only after every in-scope repo is sitting on its ticket branch do you start writing code.
|
|
107
|
+
|
|
108
|
+
## Step 4 — Execute the plan, phase by phase
|
|
70
109
|
|
|
71
110
|
Implement the plan against the **real repo paths** (resolve each from the `repo-path-<repo>`
|
|
72
|
-
memory kickoff established; never guess paths)
|
|
111
|
+
memory kickoff established; never guess paths) — each repo already checked out on its ticket
|
|
112
|
+
branch from Step 3. For non-trivial work drive it as implement →
|
|
73
113
|
verify with subagents so the conversation stays the conductor:
|
|
74
114
|
|
|
75
115
|
1. For each **phase** in the plan, an implementer subagent writes that phase's code at the
|
|
@@ -94,34 +134,13 @@ verify with subagents so the conversation stays the conductor:
|
|
|
94
134
|
|
|
95
135
|
Do **not** push anything to a remote or open a PR until the code is implemented and clean.
|
|
96
136
|
|
|
97
|
-
## Step
|
|
137
|
+
## Step 5 — Per repo: commit & push
|
|
98
138
|
|
|
99
139
|
For **each repo in the plan's `Repos:` header** that has changes, run inside that repo's path:
|
|
100
140
|
|
|
101
|
-
1. **
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
`origin/<default>` — never from whatever local branch the repo happens to be sitting on.** A
|
|
105
|
-
working checkout is frequently parked on a *different, unmerged ticket's branch* (or a stale
|
|
106
|
-
local default); branching off that silently bases your PR on someone else's unmerged work and
|
|
107
|
-
produces a wrong, conflict-prone diff. So always **fetch first, then branch off the remote
|
|
108
|
-
default**, carrying your uncommitted Step 3 changes onto the new branch:
|
|
109
|
-
```bash
|
|
110
|
-
# Resolve the repo's REAL default branch (e.g. _production for app repos, _main for dbchanges2)
|
|
111
|
-
DEFAULT=$(gh repo view <owner/repo> --json defaultBranchRef -q .defaultBranchRef.name)
|
|
112
|
-
git -C "<repo-path>" fetch origin --quiet
|
|
113
|
-
# Reuse an existing TRUE-XXXX branch if present; otherwise cut a fresh one from origin/<default>.
|
|
114
|
-
git -C "<repo-path>" rev-parse --verify TRUE-XXXX 2>/dev/null \
|
|
115
|
-
&& git -C "<repo-path>" checkout TRUE-XXXX \
|
|
116
|
-
|| git -C "<repo-path>" checkout -b TRUE-XXXX "origin/$DEFAULT"
|
|
117
|
-
```
|
|
118
|
-
`git checkout -b … origin/$DEFAULT` keeps your uncommitted working-tree edits and re-bases them
|
|
119
|
-
onto the latest remote default in one step. **First confirm `git status` shows only the files
|
|
120
|
-
your plan changed** (no stray edits from a prior ticket) before branching. If the checkout is
|
|
121
|
-
refused because a file you edited also changed on the default, `git stash` → `checkout -b … origin/$DEFAULT`
|
|
122
|
-
→ `git stash pop` and resolve. **Never branch off / commit to `_main`/`_production` directly**,
|
|
123
|
-
and **never force-push** (per `git-workflow.md`). If already on the `TRUE-XXXX` branch, still
|
|
124
|
-
`git fetch` and consider rebasing onto `origin/<default>` so the PR diff stays current.
|
|
141
|
+
1. **Verify the branch.** Confirm `git branch --show-current` is the `TRUE-XXXX` branch from
|
|
142
|
+
Step 3 — if the repo somehow ended up back on a default branch, go redo Step 3 for it before
|
|
143
|
+
committing anything.
|
|
125
144
|
2. **Commit.** Stage only the files the plan changed. Message format `type: short description`
|
|
126
145
|
(`feat`/`fix`/`refactor`/`docs`/`test`/`chore`, lowercase, present tense, ≤72 chars). Add a
|
|
127
146
|
body paragraph for substantial changes. **Pre-commit check:** `php -l` passes on changed PHP,
|
|
@@ -130,7 +149,7 @@ For **each repo in the plan's `Repos:` header** that has changes, run inside tha
|
|
|
130
149
|
|
|
131
150
|
Repeat for every in-scope repo — one branch per repo, all named the same bare ticket id.
|
|
132
151
|
|
|
133
|
-
## Step
|
|
152
|
+
## Step 6 — Open a PR per repo (auto-links to the ClickUp GitHub tab)
|
|
134
153
|
|
|
135
154
|
For each pushed repo, open a PR with `gh` from that repo's path:
|
|
136
155
|
|
|
@@ -158,7 +177,7 @@ gh pr create --repo <owner/repo> --base <DEFAULT-BRANCH> --head <TICKET> \
|
|
|
158
177
|
- End the PR body with the standard footer:
|
|
159
178
|
`🤖 Generated with [Claude Code](https://claude.com/claude-code)`.
|
|
160
179
|
|
|
161
|
-
### Step
|
|
180
|
+
### Step 6b — Post the PR links as a ClickUp comment (the RELIABLE link — do NOT skip)
|
|
162
181
|
|
|
163
182
|
**The native GitHub↔ClickUp tab is NOT reliable in this workspace.** It only populates if each
|
|
164
183
|
repo is connected under ClickUp → Settings → Integrations → GitHub (a one-time workspace OAuth
|
|
@@ -181,12 +200,12 @@ fetch("https://api.clickup.com/api/v2/task/<TICKET>/comment?custom_task_ids=true
|
|
|
181
200
|
(A comment does NOT populate the GitHub *tab* — it lands in the ticket's activity/comments —
|
|
182
201
|
but it is the dependable way for the developer to reach the PRs from the ticket.)
|
|
183
202
|
|
|
184
|
-
## Step
|
|
203
|
+
## Step 7 — Report & stop (do NOT touch ClickUp status)
|
|
185
204
|
|
|
186
205
|
Summarize for the developer:
|
|
187
206
|
- Ticket title and the repos that got changes.
|
|
188
207
|
- For each repo: the branch name (`<TICKET>`) and the **PR url** `gh` returned.
|
|
189
|
-
- Confirm the Step
|
|
208
|
+
- Confirm the Step 6b comment was posted (the reliable link). State plainly that the GitHub
|
|
190
209
|
**tab** populates only if the repos are connected in ClickUp's GitHub integration settings —
|
|
191
210
|
do not claim it will appear automatically.
|
|
192
211
|
- **Remind the developer the next step is theirs:** review/approve the PR(s), then **manually
|
|
@@ -207,7 +226,7 @@ Offer `/capture` if a durable, KB-worthy finding emerged while implementing.
|
|
|
207
226
|
- **Linking is NOT automatic.** The native GitHub tab requires a per-repo connection in ClickUp's
|
|
208
227
|
GitHub integration settings (workspace OAuth, not doable from `gh`). The branch name + bare-id
|
|
209
228
|
PR title + `CU-<internalId>` in the body are necessary but **not sufficient** — if the repo
|
|
210
|
-
isn't connected, the tab stays empty. Step
|
|
229
|
+
isn't connected, the tab stays empty. Step 6b's comment is the reliable link; always post it.
|
|
211
230
|
- **Autonomy boundary:** autonomous through PR creation; stops there. Moving the ClickUp ticket
|
|
212
231
|
is always a manual developer action.
|
|
213
232
|
- Run AFTER `/plan-ticket` has produced and the developer has approved a plan. This skill does
|