@lamemind/loom-deck 0.15.0 → 0.16.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/README.md CHANGED
@@ -4,12 +4,16 @@ Deck TUI (Ink) **per-progetto** della famiglia [loom](https://github.com/lamemin
4
4
 
5
5
  Legge il `tasks.md` del progetto e, con un tasto (poi un click), **spawna** una tab
6
6
  [Ptyxis](https://gitlab.gnome.org/chergert/ptyxis) che avvia una sessione Claude Code
7
- già bound alla task via `LOOM_TASK`, dritta su `/loom-works:run-task <Txx>`.
7
+ già bound alla task via `LOOM_TASK`, con un prompt di recap sullo stato della task.
8
8
 
9
9
  ```
10
- ↑↓ scegli la task → ⏎ → tab CC di fianco → parte già su /loom-works:run-task <Txx>
10
+ ↑↓ scegli la task → ⏎ → tab CC di fianco → LOOM_TASK bound + recap stato task
11
11
  ```
12
12
 
13
+ Entrambi i prefissi del contratto loom entrano in lista: **`T`** (code task) e
14
+ **`D`** (doc task). Nessuna differenza di trattamento — il prompt iniziale è un
15
+ recap, non l'invocazione di una skill, quindi vale identico sulle due famiglie.
16
+
13
17
  ## Ruolo nella famiglia loom
14
18
 
15
19
  `loom-deck` è un **client** con runtime proprio (TUI Ink) che **consuma** il contratto
@@ -193,6 +197,11 @@ p priorità s stato i id
193
197
 
194
198
  Partendo da catena vuota, digitare `ppi` produce `[pri ↓, id ↑]`. Il ciclo parte
195
199
  sempre **dallo stato corrente**, che il modale mostra dal vivo mentre digiti.
200
+
201
+ Sull'**id** il confronto è numerico (`T9` prima di `T10`, non lessicografico) e i
202
+ due prefissi sono blocchi distinti — le `D` in coda alle `T`: i counter `T` e `D`
203
+ sono separati nel contratto loom, quindi `T01` e `D01` non sono confrontabili
204
+ come numeri soli.
196
205
  A parità su tutte le chiavi decide sempre l'`id` (confronto **numerico**: `T9`
197
206
  prima di `T10`) → l'ordine è deterministico, mai instabile fra un refresh e l'altro.
198
207
 
@@ -234,7 +243,8 @@ npx @lamemind/loom-deck
234
243
  scripts/deck-run T18
235
244
  ```
236
245
 
237
- Apre una tab Ptyxis nella window attiva con `LOOM_TASK=T18 claude '/loom-works:run-task T18'`.
246
+ Apre una tab Ptyxis nella window attiva con `LOOM_TASK=T18 claude 'recap stato task T18'`
247
+ (prompt override-abile via `LOOM_DECK_ENTER_PROMPT`, placeholder `{TASK}`).
238
248
 
239
249
  ## Sviluppo (TUI Ink)
240
250
 
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 { findTaskFile } from './tasks.js';
6
+ import { TASK_ID_RE, findTaskFile } 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).
@@ -81,11 +81,11 @@ export function initialDetail(current, prog, date = today()) {
81
81
  * restano i token grezzi originali: nessun re-flow della tabella, il diff resta
82
82
  * di una riga. `ok:false` = id assente → il chiamante non scrive nulla.
83
83
  *
84
- * L'id deve rispettare `^T\d+$`, stesso gate di `parseTasks`: senza, un id
85
- * arbitrario matcherebbe la riga di HEADER (`| ID | Pri | K | Prog |`) o quella
86
- * di separatore, riscrivendone le celle e sfondando la tabella.
84
+ * L'id deve rispettare `TASK_ID_RE` (importato da tasks.ts stesso gate di
85
+ * `parseTasks`, non una copia): senza, un id arbitrario matcherebbe la riga di
86
+ * HEADER (`| ID | Pri | K | Prog |`) o quella di separatore, riscrivendone le
87
+ * celle e sfondando la tabella.
87
88
  */
88
- const TASK_ID_RE = /^T\d+$/;
89
89
  export function updateTasksMdRow(content, id, priGlyph, progGlyph) {
90
90
  if (!TASK_ID_RE.test(id))
91
91
  return { content, ok: false };
package/dist/tasks.js CHANGED
@@ -1,6 +1,13 @@
1
1
  import { readFileSync, readdirSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { normalizeEmoji } from './viewport.js';
4
+ /**
5
+ * Forma di un ID task: `T` (code) o `D` (doc) + numero. FONTE UNICA del gate —
6
+ * `parseTasks` lo usa per scartare header/separatore della tabella, `task-edit`
7
+ * per non riscrivere per sbaglio quelle stesse righe. Tenerlo in un posto solo
8
+ * evita che i due gate divergano (uno accetterebbe righe che l'altro rifiuta).
9
+ */
10
+ export const TASK_ID_RE = /^[TD]\d+$/;
4
11
  // D1 (preflight T20): default docs/tasks.md, override della docs-root via env
5
12
  // LOOM_DECK_DOCS_ROOT (es. questo progetto usa `runtime`). No auto-detect.
6
13
  export function resolveTasksPath(cwd = process.cwd()) {
@@ -13,9 +20,11 @@ export function resolveTasksDir(cwd = process.cwd()) {
13
20
  return join(dirname(resolveTasksPath(cwd)), 'tasks');
14
21
  }
15
22
  // Estrae le righe `| Tnn | Pri | K | Prog | Task |` della Tasks Overview.
16
- // Il prefisso task è `T` (contratto plugin); le doc-task `D{N}` e le righe
17
- // header/separatore non matchano `^T\d+$` scartate. deck-run agisce solo
18
- // su task T (run-task), quindi le D non hanno azione qui.
23
+ // Due prefissi ammessi (contratto plugin, counter separati): `T` = code task,
24
+ // `D` = doc task. Le D entrano in lista come le T perché `deck-run` NON inietta
25
+ // `run-task`: il prompt iniziale è un recap diretto ("recap stato task <id>"),
26
+ // valido identico su una doc task. Righe header/separatore non matchano `^[TD]\d+$`
27
+ // → scartate.
19
28
  export function parseTasks(content) {
20
29
  const tasks = [];
21
30
  for (const line of content.split('\n')) {
@@ -25,7 +34,7 @@ export function parseTasks(content) {
25
34
  const cells = t.split('|').map((c) => c.trim());
26
35
  // cells[0] = '' (prima del primo |), cells[last] = '' (dopo l'ultimo |)
27
36
  const id = cells[1];
28
- if (!/^T\d+$/.test(id))
37
+ if (!TASK_ID_RE.test(id))
29
38
  continue;
30
39
  // Colonne overview: | ID | Pri | K | Prog | Task | → cells[2]=Pri (icona
31
40
  // priorità), cells[4]=Prog. K (Kind, cells[3]) non serve al deck.
@@ -77,7 +86,10 @@ export function parseTaskDetail(id, content) {
77
86
  let inDesc = false;
78
87
  for (const line of content.split('\n')) {
79
88
  if (!title && line.startsWith('# ')) {
80
- title = line.replace(/^#\s+/, '').replace(/^Task:\s*/, '').trim();
89
+ // I due template scrivono H1 diversi — `# Task: …` (code) e
90
+ // `# Doc Task: …` (doc): entrambi i cappelli vanno via, il titolo mostrato
91
+ // è la descrizione, non la sua categoria (già data dall'id).
92
+ title = line.replace(/^#\s+/, '').replace(/^(?:Doc\s+)?Task:\s*/, '').trim();
81
93
  continue;
82
94
  }
83
95
  if (line.startsWith('## ')) {
package/dist/view.js CHANGED
@@ -53,11 +53,16 @@ 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
- // parseTasks ammette solo /^T\d+$/, ma un id fuori forma non deve far esplodere
57
- // il comparator finisce in coda con Number.MAX_SAFE_INTEGER.
56
+ // parseTasks ammette due prefissi con counter SEPARATI (`T` code, `D` doc)
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.
61
+ // Un id fuori forma non deve far esplodere il comparator → coda con MAX_SAFE.
62
+ const ID_PREFIX_RANK = { T: 0, D: 1 };
58
63
  export function idNum(id) {
59
- const m = /^T(\d+)$/.exec(id);
60
- return m ? Number(m[1]) : Number.MAX_SAFE_INTEGER;
64
+ const m = /^([TD])(\d+)$/.exec(id);
65
+ return m ? ID_PREFIX_RANK[m[1]] * 1_000_000 + Number(m[2]) : Number.MAX_SAFE_INTEGER;
61
66
  }
62
67
  function rankOf(task, key) {
63
68
  if (key === 'pri')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamemind/loom-deck",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Deck TUI Ink per-progetto della famiglia loom: legge tasks.md e spawna sessioni Claude Code bound via LOOM_TASK",
5
5
  "type": "module",
6
6
  "bin": {