@lumoai/cli 1.55.0 → 1.57.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/assets/skill/SKILL.md +73 -2
- package/assets/skill/references/criteria.md +1 -1
- package/assets/skill/references/priority.md +6 -5
- package/assets/skill/references/tasks.md +15 -3
- package/assets/skill/references/verify.md +18 -13
- package/assets/skill/references/worktree.md +19 -5
- package/dist/cli/src/commands/idea-comment.js +127 -0
- package/dist/cli/src/commands/idea-figma-add.js +30 -0
- package/dist/cli/src/commands/idea-figma-context.js +65 -0
- package/dist/cli/src/commands/idea-figma-list.js +32 -0
- package/dist/cli/src/commands/idea-figma-refresh.js +40 -0
- package/dist/cli/src/commands/idea-figma-rm.js +19 -0
- package/dist/cli/src/commands/idea-slack-add.js +66 -0
- package/dist/cli/src/commands/idea-slack-rm.js +63 -0
- package/dist/cli/src/commands/idea-slack-show.js +65 -0
- package/dist/cli/src/commands/idea-web-add.js +66 -0
- package/dist/cli/src/commands/idea-web-rm.js +63 -0
- package/dist/cli/src/commands/idea-web-show.js +71 -0
- package/dist/cli/src/commands/idea.js +140 -0
- package/dist/cli/src/commands/initiative.js +132 -0
- package/dist/cli/src/commands/next.js +9 -1
- package/dist/cli/src/commands/session-attach.js +4 -0
- package/dist/cli/src/commands/setup.js +17 -8
- package/dist/cli/src/commands/task-comment-list.js +2 -1
- package/dist/cli/src/commands/task-context.js +5 -1
- package/dist/cli/src/commands/task-create.js +18 -1
- package/dist/cli/src/commands/task-update.js +16 -0
- package/dist/cli/src/commands/worktree-add.js +16 -4
- package/dist/cli/src/commands/worktree-list.js +1 -1
- package/dist/cli/src/commands/worktree-rm.js +10 -2
- package/dist/cli/src/index.js +118 -3
- package/dist/cli/src/lib/idea-figma-api.js +65 -0
- package/dist/cli/src/lib/next-steps.js +55 -0
- package/dist/cli/src/lib/worktree-ref.js +53 -0
- package/dist/cli/src/lib/worktree.js +13 -11
- package/package.json +1 -1
package/assets/skill/SKILL.md
CHANGED
|
@@ -74,12 +74,73 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
74
74
|
|
|
75
75
|
- `lumo idea "<statement>" [--context <text>]` — capture a team-level idea into the pool in <10s. Provenance is grabbed opportunistically and never blocks the capture: the current `CLAUDE_CODE_SESSION_ID` (→ sourceSessionId) and the session's bound task (→ sourceTaskId, null when nothing is bound). Prints an **I-prefixed** id (`✓ 想法 LUM-I42 已入池`) — the `I` keeps ideas from colliding with task ids (`LUM-42`) in the same team namespace. Ideas are team-scoped (no project); the workspace's default team owns them. The unprocessed (CAPTURED) pool is what the Phase-2 transformer consumes.
|
|
76
76
|
- **When to suggest**: the user has a stray idea/thought/improvement they want to park without derailing the current task ("记一下这个想法", "capture this idea", "add to the backlog of ideas"). Prefer this over `task create` for un-triaged sparks — an idea is above projects and isn't yet actionable work.
|
|
77
|
+
- **Example**:
|
|
78
|
+
```bash
|
|
79
|
+
lumo idea "Add a quick-capture keyboard shortcut" --context "raised in standup"
|
|
80
|
+
```
|
|
81
|
+
- `lumo idea list` — print the team idea pool **newest-first**, one line per idea: the `LUM-I<n>` id, status (`CAPTURED` | `DEVELOPING` | `PLANNED` | `DROPPED`) and statement. Reads `GET /api/ideas`, which returns the whole pool (every status), not just the unprocessed slice. Empty pool prints `No ideas.`. The same pool is browsable/searchable in web at `/workspace/<slug>/ideas` (sidebar → Ideas), where a PLANNED idea links to the plan run that materialised it and the status filter hides DROPPED by default.
|
|
82
|
+
- **When to suggest**: the user wants to see what's parked in the idea pool / "看看想法池" / "list the ideas" / triage before running `lumo plan`.
|
|
83
|
+
- **Example**:
|
|
84
|
+
```bash
|
|
85
|
+
lumo idea list
|
|
86
|
+
```
|
|
87
|
+
- `lumo idea update <id> --status <captured|developing|planned|dropped>` — move an idea through its lifecycle (LUM-679). `<id>` is the `LUM-I<n>` id (a bare number also resolves); `--status` is case-insensitive. The four states are `CAPTURED` (pooled) → `DEVELOPING` (in triage / pre-work) → `PLANNED` (consumed by an initiative/plan), with `DROPPED` the terminal discard. Allowed transitions: `CAPTURED↔DEVELOPING`, `DEVELOPING→PLANNED`, and **any non-terminal → DROPPED**; nothing transitions out of `DROPPED`, and `CAPTURED→PLANNED` is not a manual move (only the converter's materialisation lands an idea in PLANNED). An illegal move is rejected with a clear error (400); an unknown id → 404.
|
|
88
|
+
- **When to suggest**: the user wants to triage the pool by hand — start pre-work on an idea ("开始预研这个想法" → `developing`), or discard one ("废弃/丢掉这个想法" → `dropped`). The status control also lives on the Idea detail page in web.
|
|
89
|
+
- **Example**:
|
|
90
|
+
```bash
|
|
91
|
+
lumo idea update LUM-I42 --status developing
|
|
92
|
+
lumo idea update LUM-I42 --status dropped
|
|
93
|
+
```
|
|
94
|
+
- `lumo idea slack add/show/rm <idea> ...`, `lumo idea web add/show/rm <idea> ...`, `lumo idea figma add/list/rm/refresh/context <idea> ...` (LUM-681) — attach & inspect evidence (Slack threads, web links, Figma design links) on an idea, mirroring the `task slack/web/figma` source-card commands but hitting `/api/ideas/:id/...`. `<idea>` is the `LUM-I<n>` id. `show`/`context` are Tier-2 retrieval (stored snapshot / fetched body / cached design metadata, no live re-fetch) and stamp the disclosure funnel like their task-side counterparts; `figma refresh` re-fetches metadata for every link on the idea.
|
|
95
|
+
- **When to suggest**: the user wants to attach supporting evidence (a Slack discussion, a spec doc link, a Figma mock) to an idea before it goes through `lumo plan` — the converter carries this evidence forward into the resulting task/initiative. Prefer attaching to the idea over waiting until after conversion.
|
|
96
|
+
- **Example**:
|
|
97
|
+
```bash
|
|
98
|
+
lumo idea web add LUM-I42 https://example.com/spec
|
|
99
|
+
lumo idea web show LUM-I42 wl_1
|
|
100
|
+
lumo idea web rm LUM-I42 wl_1
|
|
101
|
+
lumo idea slack add LUM-I42 https://myteam.slack.com/archives/C1/p123
|
|
102
|
+
lumo idea slack show LUM-I42 ctx_1
|
|
103
|
+
lumo idea slack rm LUM-I42 ctx_1
|
|
104
|
+
lumo idea figma add LUM-I42 https://www.figma.com/file/abc/Mock
|
|
105
|
+
lumo idea figma list LUM-I42
|
|
106
|
+
lumo idea figma context LUM-I42 cfl_1
|
|
107
|
+
lumo idea figma refresh LUM-I42
|
|
108
|
+
lumo idea figma rm LUM-I42 cfl_1
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- `lumo idea comment <id> <body>` — post a comment on an idea (LUM-680). `<id>` is the `LUM-I<n>` id (a bare number also resolves) or a cuid; the body is plain text — quote it to pass spaces/newlines. Ideas reuse the task Comment model via a polymorphic `ideaId`; the thread is flat (no replies). Posting also records a `COMMENT_ADDED` entry on the idea's activity timeline. Comments/activity also render on the Idea detail slide-over in web.
|
|
112
|
+
- `lumo idea comments list <id> [--full]` — print an idea's comment thread (oldest first), capped to the output budget like `task comments list`; `--full` prints every comment. Empty thread → `(no comments)`.
|
|
113
|
+
- **When to suggest**: the user wants to discuss or annotate a parked idea ("给这个想法加条评论", "comment on the idea"), or review an idea's discussion/history ("看看这个想法的评论/活动"). For the activity timeline (status changes, statement edits, materialisation) there is no CLI reader — it's shown on the Idea detail slide-over in web.
|
|
114
|
+
- **Example**:
|
|
115
|
+
```bash
|
|
116
|
+
lumo idea comment LUM-I42 "Let's scope this for next sprint"
|
|
117
|
+
lumo idea comments list LUM-I42
|
|
118
|
+
```
|
|
77
119
|
|
|
78
120
|
**Plan runs (design-thinking 转换器)**
|
|
79
121
|
|
|
80
122
|
- `lumo plan [--abandon-active]` — start a converter run that turns the team's captured idea pool + current priority into an executable plan through a 3-gate recoverable state machine (聚类→对齐→成计划). Freezes the input snapshot at start and enforces **one active run per team**: if a run is already active it refuses and points to `lumo plan status`; `--abandon-active` abandons the prior run first, then starts fresh. On success it prints (and opens) the **gate-A** web deep-link (`/workspace/<slug>/plan/<runId>`). The CLI only triggers — **all editing and gate confirmation happen in web**.
|
|
81
123
|
- `lumo plan status` — print the active run's current stage (working segment or the open `*_READY` gate) and the deep-link to act on its next gate; with no active run, prompts to run `lumo plan`.
|
|
82
124
|
- **When to suggest**: the user wants to turn the parked idea pool into a plan / "跑一下转换器" / "start a planning run" / "把想法变成计划"; or asks where an in-flight plan run stands ("plan 到哪一步了", "plan status"). The gate work itself is web-only — the CLI hands off the deep-link.
|
|
125
|
+
- **Example** — check where the active run stands:
|
|
126
|
+
```bash
|
|
127
|
+
lumo plan status
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Initiatives (team-level, LUM-INIT-<n>)**
|
|
131
|
+
|
|
132
|
+
- `lumo initiative create "<goal>" [--assumption <text>]` — create a team-level Initiative **directly, no plan run required** (`planRunId=null`, status `ACTIVE`). Prints the `LUM-INIT-<n>` id; `--assumption` records the bet (赌注旁注) behind it. A blank goal is rejected (400). Converter-materialised initiatives (from `lumo plan`) live in the same list.
|
|
133
|
+
- **When to suggest**: the user wants to stand up a strategic initiative directly — "建一个 initiative" / "create an initiative" / "开一个举措" — without running the converter.
|
|
134
|
+
- **Example**:
|
|
135
|
+
```bash
|
|
136
|
+
lumo initiative create "Ship the initiatives browse surface" --assumption "teams curate initiatives directly"
|
|
137
|
+
```
|
|
138
|
+
- `lumo initiative list` — print the team's initiatives newest-first, one line per initiative: the `LUM-INIT-<n>` id, status (`ACTIVE` | `DONE` | `DROPPED`) and goal. Browsable in web at `/workspace/<slug>/initiatives` (sidebar → Initiatives), where each links to its lineage (consumed ideas + attached tasks).
|
|
139
|
+
- **When to suggest**: the user wants to see the team's initiatives / "看看 initiatives" / "list the initiatives".
|
|
140
|
+
- **Example**:
|
|
141
|
+
```bash
|
|
142
|
+
lumo initiative list
|
|
143
|
+
```
|
|
83
144
|
|
|
84
145
|
**Task dependencies** — see [task-deps.md](references/task-deps.md)
|
|
85
146
|
|
|
@@ -97,7 +158,7 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
97
158
|
|
|
98
159
|
**Verification (machine acceptance loop)** — see [verify.md](references/verify.md)
|
|
99
160
|
|
|
100
|
-
- `lumo verify [task] --note "<self-report>" [--timeout <seconds>]` — run every MACHINE criterion's checkpointer locally and report a structured PASS/FAIL verdict per criterion. **`--note` is required** when a round will be posted: a one-line self-report ("我改了 X,因为 Y 可验收") frozen as your task's claim (source AGENT) the moment the round passes into IN_REVIEW, then checked against the diff for faithfulness. No note → no round posted (none burned).
|
|
161
|
+
- `lumo verify [task] --note "<self-report>" [--timeout <seconds>]` — run every MACHINE criterion's checkpointer locally and report a structured PASS/FAIL verdict per criterion. **`--note` is required** when a round will be posted: a one-line self-report ("我改了 X,因为 Y 可验收") frozen as your task's claim (source AGENT) the moment the round passes into IN_REVIEW, then checked against the diff for faithfulness. No note → no round posted (none burned). The round cap is the workspace's `verificationMaxRounds` (default 3) — read the actual budget off the `Round N/M` line the command prints, never assume a number. All-pass → task to IN_REVIEW (agent stops); a fail on the last round → escalate to human. **Run this before claiming a task is done.**
|
|
101
162
|
- `lumo task status [task] [--json]` — read-only acceptance self-check (no LLM): the contract with each criterion's latest verdict, verification history/round, last failure reasons, `nextActions` (the unmet criteria), and any OPEN boundary crossings (fails closed — `null`/`⚠` means "could not confirm", not safe). **Run it first when resuming a task or after a round was rejected.**
|
|
102
163
|
- `lumo verdict [task] --pass | --fail` — acceptance verdicts. `--pass` deep-links to the human verdict bar (records nothing). `--fail --reason <enum> …` records an AGENT send-back → IN_PROGRESS. **An unresolved send-back blocks the DONE transition with 409.**
|
|
103
164
|
- `lumo crossing explain <id> --note "<text>"` — append an agent self-explanation ("申辩") to a boundary crossing; append-only, **never clears the crossing or unblocks Done** (disposition stays web + human-only). A review aid, not a self-clear.
|
|
@@ -207,6 +268,16 @@ lumo criteria audit
|
|
|
207
268
|
|
|
208
269
|
**When to suggest**: when the user wants to audit how much of the workspace's "green" criteria are self-confirming vs externally anchored; when diagnosing whether acceptance criteria have real external hooks or are circular.
|
|
209
270
|
|
|
271
|
+
## Next-step hints (the trailing `Next:` block)
|
|
272
|
+
|
|
273
|
+
`task create`, `task update`, `session attach`, `task context` and `next` end with a short `Next:` block naming what to do now — attach an unbound session, draft a missing contract, run `lumo verify`, read `task status` after a rejected round, explain an open boundary crossing, or stop and wait when the task is IN_REVIEW.
|
|
274
|
+
|
|
275
|
+
The rules are computed **server-side** from task status × contract × session binding × verify round, so the advice tracks the real state machine rather than a hard-coded string. Capped at **3 suggestions**; a rule whose condition is already satisfied never appears. The block goes to **stdout**, after the command's own result line — the result line stays first and unchanged, so reading line 1 still works.
|
|
276
|
+
|
|
277
|
+
Silence it with `--no-hints` on any command, or `LUMO_NO_HINTS=1` for a whole shell.
|
|
278
|
+
|
|
279
|
+
The block never replaces the acceptance-contract section: on `session attach` / `task context` the criteria draft reminder comes from `criteriaSection` and is deliberately **not** repeated as a step.
|
|
280
|
+
|
|
210
281
|
## Commands & flags that do NOT exist (common mistakes)
|
|
211
282
|
|
|
212
283
|
Don't guess these:
|
|
@@ -231,7 +302,7 @@ Don't guess these:
|
|
|
231
302
|
2. `lumo task context LUM-42` — load background; review unresolved items, PR-review todos, the description
|
|
232
303
|
3. **If the task has no acceptance criteria** (context shows the draft reminder, not a contract): draft outcome-level criteria sized to the task (3–7 for a typical multi-file task; 1–2 for a micro task) and submit them with `lumo task criteria set` **before writing the first line of code** — see [criteria.md](references/criteria.md)
|
|
233
304
|
4. Do the work
|
|
234
|
-
5. **Before claiming done: `lumo verify`** — the machine half of the acceptance loop. Fix failures and re-run (
|
|
305
|
+
5. **Before claiming done: `lumo verify`** — the machine half of the acceptance loop. Fix failures and re-run (the cap is workspace-configurable — see the `Round N/M` line in the output). All-pass → task moves to IN_REVIEW and you stop. See [verify.md](references/verify.md)
|
|
235
306
|
|
|
236
307
|
**Resuming / after a send-back** — `lumo task status` **before** re-reading code or planning. It tells you where the loop stands (round, what passed, what's unmet and why, any REVIEW_ADDED criteria from review) so you don't redo finished work or miss why it bounced. A send-back means **fix in place / amend the contract — do not spin off a new task.** See [verify.md](references/verify.md)
|
|
237
308
|
|
|
@@ -219,4 +219,4 @@ lumo task criteria list LUM-42
|
|
|
219
219
|
|
|
220
220
|
## After the contract: the verification loop
|
|
221
221
|
|
|
222
|
-
The contract is judged by `lumo verify` — run it before claiming the task is done. See [verify.md](verify.md) for the loop (round cap 3, IN_REVIEW on all-pass, escalation on a round
|
|
222
|
+
The contract is judged by `lumo verify` — run it before claiming the task is done. See [verify.md](verify.md) for the loop (a workspace-configurable round cap — `verificationMaxRounds`, default 3, shown as `Round N/M` in the output — IN_REVIEW on all-pass, escalation on a last-round fail).
|
|
@@ -17,11 +17,8 @@ Declare the current team priority.
|
|
|
17
17
|
```bash
|
|
18
18
|
lumo priority "Ship the idea-capture MVP; defer the converter to Spec 2."
|
|
19
19
|
```
|
|
20
|
-
- **With no argument** —
|
|
21
|
-
|
|
22
|
-
lumo priority
|
|
23
|
-
# Priority (1-3 sentences): _
|
|
24
|
-
```
|
|
20
|
+
- **With no argument** — running `lumo priority` on its own prompts
|
|
21
|
+
interactively for a 1-3 sentence statement (`Priority (1-3 sentences): _`).
|
|
25
22
|
An empty prompt records nothing and exits non-zero.
|
|
26
23
|
|
|
27
24
|
On success it prints `Recorded priority: <statement>`. A blank statement is
|
|
@@ -32,6 +29,10 @@ rejected (the API's Zod layer returns 400).
|
|
|
32
29
|
Print the append history, newest-first. The current declaration (the row with
|
|
33
30
|
the latest `effectiveFrom`) is marked with a leading `*`:
|
|
34
31
|
|
|
32
|
+
```bash
|
|
33
|
+
lumo priority list
|
|
34
|
+
```
|
|
35
|
+
|
|
35
36
|
```
|
|
36
37
|
* 2026-02-01 Now focus on the converter state machine
|
|
37
38
|
2026-01-01 Ship the idea-capture MVP
|
|
@@ -34,9 +34,15 @@ On success the command prints one or two lines to stdout:
|
|
|
34
34
|
```
|
|
35
35
|
Created LUM-55 "Title" https://www.uselumo.ai/workspace/<slug>/my-tasks/LUM-55
|
|
36
36
|
Tags: bug, urgent
|
|
37
|
+
|
|
38
|
+
Next:
|
|
39
|
+
lumo session attach LUM-55
|
|
40
|
+
Bind this session to the task so its work is recorded against it
|
|
41
|
+
lumo task criteria set LUM-55 --file <criteria.json>
|
|
42
|
+
Draft the acceptance contract before writing the first line of code
|
|
37
43
|
```
|
|
38
44
|
|
|
39
|
-
The `Tags:` line is omitted when no tags were attached.
|
|
45
|
+
The `Tags:` line is omitted when no tags were attached. The trailing `Next:` block is server-computed and conditional — the attach line is skipped when this session is already bound (a second attach is a 409), and the whole block is suppressed by `--no-hints` / `LUMO_NO_HINTS=1`. The `Created …` line stays first and unchanged. See SKILL.md "Next-step hints".
|
|
40
46
|
|
|
41
47
|
If the workspace has multiple projects and `--project` is omitted, the server will return an error naming available projects — re-run with `--project <slug>`.
|
|
42
48
|
|
|
@@ -93,9 +99,13 @@ On success the command prints one or two lines to stdout:
|
|
|
93
99
|
```
|
|
94
100
|
Updated LUM-48 "Title" https://www.uselumo.ai/workspace/<slug>/my-tasks/LUM-48
|
|
95
101
|
Tags: urgent
|
|
102
|
+
|
|
103
|
+
Next:
|
|
104
|
+
lumo verify --note "<self-report>"
|
|
105
|
+
Run the machine acceptance loop before claiming the task is done
|
|
96
106
|
```
|
|
97
107
|
|
|
98
|
-
The `Tags:` line is omitted when the resulting tag set is empty.
|
|
108
|
+
The `Tags:` line is omitted when the resulting tag set is empty. The trailing `Next:` block reflects the **post-update** status — `--status in_review` prints a stop-and-wait note instead of a command, and a DONE task prints nothing. Suppressed by `--no-hints` / `LUMO_NO_HINTS=1`.
|
|
99
109
|
|
|
100
110
|
### Status transitions — direct moves are legal
|
|
101
111
|
|
|
@@ -213,7 +223,9 @@ Top 3 recommended tasks (of 12 open):
|
|
|
213
223
|
3. LUM-12 TODO MEDIUM Add rate limiting
|
|
214
224
|
↳ MEDIUM · blocked by 1 open dep · due 2026-06-10
|
|
215
225
|
|
|
216
|
-
Next:
|
|
226
|
+
Next:
|
|
227
|
+
lumo session attach LUM-42 && lumo task context LUM-42
|
|
228
|
+
start on the top-ranked task
|
|
217
229
|
```
|
|
218
230
|
|
|
219
231
|
When to suggest: the user asks "what should I work on", "what's next", "recommend my next task" (in any language), "pick my next task", or starts a session without a task in mind. After they choose, run `session attach` + `task context` for the picked task.
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
`lumo verify` is the machine half of the acceptance system: it executes every **MACHINE** criterion's checkpointer in the local
|
|
4
4
|
repo, POSTs one structured PASS/FAIL verdict per criterion, and prints what to
|
|
5
5
|
do next. Execution is on the client; adjudication is server-side — round
|
|
6
|
-
numbering, the **
|
|
7
|
-
happen on the server.
|
|
6
|
+
numbering, the **round cap**, escalation, and the **IN_REVIEW** transition all
|
|
7
|
+
happen on the server. The cap is **not a universal constant** — it is the
|
|
8
|
+
workspace-level `verificationMaxRounds` (default 3); the effective value for
|
|
9
|
+
_this_ workspace is the `M` in the `Round N/M` line that `lumo verify` and
|
|
10
|
+
`lumo task status` print.
|
|
8
11
|
|
|
9
12
|
## The claim-done rule
|
|
10
13
|
|
|
@@ -39,11 +42,11 @@ still verifies — the claim degrades to the synthesized run-summary fallback.)
|
|
|
39
42
|
TaskActivity event.
|
|
40
43
|
4. Prints the round outcome:
|
|
41
44
|
|
|
42
|
-
| Round outcome
|
|
43
|
-
|
|
|
44
|
-
| **All PASS**
|
|
45
|
-
| **Any FAIL**
|
|
46
|
-
| **
|
|
45
|
+
| Round outcome | Effect | What to do |
|
|
46
|
+
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
47
|
+
| **All PASS** | Task transitions to **IN_REVIEW** (existing state machine + TASK_IN_REVIEW notification); the `--note` self-report is frozen as the task's claim (source `AGENT`) | **Stop here.** Human adjudication + any HUMAN criteria take over; **never set DONE yourself** |
|
|
48
|
+
| **Any FAIL** | Task status untouched; unmet criteria printed as next actions (statement, checkpointer, failure tail) | Fix and re-run |
|
|
49
|
+
| **Last round still failing** (round = the cap `M`) | Loop escalates: a human is notified (AGENT_VERIFY, requires action); further `lumo verify` rounds are rejected with **409** | **Stop retrying**; fix only what the human directs |
|
|
47
50
|
|
|
48
51
|
Exit code 0 = all passed (or nothing to run); 1 = failures, escalation, or
|
|
49
52
|
errors.
|
|
@@ -74,9 +77,10 @@ errors.
|
|
|
74
77
|
|
|
75
78
|
## Round discipline
|
|
76
79
|
|
|
77
|
-
Rounds are a hard budget
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
Rounds are a hard budget, not a retry loop — and the size of that budget is a
|
|
81
|
+
workspace setting (`verificationMaxRounds`, default 3), so check `Round N/M`
|
|
82
|
+
instead of assuming three. Between rounds, actually fix the failures —
|
|
83
|
+
re-running without changes burns a round and (on the last one) pages a human. **A FAIL round never changes task status; only an all-pass round moves it
|
|
80
84
|
(to IN_REVIEW, never further).**
|
|
81
85
|
|
|
82
86
|
## Review-time drift habits (gap findings)
|
|
@@ -120,7 +124,7 @@ what's unmet and why (the exact failure tails), and how many rounds are left.
|
|
|
120
124
|
|
|
121
125
|
### What it prints
|
|
122
126
|
|
|
123
|
-
- **Header** — task identifier/title/status + `verification round N/
|
|
127
|
+
- **Header** — task identifier/title/status + `verification round N/M` (`M` = the workspace's `verificationMaxRounds`; round 0 = never verified) + an escalation warning when the machine loop is exhausted.
|
|
124
128
|
- **Claim vs verification** — 规律 2 声称vs核验: the headline contrast, printed right after the header (whenever the contract exists), so the report shows **both** columns instead of only the verification one. Two sides:
|
|
125
129
|
- **Claim** — what the agent _says_ it did: an **unverified self-report** (`agent self-report · estimated, not verification`), estimate-tier provenance (估, not 测). Sourced by layering (LUM-597, preference `AGENT > RUN_SUMMARY > DIGEST > null`): if you supplied a `lumo verify --note`, that **own self-report** is the claim (`↳ source: agent self-report (verify --note)`) — the汇报者's voice, what faithfulness judges. Absent a self-report (old CLI / non-verify path) it degrades to the summarizer paraphrase, labelled honestly (`↳ source: synthesized run summary (no self-report)`). **Fail-closed**: when only a raw STOP turn digest exists it prints `generating — the formal run summary is still being synthesized` and **withholds the raw digest**; with no material at all it prints `not generated yet — …`, never a fabricated claim.
|
|
126
130
|
- **Verification** — what was actually _confirmed_ (measured): the machine-verification rollup `N machine-verified / M human override (of T MACHINE criteria)` over the active MACHINE criteria (relocated here from its old standalone line under `Criteria`), plus `X of Y criteria met by their latest verdict`. **Fail-closed**: before any round runs it prints `no verification has run yet — the claim is unconfirmed` rather than implying a pass.
|
|
@@ -179,8 +183,9 @@ fields don't. Pin on `version` when scripting against it.
|
|
|
179
183
|
- **`openCrossings` is `null` when the crossings check failed** — distinct from `[]` (a successful read with zero open crossings). Script consumers must treat `null` as "unknown / could not confirm", **not** "safe".
|
|
180
184
|
|
|
181
185
|
`status` reads; `verify` judges. Running status never starts a round, never
|
|
182
|
-
escalates, and never changes task state — loop rules (
|
|
183
|
-
all-pass, human-only DONE) live entirely in `lumo verify` and the
|
|
186
|
+
escalates, and never changes task state — loop rules (the workspace round cap,
|
|
187
|
+
IN_REVIEW on all-pass, human-only DONE) live entirely in `lumo verify` and the
|
|
188
|
+
server.
|
|
184
189
|
|
|
185
190
|
## lumo verdict — the three verdict channels
|
|
186
191
|
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
# Worktrees
|
|
2
2
|
|
|
3
|
-
Local dev tooling that scaffolds parallel git worktrees.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Local dev tooling that scaffolds parallel git worktrees. These are almost
|
|
4
|
+
entirely local — they shell out to git and the local filesystem; the **only**
|
|
5
|
+
server call is a single `GET /api/teams` to expand a **bare number** to your
|
|
6
|
+
default team prefix (see "Accepted identifier forms" below), and even that is
|
|
7
|
+
skipped when you pass an explicit `<TEAM>-N`. **Run `add` and `rm` from the main
|
|
8
|
+
checkout**, not from inside a worktree (the node_modules symlink source and
|
|
9
|
+
`.worktrees/` both live there); `list` is read-only and runnable from anywhere.
|
|
10
|
+
|
|
11
|
+
### Accepted identifier forms
|
|
12
|
+
|
|
13
|
+
The `<id>` argument to `add`/`rm` is a task id with **any team prefix**, not
|
|
14
|
+
just `LUM-` (the `<LUM-N>` in the examples below is just a placeholder):
|
|
15
|
+
|
|
16
|
+
- `LUM-267`, `SPEC-12`, `spec-12` — an explicit `<TEAM>-<n>` (1–10 letters,
|
|
17
|
+
case-insensitive; the prefix is upper-cased). Resolved locally, no network.
|
|
18
|
+
- `267` — a **bare number**: expanded to the workspace's **default team**
|
|
19
|
+
prefix via `GET /api/teams` (or the sole team in a single-team workspace).
|
|
20
|
+
In a multi-team workspace with no default, this errors and asks for an
|
|
21
|
+
explicit prefix; the same happens when you're offline / not logged in.
|
|
8
22
|
|
|
9
23
|
## `lumo worktree add <LUM-N> [slug]`
|
|
10
24
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaComment = ideaComment;
|
|
4
|
+
exports.ideaCommentList = ideaCommentList;
|
|
5
|
+
const config_1 = require("../lib/config");
|
|
6
|
+
const api_1 = require("../lib/api");
|
|
7
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
8
|
+
const task_comment_list_1 = require("./task-comment-list");
|
|
9
|
+
/**
|
|
10
|
+
* `lumo idea comment <id> <body>` — post a comment on a team-level idea.
|
|
11
|
+
*
|
|
12
|
+
* `<id>` is the `<TEAM>-I<n>` identifier (a bare `<n>` also resolves) or a cuid.
|
|
13
|
+
* Unlike task comments, the idea comments endpoint is keyed by the idea ref
|
|
14
|
+
* directly, so there's no separate resolve step. Body is plain text — quote it
|
|
15
|
+
* to pass spaces/newlines.
|
|
16
|
+
*/
|
|
17
|
+
async function ideaComment(idRef, body) {
|
|
18
|
+
if (!idRef || idRef.trim().length === 0) {
|
|
19
|
+
console.error('Error: usage: lumo idea comment <LUM-I42> "<body>"');
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
if (!body || body.trim().length === 0) {
|
|
23
|
+
console.error('Error: comment body cannot be empty.');
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
const creds = (0, config_1.readCredentials)();
|
|
27
|
+
if (!creds) {
|
|
28
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
32
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
33
|
+
const url = `${base}/api/ideas/${encodeURIComponent(idRef.trim())}/comments`;
|
|
34
|
+
let res;
|
|
35
|
+
try {
|
|
36
|
+
res = await fetch(url, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: {
|
|
39
|
+
Authorization: `Bearer ${creds.token}`,
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({ body: body.trim() }),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
47
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
if (res.status === 401) {
|
|
51
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
if (res.status === 201) {
|
|
55
|
+
process.stdout.write(`✓ commented on ${(0, sanitize_1.sanitizeField)(idRef.trim())}\n`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let serverMsg = null;
|
|
59
|
+
try {
|
|
60
|
+
const errBody = (await res.json());
|
|
61
|
+
if (typeof errBody.error === 'string')
|
|
62
|
+
serverMsg = errBody.error;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Body wasn't JSON; fall through to status-only message
|
|
66
|
+
}
|
|
67
|
+
if (serverMsg) {
|
|
68
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.error(`Error: idea comment failed (HTTP ${res.status})`);
|
|
72
|
+
}
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* `lumo idea comments list <id>` — print an idea's comment thread. Reuses the
|
|
77
|
+
* shared `formatCommentThread` renderer (same output-budget cap + `--full`
|
|
78
|
+
* escape hatch as `lumo task comments list`). Idea threads are flat, so each
|
|
79
|
+
* block is a single top-level comment.
|
|
80
|
+
*/
|
|
81
|
+
async function ideaCommentList(idRef, opts = {}) {
|
|
82
|
+
if (!idRef || idRef.trim().length === 0) {
|
|
83
|
+
console.error('Error: usage: lumo idea comments list <LUM-I42>');
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
const creds = (0, config_1.readCredentials)();
|
|
87
|
+
if (!creds) {
|
|
88
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
92
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
93
|
+
const url = `${base}/api/ideas/${encodeURIComponent(idRef.trim())}/comments`;
|
|
94
|
+
let res;
|
|
95
|
+
try {
|
|
96
|
+
res = await fetch(url, {
|
|
97
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
102
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
if (res.status === 401) {
|
|
106
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
if (res.status === 404) {
|
|
110
|
+
console.error(`Error: idea ${(0, sanitize_1.sanitizeField)(idRef.trim())} not found`);
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
console.error(`Error: idea comments list failed (HTTP ${res.status})`);
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
const { comments } = (await res.json());
|
|
118
|
+
if (!comments || comments.length === 0) {
|
|
119
|
+
console.log('(no comments)');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
console.log((0, task_comment_list_1.formatCommentThread)(comments, {
|
|
123
|
+
full: opts.full,
|
|
124
|
+
identifier: idRef.trim(),
|
|
125
|
+
fetchCommand: 'idea comments list',
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaFigmaAdd = ideaFigmaAdd;
|
|
4
|
+
const idea_figma_api_1 = require("../lib/idea-figma-api");
|
|
5
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
6
|
+
async function ideaFigmaAdd(args) {
|
|
7
|
+
try {
|
|
8
|
+
const { link } = await (0, idea_figma_api_1.addIdeaFigmaLink)(args.identifier, args.url);
|
|
9
|
+
const label = (0, sanitize_1.sanitizeField)(link.frameName
|
|
10
|
+
? `${link.fileName} · ${link.frameName}`
|
|
11
|
+
: `${link.fileName}${link.nodeId === '' ? ' (file-level)' : ''}`);
|
|
12
|
+
console.log(`Linked ${args.identifier} ↔ Figma "${label}"`);
|
|
13
|
+
console.log(` ${link.url}`);
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
const err = e;
|
|
18
|
+
if (err.code === 'figma_not_connected') {
|
|
19
|
+
console.error(`✗ You haven't connected Figma yet.`);
|
|
20
|
+
console.error(` Run: open https://www.uselumo.ai/settings/integrations`);
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
if (err.code === 'figma_needs_reauth') {
|
|
24
|
+
console.error(`✗ Your Figma token is no longer valid. Please reconnect.`);
|
|
25
|
+
console.error(` Run: open https://www.uselumo.ai/settings/integrations`);
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
throw e; // let wrap() in cli/src/index.ts log and exit 1
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaFigmaContext = ideaFigmaContext;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
7
|
+
const report_pull_1 = require("../lib/report-pull");
|
|
8
|
+
/**
|
|
9
|
+
* `lumo idea figma context <LUM-I1> <link-id>`
|
|
10
|
+
*
|
|
11
|
+
* Tier-2 retrieval for the cheap inline Figma card on an idea. Reads the
|
|
12
|
+
* cached design metadata (file/frame name, url, thumbnail, last sync) from
|
|
13
|
+
* `/api/ideas/:id/figma-links/:linkId/context`. Mirrors `taskFigmaContext`
|
|
14
|
+
* (LUM-681).
|
|
15
|
+
*/
|
|
16
|
+
async function ideaFigmaContext(identifier, linkId) {
|
|
17
|
+
if (!identifier || !linkId) {
|
|
18
|
+
console.error('Error: usage: lumo idea figma context <LUM-I1> <link-id>');
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
const creds = (0, config_1.readCredentials)();
|
|
22
|
+
if (!creds) {
|
|
23
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
27
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
28
|
+
let res;
|
|
29
|
+
try {
|
|
30
|
+
res = await fetch(`${base}/api/ideas/${encodeURIComponent(identifier)}/figma-links/${encodeURIComponent(linkId)}/context`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
34
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
if (res.status === 401) {
|
|
38
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
if (res.status === 404) {
|
|
42
|
+
console.error(`Error: idea ${identifier} or figma link ${linkId} not found in workspace ${creds.workspaceSlug}`);
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
console.error(`Error: figma context failed (HTTP ${res.status})`);
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
const { metadata, note } = (await res.json());
|
|
50
|
+
if (metadata.fileName)
|
|
51
|
+
console.log(`file: ${(0, sanitize_1.sanitizeField)(metadata.fileName)}`);
|
|
52
|
+
if (metadata.frameName)
|
|
53
|
+
console.log(`frame: ${(0, sanitize_1.sanitizeField)(metadata.frameName)}`);
|
|
54
|
+
console.log(`url: ${metadata.url}`);
|
|
55
|
+
if (metadata.lastSyncedAt)
|
|
56
|
+
console.log(`synced: ${metadata.lastSyncedAt}`);
|
|
57
|
+
if (metadata.lastSyncError)
|
|
58
|
+
console.log(`syncError: ${(0, sanitize_1.sanitizeField)(metadata.lastSyncError)}`);
|
|
59
|
+
if (note)
|
|
60
|
+
console.log(`\nnote: ${(0, sanitize_1.sanitizeField)(note)}`);
|
|
61
|
+
// LUM-681: mirrors LUM-500 disclosure-funnel stamping for idea fragments.
|
|
62
|
+
// The linkId arg == lineage FIGMA fragmentId. Fire-and-forget — never
|
|
63
|
+
// blocks output, swallows failures.
|
|
64
|
+
await (0, report_pull_1.reportPull)({ fragmentType: 'FIGMA', fragmentId: linkId });
|
|
65
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaFigmaList = ideaFigmaList;
|
|
4
|
+
const idea_figma_api_1 = require("../lib/idea-figma-api");
|
|
5
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
6
|
+
const STALE_MS = 25 * 24 * 3600 * 1000;
|
|
7
|
+
function formatRow(id, fileName, frame, synced, stale) {
|
|
8
|
+
return [
|
|
9
|
+
id.padEnd(10),
|
|
10
|
+
(0, sanitize_1.sanitizeField)(fileName).padEnd(20),
|
|
11
|
+
(0, sanitize_1.sanitizeField)(frame).padEnd(24),
|
|
12
|
+
synced.padEnd(12),
|
|
13
|
+
stale ? '⚠ thumbnail stale' : '',
|
|
14
|
+
]
|
|
15
|
+
.join(' ')
|
|
16
|
+
.trimEnd();
|
|
17
|
+
}
|
|
18
|
+
async function ideaFigmaList(args) {
|
|
19
|
+
const { links } = await (0, idea_figma_api_1.listIdeaFigmaLinks)(args.identifier);
|
|
20
|
+
if (links.length === 0) {
|
|
21
|
+
console.log('No Figma links attached.');
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
for (const link of links) {
|
|
25
|
+
const frame = link.frameName ?? '(file-level)';
|
|
26
|
+
const synced = link.lastSyncedAt.slice(0, 10);
|
|
27
|
+
const stale = link.thumbnailFetchedAt === null ||
|
|
28
|
+
Date.now() - new Date(link.thumbnailFetchedAt).getTime() > STALE_MS;
|
|
29
|
+
console.log(formatRow(link.id, link.fileName, frame, synced, stale && !!link.thumbnailUrl));
|
|
30
|
+
}
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaFigmaRefresh = ideaFigmaRefresh;
|
|
4
|
+
const idea_figma_api_1 = require("../lib/idea-figma-api");
|
|
5
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
6
|
+
async function ideaFigmaRefresh(args) {
|
|
7
|
+
try {
|
|
8
|
+
const { links } = await (0, idea_figma_api_1.listIdeaFigmaLinks)(args.identifier);
|
|
9
|
+
const byId = new Map(links.map(l => [l.id, l]));
|
|
10
|
+
const { results } = await (0, idea_figma_api_1.refreshIdeaFigmaLinks)(args.identifier);
|
|
11
|
+
if (results.length === 0) {
|
|
12
|
+
console.log(`No Figma links attached to ${args.identifier}.`);
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
console.log(`Refreshed ${results.length} Figma links on ${args.identifier}`);
|
|
16
|
+
for (const r of results) {
|
|
17
|
+
const link = byId.get(r.id);
|
|
18
|
+
const label = link
|
|
19
|
+
? (0, sanitize_1.sanitizeField)(link.frameName
|
|
20
|
+
? `${link.fileName} · ${link.frameName}`
|
|
21
|
+
: `${link.fileName} (file-level)`)
|
|
22
|
+
: r.id;
|
|
23
|
+
if (r.ok)
|
|
24
|
+
console.log(` ✓ ${label}`);
|
|
25
|
+
else
|
|
26
|
+
console.log(` ✗ ${label} (${(0, sanitize_1.sanitizeField)(r.error ?? '')})`);
|
|
27
|
+
}
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
const err = e;
|
|
32
|
+
if (err.code === 'figma_not_connected' ||
|
|
33
|
+
err.code === 'figma_needs_reauth') {
|
|
34
|
+
console.error(`✗ Figma is not connected (or token expired). Reconnect:`);
|
|
35
|
+
console.error(` open https://www.uselumo.ai/settings/integrations`);
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ideaFigmaRm = ideaFigmaRm;
|
|
4
|
+
const idea_figma_api_1 = require("../lib/idea-figma-api");
|
|
5
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
6
|
+
async function ideaFigmaRm(args) {
|
|
7
|
+
const { links } = await (0, idea_figma_api_1.listIdeaFigmaLinks)(args.identifier);
|
|
8
|
+
const match = links.find(l => l.id === args.linkIdOrUrl || l.url === args.linkIdOrUrl);
|
|
9
|
+
if (!match) {
|
|
10
|
+
console.log(`Not linked: ${args.linkIdOrUrl}`);
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
await (0, idea_figma_api_1.removeIdeaFigmaLink)(args.identifier, match.id);
|
|
14
|
+
const label = (0, sanitize_1.sanitizeField)(match.frameName
|
|
15
|
+
? `${match.fileName} · ${match.frameName}`
|
|
16
|
+
: `${match.fileName} (file-level)`);
|
|
17
|
+
console.log(`Removed Figma link from ${args.identifier}: "${label}"`);
|
|
18
|
+
return 0;
|
|
19
|
+
}
|