@lamemind/loom-deck 0.62.0 → 0.63.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/actions.js +27 -18
- package/dist/cli.js +10 -4
- package/dist/deck-model.js +21 -1
- package/dist/frame.js +63 -15
- package/dist/glyphs.js +11 -0
- package/dist/input.js +10 -3
- package/dist/overlays/inbox.js +1 -1
- package/dist/spawn.js +19 -8
- package/dist/ui/fields.js +24 -8
- package/package.json +1 -1
- package/scripts/deck-run +11 -6
- package/scripts/prompt-catalog +12 -11
package/dist/actions.js
CHANGED
|
@@ -156,24 +156,30 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, setNo
|
|
|
156
156
|
spawned.child.on('error', () => setNote(`⚠ resume fallito (${DECK_RUN})`));
|
|
157
157
|
noteSpawn(spawned);
|
|
158
158
|
}
|
|
159
|
-
/** T148 — `m
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
159
|
+
/** T148/T154(P2,P3) — `m` col bersaglio RESUME: `input.ts` instrada qui solo
|
|
160
|
+
* quando il focus è sulle sessioni E una riga è selezionata (P2), quindi il
|
|
161
|
+
* solo caso residuo è il pin STALE — l'id c'è ma il transcript no, quindi
|
|
162
|
+
* non c'è un bottone da cambiare (P3): resta sul selettore di resume con la
|
|
163
|
+
* propria nota, invece di spostare di soppiatto il selettore della nuda
|
|
164
|
+
* mentre il caret è su una riga sessione.
|
|
165
|
+
*
|
|
166
|
+
* La nota sul SUCCESSO nomina bersaglio e valore (`m → resume: opus`, P2):
|
|
167
|
+
* rovescia la scelta di T148 di non scriverne nessuna, perché lì `m` aveva
|
|
168
|
+
* un bersaglio solo e il bottone che cambiava era l'unico a schermo — da
|
|
169
|
+
* T154 i due selettori possono stare a schermo insieme. */
|
|
165
170
|
function cycleResumeModel() {
|
|
166
|
-
if (model.focus !== 'sessions') {
|
|
167
|
-
setNote('m → modello: seleziona una sessione (→ per il pane)');
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
171
|
if (!model.selSessionObj) {
|
|
171
|
-
setNote(
|
|
172
|
-
? 'm → pin stale: nessun modello da cambiare'
|
|
173
|
-
: 'm → nessuna sessione selezionata');
|
|
172
|
+
setNote('m → pin stale: nessun modello da cambiare');
|
|
174
173
|
return;
|
|
175
174
|
}
|
|
176
|
-
model.cycleResumeModel();
|
|
175
|
+
const next = model.cycleResumeModel();
|
|
176
|
+
setNote(`m → resume: ${next}`);
|
|
177
|
+
}
|
|
178
|
+
/** T154 — `m` col bersaglio NUDA (`c`): il selettore che governa sta sempre
|
|
179
|
+
* a schermo sulla riga launch, quindi il tasto non è mai inerte qui. */
|
|
180
|
+
function cycleBareModel() {
|
|
181
|
+
const next = model.cycleBareModel();
|
|
182
|
+
setNote(`m → nuda: ${next}`);
|
|
177
183
|
}
|
|
178
184
|
// T53 — scrive il sidecar e ricarica subito, senza attendere il tick del poll
|
|
179
185
|
// (stesso feedback immediato del pin).
|
|
@@ -289,8 +295,8 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, setNo
|
|
|
289
295
|
* il giorno in cui una natura nuova apre la skill sbagliata.
|
|
290
296
|
*
|
|
291
297
|
* Sessione NUDA e modello esplicito: il drain lavora sulla doc, non sulla
|
|
292
|
-
* task (D10 preflight), e `
|
|
293
|
-
* come `permissionMode`.
|
|
298
|
+
* task (D10 preflight), e `MODEL_DEFAULT` passa nell'argv anche essendo il
|
|
299
|
+
* default, come `permissionMode`.
|
|
294
300
|
*/
|
|
295
301
|
function drainInbox(file, prompt) {
|
|
296
302
|
const spawned = spawnBare(cwd, prompt, MODEL_DEFAULT);
|
|
@@ -311,9 +317,11 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, setNo
|
|
|
311
317
|
spawned.child.on('error', () => setNote(`⚠ srotolamento ${path} fallito (${DECK_RUN})`));
|
|
312
318
|
noteSpawn(spawned);
|
|
313
319
|
}
|
|
314
|
-
/** `c` — sessione claude a mani nude, senza task e senza prompt.
|
|
320
|
+
/** `c` — sessione claude a mani nude, senza task e senza prompt. T154 — il
|
|
321
|
+
* modello è quello del selettore proprio della nuda (`model.bareModel`),
|
|
322
|
+
* passato SEMPRE nell'argv anche sul default. */
|
|
315
323
|
function openClaude() {
|
|
316
|
-
const spawned = spawnClaudeEmpty(cwd);
|
|
324
|
+
const spawned = spawnClaudeEmpty(cwd, model.bareModel);
|
|
317
325
|
spawned.child.on('error', () => setNote(`⚠ c → spawn claude fallito (${DECK_RUN})`));
|
|
318
326
|
noteSpawn(spawned);
|
|
319
327
|
}
|
|
@@ -352,6 +360,7 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, setNo
|
|
|
352
360
|
spawnTaskSession,
|
|
353
361
|
resumeSession,
|
|
354
362
|
cycleResumeModel,
|
|
363
|
+
cycleBareModel,
|
|
355
364
|
writeNote,
|
|
356
365
|
assignSession,
|
|
357
366
|
forkSession,
|
package/dist/cli.js
CHANGED
|
@@ -19,9 +19,10 @@ import { resolveTasksPath, resolveTasksDir } from './tasks.js';
|
|
|
19
19
|
import { LAUNCH_SEP } from './config.js';
|
|
20
20
|
import { anchorFrame, enableMouse } from './mouse.js';
|
|
21
21
|
import { sanitize } from './width.js';
|
|
22
|
-
import { modelAlias } from './glyphs.js';
|
|
23
|
-
import { MODEL_DEFAULT } from './spawn.js';
|
|
24
|
-
import { deckLegend, frameGeometry, headlineWidth, indicatorRow, launchRow } from './frame.js';
|
|
22
|
+
import { MODEL_SHORT_LIST, modelAlias } from './glyphs.js';
|
|
23
|
+
import { MODEL_DEFAULT, MODELS } from './spawn.js';
|
|
24
|
+
import { BARE_BUTTONS_WIDTH, BARE_LABEL, deckLegend, frameGeometry, headlineWidth, indicatorRow, launchRow, } from './frame.js';
|
|
25
|
+
import { choiceButtons } from './ui/fields.js';
|
|
25
26
|
import { useDeckModel } from './deck-model.js';
|
|
26
27
|
import { useDeckActions } from './actions.js';
|
|
27
28
|
import { useDeckInput } from './input.js';
|
|
@@ -162,6 +163,11 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
162
163
|
: 'none';
|
|
163
164
|
const detailParts = model.detail ? detailMetaOf(model.detail) : null;
|
|
164
165
|
const launch = launchRow(model.launch, columns);
|
|
166
|
+
// T154 — i bottoni del blocco selettore della nuda: stessa primitiva
|
|
167
|
+
// (`choiceButtons`) del selettore di resume/fork, sulle SHORT (D1) e con
|
|
168
|
+
// `avail` pari alla larghezza naturale del gruppo, così `cutParts` non
|
|
169
|
+
// taglia mai (il blocco è riservato, non conteso — vedi `launchRow`).
|
|
170
|
+
const bareButtons = choiceButtons(MODEL_SHORT_LIST, MODELS.indexOf(model.bareModel), BARE_BUTTONS_WIDTH, MODELS.indexOf(MODEL_DEFAULT));
|
|
165
171
|
// T134 — gli indicatori ancorati a destra della riga legenda. Si compongono
|
|
166
172
|
// PRIMA della legenda perché è la loro larghezza a decidere quanto ne resta:
|
|
167
173
|
// hanno la precedenza sul budget (D5 preflight).
|
|
@@ -245,7 +251,7 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
245
251
|
purgeBulk: model.purgeBulk,
|
|
246
252
|
inboxPane: model.rightPane === 'inbox',
|
|
247
253
|
hasInbox: model.selInbox !== null,
|
|
248
|
-
}), indicators: indicators, columns: columns }), frame.launchLine ? (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [launch.segments.map((s) => s.text).join(LAUNCH_SEP), launch.overflow > 0 ? (_jsxs(Text, { color: "yellow", children: [" \u00B7 +", launch.overflow, " fuori riga"] })) : null, launch.unreachable > 0 ? (_jsxs(Text, { color: "yellow", children: [" \u00B7 ", launch.unreachable, " oltre la 9\u00AA (non raggiungibili)"] })) : null] })) : null, mode === 'create' ? _jsx(TextBox, { glyph: "C", value: textModals.draft }) : null, mode === 'note' ? _jsx(TextBox, { glyph: "\u270E", value: textModals.noteDraft }) : null, mode === 'sort' ? _jsx(SortModal, { sort: model.view.sort }) : null, mode === 'filter' ? (_jsx(FilterModal, { view: model.view, cursor: viewModals.filterCursor })) : null, mode === 'edit' && textModals.edit && textModals.editTask ? (_jsx(EditModal, { id: textModals.editTask.id, draft: textModals.edit, row: textModals.editCursor.row, caret: textModals.editCursor.caret, columns: columns })) : null, mode === 'purge' && purge.draft ? (_jsx(PurgeModal, { draft: purge.draft, columns: columns })) : null, _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(TasksPane, { tasks: frame.windowTasks, counts: model.taskCounts, activeView: model.taskViewId, paneCount: model.paneTasks.length, view: model.view, selected: model.selIndex, spotCount: model.spotCount, allCount: model.sessions.length, idW: model.taskCols.id, tailW: model.taskCols.tail, focused: model.focus === 'tasks', loadError: model.loadError, windowStart: frame.taskWin.start, above: frame.taskWin.start, below: model.paneTasks.length - frame.taskWin.end, columns: columns, data: model.taskRowData, blockMark: model.blockMark }), model.rightPane === 'inbox' ? (_jsx(InboxPane, { files: frame.windowInbox, counts: model.inboxCounts, activeView: model.inboxViewId, paneCount: model.inboxFiles.length, selectedPath: model.selInboxPath, focused: model.focus === 'inbox', above: frame.inboxWin.start, below: model.inboxFiles.length - frame.inboxWin.end, columns: columns, ok: model.inboxOk, scanned: model.inboxScanned })) : (_jsx(SessionsPane, { parentLabel: model.parentLabel, isSpot: model.isSpot, isAll: model.isAll, bindings: model.bindings, taskW: model.sessionCols.task, ageW: model.sessionCols.age, rows: frame.windowRows, counts: model.sessionCounts, activeView: model.sessionViewId, paneCount: model.sessionRows.length, selectedId: model.selSessionId ?? undefined, focused: model.focus === 'sessions', above: frame.sessionWin.start, below: model.sessionRows.length - frame.sessionWin.end, columns: columns, forkOf: model.forkOf, sessionNotes: model.sessionNotes, projectCore: model.projectCore, live: model.live }))] }), frame.budget.preview && previewKind === 'task' && model.detail ? (_jsx(PreviewPane, { kind: "task", detail: model.detail, maxLines: frame.budget.detailLines, columns: columns })) : frame.budget.preview && previewKind === 'inbox' && model.selInbox ? (_jsx(PreviewPane, { kind: "inbox", file: model.selInbox, columns: columns })) : frame.budget.preview && previewKind === 'session' && model.selSessionObj ? (_jsx(PreviewPane, { kind: "session", s: model.selSessionObj, firstLines: frame.budget.sessionFirstLines, lastLines: frame.budget.sessionLastLines, columns: columns, origin: model.forkOf.get(model.selSessionObj.sessionId) ?? null, note: model.sessionNotes.get(model.selSessionObj.sessionId) ?? '', live: model.live.get(model.selSessionObj.sessionId) ?? null, resumeModel: model.resumeModel })) : null, note ? _jsx(Text, { color: "green", wrap: "truncate-end", children: sanitize(note) }) : null] }));
|
|
254
|
+
}), indicators: indicators, columns: columns }), frame.launchLine ? (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [launch.segments.map((s) => s.text).join(LAUNCH_SEP), launch.overflow > 0 ? (_jsxs(Text, { color: "yellow", children: [" \u00B7 +", launch.overflow, " fuori riga"] })) : null, launch.unreachable > 0 ? (_jsxs(Text, { color: "yellow", children: [" \u00B7 ", launch.unreachable, " oltre la 9\u00AA (non raggiungibili)"] })) : null, launch.filler, BARE_LABEL.text, ' ', bareButtons.nodes] })) : null, mode === 'create' ? _jsx(TextBox, { glyph: "C", value: textModals.draft }) : null, mode === 'note' ? _jsx(TextBox, { glyph: "\u270E", value: textModals.noteDraft }) : null, mode === 'sort' ? _jsx(SortModal, { sort: model.view.sort }) : null, mode === 'filter' ? (_jsx(FilterModal, { view: model.view, cursor: viewModals.filterCursor })) : null, mode === 'edit' && textModals.edit && textModals.editTask ? (_jsx(EditModal, { id: textModals.editTask.id, draft: textModals.edit, row: textModals.editCursor.row, caret: textModals.editCursor.caret, columns: columns })) : null, mode === 'purge' && purge.draft ? (_jsx(PurgeModal, { draft: purge.draft, columns: columns })) : null, _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(TasksPane, { tasks: frame.windowTasks, counts: model.taskCounts, activeView: model.taskViewId, paneCount: model.paneTasks.length, view: model.view, selected: model.selIndex, spotCount: model.spotCount, allCount: model.sessions.length, idW: model.taskCols.id, tailW: model.taskCols.tail, focused: model.focus === 'tasks', loadError: model.loadError, windowStart: frame.taskWin.start, above: frame.taskWin.start, below: model.paneTasks.length - frame.taskWin.end, columns: columns, data: model.taskRowData, blockMark: model.blockMark }), model.rightPane === 'inbox' ? (_jsx(InboxPane, { files: frame.windowInbox, counts: model.inboxCounts, activeView: model.inboxViewId, paneCount: model.inboxFiles.length, selectedPath: model.selInboxPath, focused: model.focus === 'inbox', above: frame.inboxWin.start, below: model.inboxFiles.length - frame.inboxWin.end, columns: columns, ok: model.inboxOk, scanned: model.inboxScanned })) : (_jsx(SessionsPane, { parentLabel: model.parentLabel, isSpot: model.isSpot, isAll: model.isAll, bindings: model.bindings, taskW: model.sessionCols.task, ageW: model.sessionCols.age, rows: frame.windowRows, counts: model.sessionCounts, activeView: model.sessionViewId, paneCount: model.sessionRows.length, selectedId: model.selSessionId ?? undefined, focused: model.focus === 'sessions', above: frame.sessionWin.start, below: model.sessionRows.length - frame.sessionWin.end, columns: columns, forkOf: model.forkOf, sessionNotes: model.sessionNotes, projectCore: model.projectCore, live: model.live }))] }), frame.budget.preview && previewKind === 'task' && model.detail ? (_jsx(PreviewPane, { kind: "task", detail: model.detail, maxLines: frame.budget.detailLines, columns: columns })) : frame.budget.preview && previewKind === 'inbox' && model.selInbox ? (_jsx(PreviewPane, { kind: "inbox", file: model.selInbox, columns: columns })) : frame.budget.preview && previewKind === 'session' && model.selSessionObj ? (_jsx(PreviewPane, { kind: "session", s: model.selSessionObj, firstLines: frame.budget.sessionFirstLines, lastLines: frame.budget.sessionLastLines, columns: columns, origin: model.forkOf.get(model.selSessionObj.sessionId) ?? null, note: model.sessionNotes.get(model.selSessionObj.sessionId) ?? '', live: model.live.get(model.selSessionObj.sessionId) ?? null, resumeModel: model.resumeModel })) : null, note ? _jsx(Text, { color: "green", wrap: "truncate-end", children: sanitize(note) }) : null] }));
|
|
249
255
|
}
|
|
250
256
|
const cwd = process.cwd();
|
|
251
257
|
// T116 — `exitOnCtrlC: false` toglie a Ink l'uscita immediata su `^C`: senza,
|
package/dist/deck-model.js
CHANGED
|
@@ -131,6 +131,12 @@ export function useDeckModel({ cwd, tasksPath, tasksDir, setNote, }) {
|
|
|
131
131
|
// tornare su una riga già visitata NON ricorda la scelta fatta lì — è lo
|
|
132
132
|
// stesso costo che T108 ha già accettato per il selettore del detail.
|
|
133
133
|
const [resumeModel, setResumeModel] = useState(MODEL_DEFAULT);
|
|
134
|
+
// T154 — il modello con cui la sessione NUDA (`c`) apre. Stato PROPRIO,
|
|
135
|
+
// indipendente da `resumeModel`: non segue nessuna riga selezionata (`c` non
|
|
136
|
+
// ha una riga, è un'azione sulla surface del cappello) e quindi non si
|
|
137
|
+
// azzera mai — resta quello scelto finché non si sceglie altro, per tutta la
|
|
138
|
+
// vita del deck.
|
|
139
|
+
const [bareModel, setBareModel] = useState(MODEL_DEFAULT);
|
|
134
140
|
// T39 — vista corrente (filtri + sort). Vive nel modello e non nell'hook dei
|
|
135
141
|
// modali che la editano: è ciò che `applyView` consuma per produrre la lista,
|
|
136
142
|
// è persistita su disco e la rilegge il tasto `w`. È stato del modello che due
|
|
@@ -407,10 +413,22 @@ export function useDeckModel({ cwd, tasksPath, tasksDir, setNote, }) {
|
|
|
407
413
|
* valore SUCCESSIVO del catalogo (ciclico), gemella di `cycleView` ma su un
|
|
408
414
|
* asse diverso: qui non c'è una vista da cambiare, solo un valore da far
|
|
409
415
|
* avanzare. Il guard (nessuna sessione, focus altrove) sta nell'attuatore
|
|
410
|
-
* `actions.ts`, come per `f`/`p`: qui c'è solo la meccanica dello stato.
|
|
416
|
+
* `actions.ts`, come per `f`/`p`: qui c'è solo la meccanica dello stato.
|
|
417
|
+
* T154/P2 — ritorna il valore NUOVO: il chiamante lo scrive nella nota di
|
|
418
|
+
* conferma prima che il render con lo stato aggiornato arrivi. */
|
|
411
419
|
function cycleResumeModel() {
|
|
412
420
|
const next = MODELS[(MODELS.indexOf(resumeModel) + 1) % MODELS.length];
|
|
413
421
|
setResumeModel(next);
|
|
422
|
+
return next;
|
|
423
|
+
}
|
|
424
|
+
/** T154 — gemella di `cycleResumeModel`, sullo stato della sessione NUDA:
|
|
425
|
+
* `m` ci finisce quando il focus non è su una riga sessione vera (P2, D4/D5
|
|
426
|
+
* degli AC). Nessun guard qui: il bersaglio non è mai inerte — il selettore
|
|
427
|
+
* che governa è sempre a schermo sulla riga launch. */
|
|
428
|
+
function cycleBareModel() {
|
|
429
|
+
const next = MODELS[(MODELS.indexOf(bareModel) + 1) % MODELS.length];
|
|
430
|
+
setBareModel(next);
|
|
431
|
+
return next;
|
|
414
432
|
}
|
|
415
433
|
/**
|
|
416
434
|
* T134 — `^B` scambia i due pane dello slot destro (D8 preflight).
|
|
@@ -493,6 +511,8 @@ export function useDeckModel({ cwd, tasksPath, tasksDir, setNote, }) {
|
|
|
493
511
|
sessionCols,
|
|
494
512
|
resumeModel,
|
|
495
513
|
cycleResumeModel,
|
|
514
|
+
bareModel,
|
|
515
|
+
cycleBareModel,
|
|
496
516
|
// derivazioni del pane inbox
|
|
497
517
|
rightPane,
|
|
498
518
|
inboxScanned: inbox.scanned,
|
package/dist/frame.js
CHANGED
|
@@ -20,17 +20,15 @@ import { FRAME_TEXT_COL, HINT_ROW, inlineRegions, LAUNCH_ROW, PANE_TEXT_PAD, pan
|
|
|
20
20
|
import { cellWidth, launchLegend, LAUNCH_SEP } from './config.js';
|
|
21
21
|
import { layoutBudget, windowRange } from './viewport.js';
|
|
22
22
|
import { sanitize, termWidth } from './width.js';
|
|
23
|
-
import { WARN } from './glyphs.js';
|
|
23
|
+
import { MODEL_SHORT_LIST, WARN } from './glyphs.js';
|
|
24
24
|
import { STATUS_MISSING } from './project-status.js';
|
|
25
25
|
import { META_ROWS } from './model.js';
|
|
26
26
|
import { rowIndexOf } from './session-list.js';
|
|
27
27
|
import { VERSION } from './version.js';
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// compass: 🤖 = nuova sessione claude. Per il terminale compass usa 🖥️, che nel
|
|
33
|
-
// frame Ink NON passa — `sanitize` lo sostituisce (VTE lo disegna largo 1,
|
|
28
|
+
// La surface built-in del cappello, in testa alla riga launch. Non sta fra i
|
|
29
|
+
// tasti perché ha la stessa natura delle voci `launch` — fire-once, cwd =
|
|
30
|
+
// project root, nessuno stato. Per il terminale compass usa 🖥️, che nel frame
|
|
31
|
+
// Ink NON passa — `sanitize` lo sostituisce (VTE lo disegna largo 1,
|
|
34
32
|
// string-width dice 2: discordante, invariante ① di width.ts) e resterebbe un
|
|
35
33
|
// `·` muto. 💻 è il gemello concorde; il `sanitize` qui rende il vincolo
|
|
36
34
|
// automatico invece che da ricordare.
|
|
@@ -38,10 +36,35 @@ import { VERSION } from './version.js';
|
|
|
38
36
|
// T21 — `key` è il tasto che la superficie rappresenta: un click su di essa
|
|
39
37
|
// entra nell'handler di tastiera con quel tasto, invece di chiamare l'azione
|
|
40
38
|
// per conto proprio. È ciò che tiene click e tasto per costruzione allineati.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
//
|
|
40
|
+
// T154/P9 — `c` non vive più qui: la surface trasloca a destra come etichetta
|
|
41
|
+
// del blocco selettore della nuda (`BARE_LABEL` sotto), adiacente ai bottoni
|
|
42
|
+
// col modello con cui apre invece che sull'estremo opposto della riga.
|
|
43
|
+
export const SURFACE_SEGMENTS = [{ key: 't', text: sanitize('t 💻') }];
|
|
44
|
+
// T154/P9 — l'etichetta del blocco selettore della nuda: unico segmento
|
|
45
|
+
// cliccabile del blocco (nessun tasto nomina un modello, `m` cicla e non
|
|
46
|
+
// seleziona — i bottoni non lo sono).
|
|
47
|
+
export const BARE_LABEL = { key: 'c', text: sanitize('c 🤖') };
|
|
48
|
+
// T154/D1 — i bottoni usano le SHORT (`fab`/`ops`/`son`/`hak`), non i nomi
|
|
49
|
+
// interi del selettore di resume/fork: la riga launch compete per spazio con
|
|
50
|
+
// la legenda delle voci custom, e le short sono già il vocabolario compatto
|
|
51
|
+
// che la colonna modello della lista sessioni usa per lo stesso motivo.
|
|
52
|
+
const BARE_BUTTONS_TEXT = MODEL_SHORT_LIST.map((s) => `[ ${s} ]`).join(' ');
|
|
53
|
+
/** Larghezza dei soli bottoni, in celle: passata come `avail` a
|
|
54
|
+
* `choiceButtons` — esattamente la larghezza naturale, così `cutParts` non
|
|
55
|
+
* taglia mai (il blocco è RISERVATO, D1/P8: a cedere sono le voci `launch`,
|
|
56
|
+
* mai il selettore). */
|
|
57
|
+
export const BARE_BUTTONS_WIDTH = termWidth(BARE_BUTTONS_TEXT);
|
|
58
|
+
/** Spazio fra l'etichetta `c 🤖` e il primo bottone. */
|
|
59
|
+
const BARE_LABEL_GAP = ' ';
|
|
60
|
+
/** Larghezza FISSA del blocco intero (etichetta + spazio + bottoni): non
|
|
61
|
+
* dipende da quale modello sia selezionato né dal default — ogni short è
|
|
62
|
+
* larga uguale (3 lettere) — quindi la geometria non ha bisogno di saperli;
|
|
63
|
+
* la STILATURA sì, e la fa il chiamante con `choiceButtons` sugli stessi
|
|
64
|
+
* valori (`MODEL_SHORT_LIST`). 39 celle con l'attuale `MODELS` a 4 voci
|
|
65
|
+
* (D1 preflight).
|
|
66
|
+
*/
|
|
67
|
+
const BARE_BLOCK_WIDTH = termWidth(BARE_LABEL.text) + termWidth(BARE_LABEL_GAP) + BARE_BUTTONS_WIDTH;
|
|
45
68
|
/**
|
|
46
69
|
* La legenda della modalità normale. Elenca SOLO i tasti che fanno qualcosa qui
|
|
47
70
|
* e ora: le voci contestuali compaiono quando il pane a fuoco le rende possibili
|
|
@@ -102,21 +125,46 @@ export function deckLegend(state) {
|
|
|
102
125
|
* seconde ri-splittando il primo sarebbe un conto parallelo, che diverge alla
|
|
103
126
|
* prima label che contenga il separatore.
|
|
104
127
|
*
|
|
105
|
-
* Le celle
|
|
106
|
-
* sulla riga → vanno riservate, o le voci launch la sfonderebbero di quel
|
|
128
|
+
* Le celle della surface (più il ` · ` che la separa dalle voci) sono già spese
|
|
129
|
+
* sulla riga → vanno riservate, o le voci launch la sfonderebbero di quel
|
|
130
|
+
* tanto. T154/P8 — il blocco selettore della nuda entra nella STESSA riserva:
|
|
131
|
+
* su un progetto con molte voci `launch` a cedere per prima è una voce custom,
|
|
132
|
+
* mai il selettore (un bottone di modello troncato sarebbe indistinguibile da
|
|
133
|
+
* un modello che non esiste, una voce launch resta raggiungibile col suo
|
|
134
|
+
* tasto-cifra anche quando non è scritta in riga).
|
|
107
135
|
*/
|
|
108
136
|
export function launchRow(launch, columns) {
|
|
109
137
|
const surfaceLegend = SURFACE_SEGMENTS.map((s) => s.text).join(LAUNCH_SEP);
|
|
110
|
-
const
|
|
138
|
+
const reserved = cellWidth(surfaceLegend) + cellWidth(LAUNCH_SEP) + BARE_BLOCK_WIDTH;
|
|
139
|
+
const legend = launchLegend(launch, columns, reserved);
|
|
111
140
|
const segments = [
|
|
112
141
|
...SURFACE_SEGMENTS,
|
|
113
142
|
...legend.taken.map((text, i) => ({ key: String(i + 1), text })),
|
|
114
143
|
];
|
|
144
|
+
// Larghezza REALE della sinistra, testo di overflow/irraggiungibili incluso:
|
|
145
|
+
// sono loro a decidere quanto riempimento serve, non solo i segmenti.
|
|
146
|
+
const leftWidth = termWidth(segments.map((s) => s.text).join(LAUNCH_SEP)) +
|
|
147
|
+
(legend.overflow > 0 ? termWidth(` · +${legend.overflow} fuori riga`) : 0) +
|
|
148
|
+
(legend.unreachable > 0
|
|
149
|
+
? termWidth(` · ${legend.unreachable} oltre la 9ª (non raggiungibili)`)
|
|
150
|
+
: 0);
|
|
151
|
+
// T154/P5 — la formula dell'ancoraggio: colonne del bordo esterno, meno la
|
|
152
|
+
// sinistra già disegnata, meno il blocco che deve starci per intero. Un
|
|
153
|
+
// pavimento di 1 tiene sempre almeno uno spazio fra le due metà, invece di
|
|
154
|
+
// farle toccare quando lo schermo è troppo stretto per il resto.
|
|
155
|
+
const filler = ' '.repeat(Math.max(1, columns - 6 - leftWidth - BARE_BLOCK_WIDTH));
|
|
156
|
+
const bareCol = FRAME_TEXT_COL + leftWidth + termWidth(filler);
|
|
157
|
+
const bareRegion = {
|
|
158
|
+
key: BARE_LABEL.key,
|
|
159
|
+
start: bareCol,
|
|
160
|
+
end: bareCol + termWidth(BARE_LABEL.text) - 1,
|
|
161
|
+
};
|
|
115
162
|
return {
|
|
116
163
|
segments,
|
|
117
|
-
regions: rowRegions(segments, LAUNCH_SEP, FRAME_TEXT_COL),
|
|
164
|
+
regions: [...rowRegions(segments, LAUNCH_SEP, FRAME_TEXT_COL), bareRegion],
|
|
118
165
|
overflow: legend.overflow,
|
|
119
166
|
unreachable: legend.unreachable,
|
|
167
|
+
filler,
|
|
120
168
|
};
|
|
121
169
|
}
|
|
122
170
|
/**
|
package/dist/glyphs.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// Glifi letterali del frame e formatter di display. Fase VISTA, ma senza JSX:
|
|
2
2
|
// tutto qui è puro e testabile, e nessun sito di render scrive un glifo nudo.
|
|
3
3
|
import { sanitize } from './width.js';
|
|
4
|
+
// T148/P3 — costo accettato: la fase vista importa dalla fase effetti esterni.
|
|
5
|
+
// Il TIPO si cancella alla compilazione (nessuna dipendenza runtime); T154
|
|
6
|
+
// aggiunge MODELS come VALORE — una dipendenza runtime vera, ma senza ciclo:
|
|
7
|
+
// `spawn.ts` non importa questo file, quindi il verso resta uno solo.
|
|
8
|
+
import { MODELS } from './spawn.js';
|
|
4
9
|
// Glifi LETTERALI del JSX. I dati passano dai loader, che sanificano al
|
|
5
10
|
// confine; questi no — quindi passano da `sanitize` una volta qui, così nessun
|
|
6
11
|
// sito di render scrive un glifo nudo. `↳ ○ ▸ ⏎ · − ↑ ↓` sono già concordi e
|
|
@@ -41,6 +46,12 @@ export const MODEL_EMPTY = TASK_EMPTY;
|
|
|
41
46
|
* legge come una riga normale e si scorre senza notarla; l'id per intero sta
|
|
42
47
|
* nel blocco preview. */
|
|
43
48
|
export const MODEL_UNKNOWN = '?';
|
|
49
|
+
/** T154 — le short di `MODEL_SHORT` nell'ordine POSIZIONALE di `MODELS`, non
|
|
50
|
+
* quello di inserimento dell'oggetto sopra: il blocco selettore della nuda
|
|
51
|
+
* sulla riga launch cicla su questo elenco, e `index`/`originIndex` restano
|
|
52
|
+
* indici in `MODELS` — derivarlo da lì invece di scriverlo a mano evita una
|
|
53
|
+
* seconda lista capace di disallinearsi al primo modello aggiunto. */
|
|
54
|
+
export const MODEL_SHORT_LIST = MODELS.map((m) => MODEL_SHORT[m]);
|
|
44
55
|
export function modelShort(id) {
|
|
45
56
|
if (!id)
|
|
46
57
|
return MODEL_EMPTY;
|
package/dist/input.js
CHANGED
|
@@ -467,9 +467,16 @@ export function useDeckInput({ cwd, tasksDir, mode, setMode, setNote, model, act
|
|
|
467
467
|
actions.togglePin();
|
|
468
468
|
}
|
|
469
469
|
else if (input === 'm') {
|
|
470
|
-
// T148 —
|
|
471
|
-
//
|
|
472
|
-
|
|
470
|
+
// T148/T154 — bersaglio deciso dal focus E dalla selezione (P2): con
|
|
471
|
+
// una riga sessioni selezionata (stale compresa, P3) scorre il modello
|
|
472
|
+
// di resume/fork di quella riga; altrove (focus task, focus inbox,
|
|
473
|
+
// pane sessioni senza selezione) scorre il modello della sessione nuda.
|
|
474
|
+
if (model.focus === 'sessions' && model.selSessionId) {
|
|
475
|
+
actions.cycleResumeModel();
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
actions.cycleBareModel();
|
|
479
|
+
}
|
|
473
480
|
}
|
|
474
481
|
else if (input === 'N') {
|
|
475
482
|
// T53 — nota sulla conversazione selezionata. MAIUSCOLA perché apre un
|
package/dist/overlays/inbox.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Molto più magro dello sheet: nessuna area di compilazione. Lo sheet ha
|
|
10
10
|
// quattro righe perché quattro parametri dello spawn sono una scelta (azione,
|
|
11
11
|
// prompt, modello, titolo); qui non ce n'è nessuna — la skill la decide la
|
|
12
|
-
// natura (D8), il modello è `
|
|
12
|
+
// natura (D8), il modello è `MODEL_DEFAULT` per tutte e tre (D11 preflight), la sessione
|
|
13
13
|
// nasce nuda (D10 preflight). Restano la lettura e un tasto.
|
|
14
14
|
//
|
|
15
15
|
// Lo spawn NON sta qui: arriva come callback `onDrain`, per lo stesso confine
|
package/dist/spawn.js
CHANGED
|
@@ -153,13 +153,15 @@ export const MODELS = ['fable', 'opus', 'sonnet', 'haiku'];
|
|
|
153
153
|
// kind non ha una riga. Resta l'unico default che resume e fork usano per
|
|
154
154
|
// intero, perché lì il modello non è mai una funzione del kind — è quello
|
|
155
155
|
// della conversazione d'origine (T148).
|
|
156
|
-
export const MODEL_DEFAULT = '
|
|
156
|
+
export const MODEL_DEFAULT = 'fable';
|
|
157
157
|
// T152 — `recap-status-project` non è una chiave del catalogo (§Doc Impact,
|
|
158
158
|
// "le chiavi del catalogo sono i valori di --prompt-kind"): lo spawn headless
|
|
159
159
|
// non passa mai --prompt-kind, quindi una riga lì sarebbe irraggiungibile da
|
|
160
160
|
// ogni chiamante. Il default vive qui, accanto a MODEL_DEFAULT, e non nel
|
|
161
|
-
// catalogo.
|
|
162
|
-
//
|
|
161
|
+
// catalogo. Resta `opus` mentre i tre recap del catalogo stanno su fable: è
|
|
162
|
+
// l'unico recap che gira in headless (`-p`), senza nessuno che ne legga
|
|
163
|
+
// l'esito mentre si forma e possa rilanciarlo — il testo prodotto è il
|
|
164
|
+
// deliverable, e viene riletto da disco anche giorni dopo.
|
|
163
165
|
export const PROJECT_STATUS_MODEL = 'opus';
|
|
164
166
|
// T66 — le azioni del detail. Non sono un catalogo nuovo: ognuna è un
|
|
165
167
|
// `--prompt-kind` già esistente più `checkpoint`, e tutte passano dallo stesso
|
|
@@ -276,9 +278,9 @@ export function spawnDeck(id, cwd, sessionId, kind, model = MODEL_DEFAULT, spawn
|
|
|
276
278
|
// volta sola, quindi una nota cambiata DOPO non ri-titola la tab già aperta.
|
|
277
279
|
//
|
|
278
280
|
// T148 — il modello viaggia SEMPRE, come in `deckArgs`: senza `--model`
|
|
279
|
-
// `deck-run` risolve il proprio default
|
|
280
|
-
// CLI, quindi una conversazione haiku ripresa senza il flag ripartirebbe
|
|
281
|
-
//
|
|
281
|
+
// `deck-run` risolve il proprio default (`MODEL_DEFAULT`) e lo passa comunque
|
|
282
|
+
// al CLI, quindi una conversazione haiku ripresa senza il flag ripartirebbe sul
|
|
283
|
+
// default (P2 preflight). Qui però non c'è un default da cablare: chi
|
|
282
284
|
// chiama ha già risolto il valore — il modello della conversazione d'origine,
|
|
283
285
|
// o quello scelto al suo posto — e lo passa come argomento obbligatorio.
|
|
284
286
|
export function resumeArgs(taskId, sessionId, model, note) {
|
|
@@ -328,8 +330,17 @@ export function spawnDeckFork(taskId, cwd, originId, newId, model) {
|
|
|
328
330
|
// di spawnDeck: i tre argomenti mancano tutti insieme, un `if` per ciascuno
|
|
329
331
|
// sporcherebbe il percorso bound. Il titolo tab resta la label loom — lo mette
|
|
330
332
|
// deck-run, perché il match compass è window-level e non sa nulla di task.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
+
//
|
|
334
|
+
// T154 — il modello viaggia SEMPRE nell'argv (`emptyArgs`), anche sul default:
|
|
335
|
+
// prima di questa task il tasto `c` non lo passava affatto, e chi lo premeva
|
|
336
|
+
// non sceglieva né vedeva quale modello `deck-run` avrebbe risolto da sé.
|
|
337
|
+
// Estratta come i suoi fratelli (`deckArgs`/`resumeArgs`/`forkArgs`): tutti i
|
|
338
|
+
// costruttori di argv con un test proprio lo sono (P4 preflight).
|
|
339
|
+
export function emptyArgs(model) {
|
|
340
|
+
return ['--no-task', '--model', model];
|
|
341
|
+
}
|
|
342
|
+
export function spawnClaudeEmpty(cwd, model) {
|
|
343
|
+
return launchDeckRun(emptyArgs(model), cwd);
|
|
333
344
|
}
|
|
334
345
|
/**
|
|
335
346
|
* T134 — sessione NUDA con un prompt letterale: `deck-run --no-task --prompt
|
package/dist/ui/fields.js
CHANGED
|
@@ -28,8 +28,8 @@ export function FieldText({ value, caret, focused, cols, }) {
|
|
|
28
28
|
* preview sessione). */
|
|
29
29
|
export const LABEL_W = 8;
|
|
30
30
|
/**
|
|
31
|
-
* T117 — una riga a
|
|
32
|
-
* voce attiva in video inverso.
|
|
31
|
+
* T117 — i soli BOTTONI di una riga a scelta: valore per valore, già tagliati
|
|
32
|
+
* al budget e stilati (voce attiva in video inverso, origine sottolineata).
|
|
33
33
|
*
|
|
34
34
|
* Due passate di `cutParts`: la seconda serve SOLO quando qualcosa cade, e
|
|
35
35
|
* riserva le colonne del contatore. Riservarle sempre costerebbe 6 colonne su
|
|
@@ -39,11 +39,14 @@ export const LABEL_W = 8;
|
|
|
39
39
|
* cancellerebbe l'unica informazione che la riga esiste per dare — quale valore
|
|
40
40
|
* sta per essere usato.
|
|
41
41
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* T154/P6 — estratta da `ChoiceRow`, che ne resta il primo chiamante: un
|
|
43
|
+
* secondo (il blocco selettore della nuda sulla riga launch) ha bisogno dei
|
|
44
|
+
* soli bottoni dentro una cornice propria (nessun caret, un'etichetta diversa
|
|
45
|
+
* da `label.padEnd`), e riscriverne la resa produrrebbe la seconda copia della
|
|
46
|
+
* regola di troncamento e sottolineatura — quella che diverge al primo modello
|
|
47
|
+
* aggiunto a `MODELS`.
|
|
45
48
|
*/
|
|
46
|
-
export function
|
|
49
|
+
export function choiceButtons(values, index, avail, originIndex) {
|
|
47
50
|
const segs = values.map((v) => `[ ${v} ]`);
|
|
48
51
|
const parts = [];
|
|
49
52
|
segs.forEach((s, i) => {
|
|
@@ -51,11 +54,24 @@ export function ChoiceRow({ label, values, index, focused, width, originIndex, }
|
|
|
51
54
|
parts.push(' ');
|
|
52
55
|
parts.push(s);
|
|
53
56
|
});
|
|
54
|
-
const avail = Math.max(0, width - LABEL_W - CARET_OFF.length);
|
|
55
57
|
let shown = cutParts(parts, avail, index * 2);
|
|
56
58
|
const dropped = (v) => segs.filter((s, i) => v[i * 2] !== s).length;
|
|
57
59
|
if (dropped(shown) > 0)
|
|
58
60
|
shown = cutParts(parts, Math.max(0, avail - 6), index * 2);
|
|
59
61
|
const cut = dropped(shown);
|
|
60
|
-
|
|
62
|
+
const nodes = shown.map((part, i) => i % 2 === 1 ? (_jsx(Text, { children: part }, i)) : (_jsx(Text, { inverse: i / 2 === index, underline: i / 2 === originIndex, color: i / 2 === index ? 'green' : 'gray', children: part }, i)));
|
|
63
|
+
return { nodes, cut };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* T117 — una riga a SCELTA di un'area di compilazione: caret + etichetta +
|
|
67
|
+
* bottoni di `choiceButtons`.
|
|
68
|
+
*
|
|
69
|
+
* T148 — estratto dal detail della task perché un secondo chiamante (il
|
|
70
|
+
* selettore modello della preview sessione) ne riusa la stessa resa a quadre,
|
|
71
|
+
* non un componente gemello capace di divergere.
|
|
72
|
+
*/
|
|
73
|
+
export function ChoiceRow({ label, values, index, focused, width, originIndex, }) {
|
|
74
|
+
const avail = Math.max(0, width - LABEL_W - CARET_OFF.length);
|
|
75
|
+
const { nodes, cut } = choiceButtons(values, index, avail, originIndex);
|
|
76
|
+
return (_jsxs(Text, { wrap: "truncate-end", children: [focused ? CARET : CARET_OFF, _jsx(Text, { dimColor: true, children: label.padEnd(LABEL_W) }), nodes, cut > 0 ? _jsxs(Text, { color: "yellow", children: [" \u00B7 +", cut] }) : null] }));
|
|
61
77
|
}
|
package/package.json
CHANGED
package/scripts/deck-run
CHANGED
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
# LOOM_DECK_STATE_PROFILE PTYXIS_PROFILE annunciata a compass (default: bindings/claude
|
|
61
61
|
# del progetto, letto da dconf; settata a vuoto = nessun annuncio)
|
|
62
62
|
# LOOM_DECK_PERMISSION_MODE override del permissionMode del file config (default: campo file, poi 'manual')
|
|
63
|
-
# LOOM_DECK_MODEL modello quando --model non è passato (batte il catalogo, default:
|
|
63
|
+
# LOOM_DECK_MODEL modello quando --model non è passato (batte il catalogo, default: fable)
|
|
64
64
|
#
|
|
65
65
|
set -euo pipefail
|
|
66
66
|
|
|
@@ -181,7 +181,7 @@ USAGE="uso: deck-run <TaskID> [--model <alias>] [--prompt-kind <kind>] [--sessio
|
|
|
181
181
|
ha una task da nominare, a differenza dei kind del catalogo
|
|
182
182
|
|
|
183
183
|
--model modello della sessione: fable|opus|sonnet|haiku
|
|
184
|
-
(default: il modello del kind in catalogo, o '
|
|
184
|
+
(default: il modello del kind in catalogo, o 'fable' fuori catalogo)
|
|
185
185
|
valore ignoto → fallback sul default, con avviso su stderr
|
|
186
186
|
|
|
187
187
|
--title-note nota della conversazione, appesa al titolo tab
|
|
@@ -356,7 +356,12 @@ MODE_FLAG="--permission-mode ${PERM_MODE} "
|
|
|
356
356
|
|
|
357
357
|
# ── modello (T108 + T152) ────────────────────────────────────────────────────
|
|
358
358
|
# Precedenza: --model > env LOOM_DECK_MODEL > modello del kind nel catalogo >
|
|
359
|
-
# '
|
|
359
|
+
# 'fable'. L'ultimo gradino è il gemello bash di `MODEL_DEFAULT` in spawn.ts e
|
|
360
|
+
# va tenuto allineato a mano: i due lati non si leggono a vicenda (il deck deve
|
|
361
|
+
# poter MOSTRARE la selezione iniziale prima che deck-run esista come processo),
|
|
362
|
+
# quindi un cambio qui senza l'altro fa partire dalla lista un modello diverso
|
|
363
|
+
# da quello che il detail aveva scritto a schermo.
|
|
364
|
+
# Fino a T152 il default era un valore unico per tutta la famiglia,
|
|
360
365
|
# perché il frontmatter `model:` delle skill riscriveva comunque il modello di
|
|
361
366
|
# sessione — un default configurabile qui non sarebbe servito a nulla. Da
|
|
362
367
|
# quando le skill non dichiarano più `model:`, il default DEVE viaggiare col
|
|
@@ -395,9 +400,9 @@ if [[ -z "$MODEL" && -n "$CATALOG_KIND" && "$CATALOG_KIND" != "none" ]]; then
|
|
|
395
400
|
fi
|
|
396
401
|
case "${MODEL:-}" in
|
|
397
402
|
fable|opus|sonnet|haiku) ;;
|
|
398
|
-
'') MODEL="
|
|
399
|
-
*) echo "modello ignoto: '${MODEL}' → fallback '
|
|
400
|
-
MODEL="
|
|
403
|
+
'') MODEL="fable" ;;
|
|
404
|
+
*) echo "modello ignoto: '${MODEL}' → fallback 'fable' (usa fable|opus|sonnet|haiku)" >&2
|
|
405
|
+
MODEL="fable" ;;
|
|
401
406
|
esac
|
|
402
407
|
MODEL_FLAG="--model ${MODEL} "
|
|
403
408
|
|
package/scripts/prompt-catalog
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Formato a righe: <kind><TAB><template><TAB><modello>, con `{TASK}`
|
|
4
4
|
# interpolato col TaskID. Righe vuote e righe che iniziano con `#` sono
|
|
5
5
|
# commenti. Il modello è OPZIONALE: una riga a due colonne resta leggibile e
|
|
6
|
-
# chi la consulta cade sul proprio default (`MODEL_DEFAULT`/`
|
|
6
|
+
# chi la consulta cade sul proprio default (`MODEL_DEFAULT`/`fable`) — il campo
|
|
7
7
|
# esiste per i kind bound a una skill (T152), non per ogni riga del file.
|
|
8
8
|
#
|
|
9
9
|
# Perché un file dati e non un `case` dentro deck-run: due lati hanno bisogno
|
|
@@ -20,18 +20,19 @@
|
|
|
20
20
|
# nel comando passato a `bash -lc`, e questo file è committato — un apice qui
|
|
21
21
|
# sarebbe un errore di scrittura, non un input da quotare.
|
|
22
22
|
#
|
|
23
|
-
# Il
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
23
|
+
# Il criterio della colonna modello: lettura e conversazione stanno su fable
|
|
24
|
+
# (i tre recap, il preflight), la scrittura presidiata su opus (run e
|
|
25
|
+
# checkpoint, che toccano codice e committano). Cambiare una parola qui cambia
|
|
26
|
+
# il costo di OGNI sessione aperta su quel kind, da ogni superficie del deck —
|
|
27
|
+
# non è una preferenza di questo progetto, è il default di famiglia.
|
|
27
28
|
# `recap` è il DISPATCHER: risolve la task, ne legge il Size e passa da sé alla
|
|
28
29
|
# sotto-skill. Lo usa chi non sa se la task è un cappello — gli acceleratori
|
|
29
30
|
# della lista, dove il deck ha in mano solo tasks.md, che il Size non ce l'ha.
|
|
30
31
|
# Le due voci `recap-task`/`recap-epic` saltano quel giro: le sceglie il DETAIL,
|
|
31
32
|
# che il task file l'ha già aperto e quindi il Size lo conosce.
|
|
32
|
-
recap /loom-works:recap-status {TASK}
|
|
33
|
-
recap-task /loom-works:recap-status-task {TASK}
|
|
34
|
-
recap-epic /loom-works:recap-status-epic {TASK}
|
|
35
|
-
preflight /loom-works:preflight-task {TASK}
|
|
36
|
-
run /loom-works:run-task {TASK}
|
|
37
|
-
checkpoint /loom-works:checkpoint-task {TASK}
|
|
33
|
+
recap /loom-works:recap-status {TASK} fable
|
|
34
|
+
recap-task /loom-works:recap-status-task {TASK} fable
|
|
35
|
+
recap-epic /loom-works:recap-status-epic {TASK} fable
|
|
36
|
+
preflight /loom-works:preflight-task {TASK} fable
|
|
37
|
+
run /loom-works:run-task {TASK} opus
|
|
38
|
+
checkpoint /loom-works:checkpoint-task {TASK} opus
|