@lamemind/loom-deck 0.30.0 → 0.31.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/dist/cli.js +9 -1
- package/dist/session-list.js +28 -0
- package/dist/task-edit.js +33 -26
- package/dist/tasks.js +52 -20
- package/dist/view.js +3 -8
- package/package.json +1 -1
- package/scripts/deck-run +5 -13
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { discoverProjectSessions } from './sessions.js';
|
|
|
13
13
|
import { discoverLiveSessions, liveSig } from './live-sessions.js';
|
|
14
14
|
import { buildRows, firstRowKey, moveRowSelection, rowIndexOfKey, searchSessions, selectedRow, DEFAULT_OPTIONS, MIN_QUERY, } from './search.js';
|
|
15
15
|
import { appendNote, appendPin, appendSessionRecord, appendTaskBinding, loadSessionIndex, } from './task-index.js';
|
|
16
|
-
import { assembleSessionList, firstSelectableId, moveSelection, neighborId, rowIndexOf, rowLabel, selectedSession, sessionTitle, stripProjectCore, } from './session-list.js';
|
|
16
|
+
import { assembleSessionList, firstSelectableId, moveSelection, neighborId, rowIndexOf, rowLabel, selectedSession, sessionTitle, stripProjectCore, unpinLandingId, } from './session-list.js';
|
|
17
17
|
import { cellWidth, launchLegend, loadArchivableDays, loadIdentity, loadLaunch, } from './config.js';
|
|
18
18
|
import { countArchivable, SCAN_INTERVAL_MS } from './archivable.js';
|
|
19
19
|
import { assignListCapacity, detailCapacity, isCompact, layoutBudget, readerCapacity, searchListCapacity, searchPreviewCapacity, windowRange, } from './viewport.js';
|
|
@@ -1824,8 +1824,16 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
1824
1824
|
}
|
|
1825
1825
|
else {
|
|
1826
1826
|
const isPinned = pinned.has(selSessionId);
|
|
1827
|
+
// Dove atterra la selezione: sul pin resta sulla sessione (sale in cima
|
|
1828
|
+
// con lei, il caret la segue perché è l'oggetto dell'azione); sull'unpin
|
|
1829
|
+
// resta IN PLACE nel gruppo pinnate — successiva, precedente, o prima
|
|
1830
|
+
// contestuale se il gruppo si svuota. Calcolato PRIMA della riscrittura
|
|
1831
|
+
// del sidecar, quando la riga pinnata esiste ancora.
|
|
1832
|
+
const landing = isPinned ? unpinLandingId(sessionRows, selSessionId) : null;
|
|
1827
1833
|
appendPin(cwd, selSessionId, !isPinned);
|
|
1828
1834
|
reloadSessions();
|
|
1835
|
+
if (landing)
|
|
1836
|
+
setSelSessionId(landing);
|
|
1829
1837
|
setNote(`${isPinned ? 'unpin' : '📌 pin'} ${selSessionId.slice(0, 8)}`);
|
|
1830
1838
|
}
|
|
1831
1839
|
}
|
package/dist/session-list.js
CHANGED
|
@@ -197,6 +197,34 @@ export function neighborId(rows, sessionId) {
|
|
|
197
197
|
return null;
|
|
198
198
|
return selectable[at + 1]?.sessionId ?? selectable[at - 1]?.sessionId ?? null;
|
|
199
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Id su cui atterrare quando la riga pinnata `sessionId` viene SPINNATA: la
|
|
202
|
+
* pinnata successiva, la precedente se era l'ultima, la prima contestuale se il
|
|
203
|
+
* gruppo pinnate si svuota. `null` se `sessionId` non è pinnata o non resta
|
|
204
|
+
* nessuna riga.
|
|
205
|
+
*
|
|
206
|
+
* Non è `neighborId` ristretto al gruppo: quello attraversa il separatore,
|
|
207
|
+
* quindi sull'ULTIMA pinnata atterrerebbe sulla prima contestuale — il caret
|
|
208
|
+
* scavalcherebbe l'intero gruppo invece di risalire di una riga. Qui il confine
|
|
209
|
+
* fra i due gruppi è la regola, non un ostacolo.
|
|
210
|
+
*
|
|
211
|
+
* L'id va calcolato PRIMA di riscrivere il sidecar: dopo, la riga pinnata non
|
|
212
|
+
* c'è più e il vicino nel gruppo non è calcolabile. Serve perché una spinnata
|
|
213
|
+
* non sparisce necessariamente dalla lista — se appartiene al parent
|
|
214
|
+
* selezionato ricompare fra le contestuali, e la selezione keyed sull'id la
|
|
215
|
+
* seguirebbe fin laggiù, trascinando il caret.
|
|
216
|
+
*/
|
|
217
|
+
export function unpinLandingId(rows, sessionId) {
|
|
218
|
+
const pinnedRows = rows.filter((r) => r.kind === 'pinned');
|
|
219
|
+
const at = pinnedRows.findIndex((r) => r.sessionId === sessionId);
|
|
220
|
+
if (at < 0)
|
|
221
|
+
return null;
|
|
222
|
+
const inGroup = pinnedRows[at + 1]?.sessionId ?? pinnedRows[at - 1]?.sessionId;
|
|
223
|
+
if (inGroup)
|
|
224
|
+
return inGroup;
|
|
225
|
+
const firstContext = rows.find((r) => r.kind === 'context');
|
|
226
|
+
return firstContext && isSelectable(firstContext) ? firstContext.sessionId : null;
|
|
227
|
+
}
|
|
200
228
|
/** Session della riga selezionata; null se stale o nessuna selezione. */
|
|
201
229
|
export function selectedSession(rows, sessionId) {
|
|
202
230
|
const r = rows.find((row) => isSelectable(row) && row.sessionId === sessionId);
|
package/dist/task-edit.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// isolata in `writeTaskEdit`, così la grammatica delle sostituzioni è testabile
|
|
4
4
|
// senza toccare il filesystem.
|
|
5
5
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
-
import { TASK_ID_RE, findTaskFile } from './tasks.js';
|
|
6
|
+
import { TASK_ID_RE, findTaskFile, headerColumns } from './tasks.js';
|
|
7
7
|
// Il task file scrive la priorità per NOME (`- **Priority**: Med`), tasks.md la
|
|
8
8
|
// scrive come GLIFO (colonna Pri). Due rappresentazioni dello stesso valore →
|
|
9
9
|
// due mappe, entrambe keyed sul nome canonico di view.ts (unica fonte del rango).
|
|
@@ -75,31 +75,38 @@ export function initialDetail(current, prog, date = today()) {
|
|
|
75
75
|
return stripProgGlyph(s);
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* Riscrive le celle Pri
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
78
|
+
* Riscrive le celle Pri, Prog e — se `desc` è passata — la descrizione della
|
|
79
|
+
* riga `| Tnn | … |` in tasks.md. Solo la PRIMA riga con quell'id — l'overview
|
|
80
|
+
* è unica, un secondo match sarebbe un duplicato da non propagare. Le celle non
|
|
81
|
+
* toccate restano i token grezzi originali: nessun re-flow della tabella, il
|
|
82
|
+
* diff resta di una riga. `ok:false` = id assente → il chiamante non scrive nulla.
|
|
83
|
+
*
|
|
84
|
+
* Le posizioni vengono dall'HEADER (`headerColumns`, stessa fonte di
|
|
85
|
+
* `parseTasks`), mai da un indice cablato: qui si SCRIVE su disco, quindi una
|
|
86
|
+
* colonna in più o in meno non produrrebbe una lettura sbagliata ma un titolo
|
|
87
|
+
* sovrascritto col glifo. Header assente → `ok:false`, non si tira a indovinare.
|
|
84
88
|
*
|
|
85
89
|
* `desc` undefined ≠ `desc` vuota: la prima non tocca la cella (chiamata
|
|
86
90
|
* legacy, solo pri/prog), la seconda la svuota davvero.
|
|
87
91
|
*
|
|
88
92
|
* La descrizione è l'ULTIMA colonna e può contenere `|` — che in una tabella
|
|
89
93
|
* markdown spezza la cella. Riscriverla significa quindi collassare tutte le
|
|
90
|
-
* celle da
|
|
91
|
-
* della vecchia descrizione resterebbero appesi in coda alla nuova.
|
|
94
|
+
* celle da `cols.desc` fino alla penultima in una sola (`splice`), altrimenti i
|
|
95
|
+
* pezzi della vecchia descrizione resterebbero appesi in coda alla nuova.
|
|
92
96
|
*
|
|
93
97
|
* L'id deve rispettare `TASK_ID_RE` (importato da tasks.ts — stesso gate di
|
|
94
98
|
* `parseTasks`, non una copia): senza, un id arbitrario matcherebbe la riga di
|
|
95
|
-
*
|
|
96
|
-
* celle e sfondando la tabella.
|
|
99
|
+
* separatore, riscrivendone le celle e sfondando la tabella.
|
|
97
100
|
*/
|
|
98
101
|
export function updateTasksMdRow(content, id, priGlyph, progGlyph, desc) {
|
|
99
102
|
if (!TASK_ID_RE.test(id))
|
|
100
103
|
return { content, ok: false };
|
|
104
|
+
const lines = content.split('\n');
|
|
105
|
+
const cols = lines.reduce((found, l) => found ?? headerColumns(l), null);
|
|
106
|
+
if (!cols)
|
|
107
|
+
return { content, ok: false };
|
|
101
108
|
let ok = false;
|
|
102
|
-
const
|
|
109
|
+
const out = lines.map((line) => {
|
|
103
110
|
if (ok)
|
|
104
111
|
return line;
|
|
105
112
|
if (!line.trim().startsWith('|'))
|
|
@@ -107,20 +114,21 @@ export function updateTasksMdRow(content, id, priGlyph, progGlyph, desc) {
|
|
|
107
114
|
const cells = line.split('|');
|
|
108
115
|
if ((cells[1] ?? '').trim() !== id)
|
|
109
116
|
return line;
|
|
110
|
-
|
|
117
|
+
// Serve la cella desc PIÙ la coda dopo il `|` finale: senza la coda lo
|
|
118
|
+
// splice inserirebbe invece di sostituire.
|
|
119
|
+
if (cells.length < cols.desc + 2)
|
|
111
120
|
return line; // riga malformata → non toccarla
|
|
112
121
|
ok = true;
|
|
113
|
-
cells[
|
|
114
|
-
cells[
|
|
122
|
+
cells[cols.pri] = ` ${priGlyph} `;
|
|
123
|
+
cells[cols.prog] = ` ${progGlyph} `;
|
|
115
124
|
if (desc !== undefined) {
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
cells.splice(5, cells.length - 6, ` ${sanitizeCell(desc)} `);
|
|
125
|
+
// L'ultima cella è la coda dopo il `|` finale (stringa vuota su una riga
|
|
126
|
+
// ben formata) e va conservata, o la riga perderebbe il bordo destro.
|
|
127
|
+
cells.splice(cols.desc, cells.length - cols.desc - 1, ` ${sanitizeCell(desc)} `);
|
|
120
128
|
}
|
|
121
129
|
return cells.join('|');
|
|
122
130
|
});
|
|
123
|
-
return { content: ok ?
|
|
131
|
+
return { content: ok ? out.join('\n') : content, ok };
|
|
124
132
|
}
|
|
125
133
|
/**
|
|
126
134
|
* Rende un testo scrivibile dentro una CELLA di tabella markdown. Due caratteri
|
|
@@ -139,11 +147,10 @@ function sanitizeCell(s) {
|
|
|
139
147
|
* ricompare nel body (residuo template) vince quello dell'header — così ciò che
|
|
140
148
|
* il deck mostra e ciò che scrive restano la stessa riga.
|
|
141
149
|
*
|
|
142
|
-
* L'H1 conserva il proprio CAPPELLO (`# Task: …`
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* una T resta `# Task:` e una D resta `# Doc Task:` senza doverlo sapere qui.
|
|
150
|
+
* L'H1 conserva il proprio CAPPELLO (`# Task: …`): `parseTaskDetail` lo strippa
|
|
151
|
+
* in lettura, quindi il titolo che arriva dal modale non ce l'ha e riscrivere la
|
|
152
|
+
* riga nuda lo perderebbe. Si rilegge dalla riga esistente e si ri-antepone tale
|
|
153
|
+
* e quale, così un file senza cappello resta senza.
|
|
147
154
|
*/
|
|
148
155
|
export function updateTaskFileFields(content, priLabel, progress, title) {
|
|
149
156
|
let priDone = false;
|
|
@@ -152,7 +159,7 @@ export function updateTaskFileFields(content, priLabel, progress, title) {
|
|
|
152
159
|
const lines = content.split('\n').map((line) => {
|
|
153
160
|
if (title !== undefined && !titleDone && /^#\s+/.test(line)) {
|
|
154
161
|
titleDone = true;
|
|
155
|
-
const prefix = line.match(/^#\s+(
|
|
162
|
+
const prefix = line.match(/^#\s+(Task:\s*)/)?.[1] ?? '';
|
|
156
163
|
return `# ${prefix}${title.replace(/\s+/g, ' ').trim()}`;
|
|
157
164
|
}
|
|
158
165
|
if (!priDone && /^-\s*\*\*Priority\*\*:/.test(line)) {
|
package/dist/tasks.js
CHANGED
|
@@ -2,12 +2,12 @@ import { readFileSync, readdirSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { sanitize } from './width.js';
|
|
4
4
|
/**
|
|
5
|
-
* Forma di un ID task: `T`
|
|
6
|
-
*
|
|
7
|
-
* per
|
|
8
|
-
*
|
|
5
|
+
* Forma di un ID task: `T` + numero. FONTE UNICA del gate — `parseTasks` lo usa
|
|
6
|
+
* per scartare header/separatore della tabella, `task-edit` per non riscrivere
|
|
7
|
+
* per sbaglio quelle stesse righe. Tenerlo in un posto solo evita che i due gate
|
|
8
|
+
* divergano (uno accetterebbe righe che l'altro rifiuta).
|
|
9
9
|
*/
|
|
10
|
-
export const TASK_ID_RE = /^
|
|
10
|
+
export const TASK_ID_RE = /^T\d+$/;
|
|
11
11
|
// D1 (preflight T20): default docs/tasks.md, override della docs-root via env
|
|
12
12
|
// LOOM_DECK_DOCS_ROOT (es. questo progetto usa `runtime`). No auto-detect.
|
|
13
13
|
export function resolveTasksPath(cwd = process.cwd()) {
|
|
@@ -19,15 +19,50 @@ export function resolveTasksPath(cwd = process.cwd()) {
|
|
|
19
19
|
export function resolveTasksDir(cwd = process.cwd()) {
|
|
20
20
|
return join(dirname(resolveTasksPath(cwd)), 'tasks');
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Indici di colonna della Tasks Overview letti dall'HEADER, non cablati.
|
|
24
|
+
*
|
|
25
|
+
* Cablarli lega il parser al NUMERO di colonne invece che al loro nome: toglierne
|
|
26
|
+
* una fa scivolare tutto di uno e il deck legge la cella sbagliata **senza
|
|
27
|
+
* sollevare un errore** — la descrizione finisce dove si cerca il glifo di
|
|
28
|
+
* progresso, e `updateTasksMdRow` riscrive quella posizione su disco. È la stessa
|
|
29
|
+
* trappola già nota sull'indice di RIGA (selezione keyed su id, mai su posizione),
|
|
30
|
+
* applicata all'indice di colonna.
|
|
31
|
+
*
|
|
32
|
+
* Solo `pri` e `prog` si cercano per nome; la descrizione è per costruzione la
|
|
33
|
+
* colonna successiva a Prog, quindi rinominarla non rompe il parse. `null` = la
|
|
34
|
+
* riga non è l'header — l'header della tabella Lane (`| Lane | …`) cade qui.
|
|
35
|
+
*/
|
|
36
|
+
export function headerColumns(line) {
|
|
37
|
+
const t = line.trim();
|
|
38
|
+
if (!t.startsWith('|'))
|
|
39
|
+
return null;
|
|
40
|
+
const cells = t.split('|').map((c) => c.trim().toLowerCase());
|
|
41
|
+
if (cells[1] !== 'id')
|
|
42
|
+
return null;
|
|
43
|
+
const pri = cells.indexOf('pri');
|
|
44
|
+
const prog = cells.indexOf('prog');
|
|
45
|
+
if (pri < 0 || prog < 0)
|
|
46
|
+
return null;
|
|
47
|
+
return { pri, prog, desc: prog + 1 };
|
|
48
|
+
}
|
|
49
|
+
// Estrae le righe `| Tnn | Pri | Prog | Task |` della Tasks Overview. Righe
|
|
50
|
+
// header/separatore non matchano `^T\d+$` → scartate.
|
|
51
|
+
//
|
|
52
|
+
// Le righe si parsano solo DOPO aver incontrato l'header: senza indici derivati
|
|
53
|
+
// non si tira a indovinare, la lista resta vuota. Un fallimento visibile invece
|
|
54
|
+
// di una cella letta al posto sbagliato.
|
|
28
55
|
export function parseTasks(content) {
|
|
29
56
|
const tasks = [];
|
|
57
|
+
let cols = null;
|
|
30
58
|
for (const line of content.split('\n')) {
|
|
59
|
+
const header = headerColumns(line);
|
|
60
|
+
if (header) {
|
|
61
|
+
cols = header;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (!cols)
|
|
65
|
+
continue;
|
|
31
66
|
const t = line.trim();
|
|
32
67
|
if (!t.startsWith('|'))
|
|
33
68
|
continue;
|
|
@@ -36,12 +71,10 @@ export function parseTasks(content) {
|
|
|
36
71
|
const id = cells[1];
|
|
37
72
|
if (!TASK_ID_RE.test(id))
|
|
38
73
|
continue;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const pri = cells[2] ?? '';
|
|
42
|
-
const prog = cells[4] ?? '';
|
|
74
|
+
const pri = cells[cols.pri] ?? '';
|
|
75
|
+
const prog = cells[cols.prog] ?? '';
|
|
43
76
|
// desc = colonna finale; join per resistere a eventuali `|` nella descrizione.
|
|
44
|
-
const desc = cells.slice(
|
|
77
|
+
const desc = cells.slice(cols.desc, -1).join('|').trim();
|
|
45
78
|
// Sanificazione AL CONFINE: tutto ciò che entra da tasks.md è testo
|
|
46
79
|
// arbitrario e può contenere glifi che Ink e il terminale misurano in modo
|
|
47
80
|
// diverso (vedi `width.ts`). Farlo qui invece che a ogni sito di render è
|
|
@@ -87,10 +120,9 @@ export function parseTaskDetail(id, content) {
|
|
|
87
120
|
let inDesc = false;
|
|
88
121
|
for (const line of content.split('\n')) {
|
|
89
122
|
if (!title && line.startsWith('# ')) {
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
title = line.replace(/^#\s+/, '').replace(/^(?:Doc\s+)?Task:\s*/, '').trim();
|
|
123
|
+
// Il cappello `Task:` del template va via: il titolo mostrato è la
|
|
124
|
+
// descrizione, non la categoria (già data dall'id).
|
|
125
|
+
title = line.replace(/^#\s+/, '').replace(/^Task:\s*/, '').trim();
|
|
94
126
|
continue;
|
|
95
127
|
}
|
|
96
128
|
if (line.startsWith('## ')) {
|
package/dist/view.js
CHANGED
|
@@ -53,16 +53,11 @@ export function progRank(glyph) {
|
|
|
53
53
|
return PROG_TABLE.find((e) => e.glyph === g)?.rank ?? UNKNOWN_RANK;
|
|
54
54
|
}
|
|
55
55
|
// `T10`.localeCompare(`T9`) mette T10 prima: l'ID va confrontato NUMERICO.
|
|
56
|
-
//
|
|
57
|
-
// il solo numero non basta, T01 e D01 collidono. Il prefisso pesa quindi come
|
|
58
|
-
// cifra più significativa (T prima di D, blocco doc in coda), il numero come
|
|
59
|
-
// meno significativa: il rango resta un intero singolo, quindi il comparator
|
|
60
|
-
// della chain non cambia forma. Il gap 1e6 è oltre qualunque counter reale.
|
|
56
|
+
// Prefisso unico → il numero è già una chiave totale, e il rango è il numero.
|
|
61
57
|
// Un id fuori forma non deve far esplodere il comparator → coda con MAX_SAFE.
|
|
62
|
-
const ID_PREFIX_RANK = { T: 0, D: 1 };
|
|
63
58
|
export function idNum(id) {
|
|
64
|
-
const m = /^(
|
|
65
|
-
return m ?
|
|
59
|
+
const m = /^T(\d+)$/.exec(id);
|
|
60
|
+
return m ? Number(m[1]) : Number.MAX_SAFE_INTEGER;
|
|
66
61
|
}
|
|
67
62
|
function rankOf(task, key) {
|
|
68
63
|
if (key === 'pri')
|
package/package.json
CHANGED
package/scripts/deck-run
CHANGED
|
@@ -124,7 +124,7 @@ USAGE="uso: deck-run <TaskID> [--prompt-kind <kind>] [--session-id <uuid>]
|
|
|
124
124
|
none nessun prompt — sessione aperta sulla task, a mani nude
|
|
125
125
|
recap recap stato task <TaskID>
|
|
126
126
|
preflight /loom-works:preflight-task <TaskID>
|
|
127
|
-
run /loom-works:run-task <TaskID>
|
|
127
|
+
run /loom-works:run-task <TaskID>
|
|
128
128
|
checkpoint /loom-works:checkpoint-task <TaskID>
|
|
129
129
|
|
|
130
130
|
--title-note nota della conversazione, appesa al titolo tab come «nota»
|
|
@@ -353,23 +353,15 @@ FORK_FLAG=""
|
|
|
353
353
|
# possa scavalcare. Il placeholder {TASK} vale solo per il valore dell'override;
|
|
354
354
|
# i template del catalogo interpolano ${TASK} diretto (nel `${VAR:-...}` la `}`
|
|
355
355
|
# di `{TASK}` chiuderebbe in anticipo l'espansione).
|
|
356
|
-
#
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
# Su una D il prompt `run-task` invocherebbe la skill sbagliata. Gli altri kind
|
|
360
|
-
# non discriminano, ma è una verifica da rifare per OGNI kind nuovo, non una
|
|
361
|
-
# proprietà del catalogo: `recap` è un prompt diretto (nessuna skill),
|
|
362
|
-
# `preflight-task` risolve il task file per id, e `checkpoint-task` estrae il
|
|
363
|
-
# taskId con `T\d+|D\d+` — copre quindi entrambi i prefissi da sé.
|
|
356
|
+
# Ogni kind è un template diretto sull'id: la lista del deck è monofamiglia
|
|
357
|
+
# (`T`, unico prefisso del contratto loom), quindi nessun prompt discrimina sulla
|
|
358
|
+
# forma dell'id.
|
|
364
359
|
case "${PROMPT_KIND:-recap}" in
|
|
365
360
|
none) PROMPT="" ;;
|
|
366
361
|
recap) PROMPT="recap stato task ${TASK}" ;;
|
|
367
362
|
preflight) PROMPT="/loom-works:preflight-task ${TASK}" ;;
|
|
368
363
|
checkpoint) PROMPT="/loom-works:checkpoint-task ${TASK}" ;;
|
|
369
|
-
run)
|
|
370
|
-
D*) PROMPT="/loom-works:run-doc ${TASK}" ;;
|
|
371
|
-
*) PROMPT="/loom-works:run-task ${TASK}" ;;
|
|
372
|
-
esac ;;
|
|
364
|
+
run) PROMPT="/loom-works:run-task ${TASK}" ;;
|
|
373
365
|
esac
|
|
374
366
|
if [[ "${PROMPT_KIND:-}" != "none" && -n "${LOOM_DECK_ENTER_PROMPT:-}" ]]; then
|
|
375
367
|
PROMPT="${LOOM_DECK_ENTER_PROMPT//\{TASK\}/${TASK}}"
|