mcp-cronos 1.0.0__py3-none-any.whl
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.
- mcp_cronos/__init__.py +16 -0
- mcp_cronos/config.py +232 -0
- mcp_cronos/default_templates/consolida.md +50 -0
- mcp_cronos/default_templates/fine_giornata.md +146 -0
- mcp_cronos/default_templates/standup.md +45 -0
- mcp_cronos/i18n.py +153 -0
- mcp_cronos/server.py +550 -0
- mcp_cronos/template_loader.py +54 -0
- mcp_cronos/templates.py +190 -0
- mcp_cronos/tools/__init__.py +25 -0
- mcp_cronos/tools/aggiungi_progetto.py +212 -0
- mcp_cronos/tools/cerca.py +102 -0
- mcp_cronos/tools/consolida.py +162 -0
- mcp_cronos/tools/entries.py +221 -0
- mcp_cronos/tools/fine_giornata.py +181 -0
- mcp_cronos/tools/reader.py +192 -0
- mcp_cronos/tools/scrivi_fine_giornata.py +128 -0
- mcp_cronos/tools/settimana.py +103 -0
- mcp_cronos/tools/standup.py +174 -0
- mcp_cronos/utils/__init__.py +23 -0
- mcp_cronos/utils/dates.py +139 -0
- mcp_cronos/utils/markdown.py +342 -0
- mcp_cronos-1.0.0.dist-info/METADATA +713 -0
- mcp_cronos-1.0.0.dist-info/RECORD +26 -0
- mcp_cronos-1.0.0.dist-info/WHEEL +4 -0
- mcp_cronos-1.0.0.dist-info/entry_points.txt +2 -0
mcp_cronos/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Cronos - Server MCP per la gestione del diario di lavoro.
|
|
3
|
+
|
|
4
|
+
Fornisce tool per:
|
|
5
|
+
- Aggiungere entry al diario giornaliero
|
|
6
|
+
- Aggiungere contenuto a entry di progetto esistenti
|
|
7
|
+
- Leggere entry per data o range
|
|
8
|
+
- Generare riassunti discorsivi per lo standup
|
|
9
|
+
- Cercare testo nelle entry
|
|
10
|
+
- Generare riassunti settimanali
|
|
11
|
+
- Gestire bloccanti
|
|
12
|
+
- Elencare progetti menzionati
|
|
13
|
+
- Chiudere la giornata con riassunti e consolidamento
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__version__ = "1.0.0"
|
mcp_cronos/config.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration for MCP Cronos.
|
|
3
|
+
|
|
4
|
+
Loads settings from a cronos.toml file (searched in priority order: explicit
|
|
5
|
+
CRONOS_CONFIG_PATH env var, diary root, ~/.config/cronos/) and merges them with
|
|
6
|
+
language-specific defaults from the i18n module.
|
|
7
|
+
|
|
8
|
+
The diary path is still provided by the original helper functions, which are kept
|
|
9
|
+
as-is because other modules import them directly.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Optional
|
|
17
|
+
|
|
18
|
+
if sys.version_info >= (3, 11):
|
|
19
|
+
import tomllib
|
|
20
|
+
else:
|
|
21
|
+
try:
|
|
22
|
+
import tomli as tomllib # type: ignore[no-redef]
|
|
23
|
+
except ImportError:
|
|
24
|
+
tomllib = None # type: ignore[assignment]
|
|
25
|
+
|
|
26
|
+
from mcp_cronos.i18n import get_language_pack
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Diary path helpers (unchanged — other modules depend on these)
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_diario_path() -> Path:
|
|
34
|
+
"""
|
|
35
|
+
Return the diary root path from the CRONOS_DIARIO_PATH environment variable.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Path to the diary working directory.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
RuntimeError: If CRONOS_DIARIO_PATH is not set.
|
|
42
|
+
"""
|
|
43
|
+
path_str = os.environ.get("CRONOS_DIARIO_PATH")
|
|
44
|
+
if not path_str:
|
|
45
|
+
raise RuntimeError(
|
|
46
|
+
"Variabile d'ambiente CRONOS_DIARIO_PATH non impostata. "
|
|
47
|
+
"Imposta il path del diario di lavoro, es: "
|
|
48
|
+
"CRONOS_DIARIO_PATH=/path/to/Diario"
|
|
49
|
+
)
|
|
50
|
+
return Path(path_str)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def ensure_diario_exists() -> bool:
|
|
54
|
+
"""
|
|
55
|
+
Check whether the diary directory exists.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if the directory exists, False otherwise.
|
|
59
|
+
"""
|
|
60
|
+
return get_diario_path().exists()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
# Config dataclass
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class CronosConfig:
|
|
70
|
+
"""Full application configuration, merged from TOML file and language defaults."""
|
|
71
|
+
|
|
72
|
+
lang: str
|
|
73
|
+
section_entries: str
|
|
74
|
+
section_blockers: str
|
|
75
|
+
section_day_summary: str
|
|
76
|
+
section_tech_summary: str
|
|
77
|
+
section_standup_message: str
|
|
78
|
+
blockers_default: str
|
|
79
|
+
title_format: str
|
|
80
|
+
git_enabled: bool
|
|
81
|
+
auto_push: bool
|
|
82
|
+
commit_message: str
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
# Singleton
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
_config: Optional[CronosConfig] = None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _reset_config() -> None:
|
|
93
|
+
"""
|
|
94
|
+
Clear the cached CronosConfig singleton.
|
|
95
|
+
|
|
96
|
+
Intended exclusively for tests that need to reload config between cases.
|
|
97
|
+
Should not be called in production code.
|
|
98
|
+
"""
|
|
99
|
+
global _config
|
|
100
|
+
_config = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
# Internal helpers
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _find_config_file() -> Optional[Path]:
|
|
109
|
+
"""
|
|
110
|
+
Search for a cronos.toml file in priority order.
|
|
111
|
+
|
|
112
|
+
Search order:
|
|
113
|
+
1. CRONOS_CONFIG_PATH env var (explicit path, highest priority)
|
|
114
|
+
2. {CRONOS_DIARIO_PATH}/cronos.toml
|
|
115
|
+
3. ~/.config/cronos/cronos.toml
|
|
116
|
+
|
|
117
|
+
Returns the first existing Path found, or None if none exist.
|
|
118
|
+
"""
|
|
119
|
+
explicit = os.environ.get("CRONOS_CONFIG_PATH")
|
|
120
|
+
if explicit:
|
|
121
|
+
p = Path(explicit)
|
|
122
|
+
if p.exists():
|
|
123
|
+
return p
|
|
124
|
+
|
|
125
|
+
# Diary root — only attempt if CRONOS_DIARIO_PATH is set (avoid RuntimeError)
|
|
126
|
+
diario_env = os.environ.get("CRONOS_DIARIO_PATH")
|
|
127
|
+
if diario_env:
|
|
128
|
+
candidate = Path(diario_env) / "cronos.toml"
|
|
129
|
+
if candidate.exists():
|
|
130
|
+
return candidate
|
|
131
|
+
|
|
132
|
+
# XDG-style user config
|
|
133
|
+
xdg = Path.home() / ".config" / "cronos" / "cronos.toml"
|
|
134
|
+
if xdg.exists():
|
|
135
|
+
return xdg
|
|
136
|
+
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _parse_toml(path: Path) -> dict[str, Any]:
|
|
141
|
+
"""
|
|
142
|
+
Parse a TOML file and return its contents as a dict.
|
|
143
|
+
|
|
144
|
+
Returns an empty dict on any error (missing file, invalid syntax, missing
|
|
145
|
+
tomllib/tomli library) so that callers can always fall back to defaults
|
|
146
|
+
without special-casing the error.
|
|
147
|
+
"""
|
|
148
|
+
if tomllib is None:
|
|
149
|
+
return {}
|
|
150
|
+
try:
|
|
151
|
+
with open(path, "rb") as fh:
|
|
152
|
+
return tomllib.load(fh)
|
|
153
|
+
except Exception: # noqa: BLE001 — intentional catch-all for TOML parse errors
|
|
154
|
+
return {}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
# Public loader
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def load_config() -> CronosConfig:
|
|
163
|
+
"""
|
|
164
|
+
Load, merge, and cache the application configuration.
|
|
165
|
+
|
|
166
|
+
Resolution order for each setting:
|
|
167
|
+
- Explicit user value in cronos.toml beats language default.
|
|
168
|
+
- Language defaults come from get_language_pack(lang).
|
|
169
|
+
- Hard-coded fallbacks apply when neither source provides a value.
|
|
170
|
+
|
|
171
|
+
The result is cached in the module-level _config singleton. Call
|
|
172
|
+
_reset_config() to force a fresh load (useful in tests).
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
A fully populated CronosConfig instance.
|
|
176
|
+
"""
|
|
177
|
+
global _config
|
|
178
|
+
if _config is not None:
|
|
179
|
+
return _config
|
|
180
|
+
|
|
181
|
+
# Parse the config file (empty dict if not found or unreadable)
|
|
182
|
+
config_path = _find_config_file()
|
|
183
|
+
raw: dict[str, Any] = _parse_toml(config_path) if config_path is not None else {}
|
|
184
|
+
|
|
185
|
+
cronos_section: dict[str, Any] = raw.get("cronos", {})
|
|
186
|
+
|
|
187
|
+
# Language
|
|
188
|
+
lang: str = cronos_section.get("lang", "it")
|
|
189
|
+
pack = get_language_pack(lang)
|
|
190
|
+
|
|
191
|
+
# Section names: user overrides > language defaults
|
|
192
|
+
user_sections: dict[str, Any] = cronos_section.get("sections", {})
|
|
193
|
+
section_entries = user_sections.get("entries", pack.sections["entries"])
|
|
194
|
+
section_blockers = user_sections.get("blockers", pack.sections["blockers"])
|
|
195
|
+
section_day_summary = user_sections.get("day_summary", pack.sections["day_summary"])
|
|
196
|
+
section_tech_summary = user_sections.get("tech_summary", pack.sections["tech_summary"])
|
|
197
|
+
section_standup_message = user_sections.get("standup_message", pack.sections["standup_message"])
|
|
198
|
+
|
|
199
|
+
# Diary settings
|
|
200
|
+
user_diary: dict[str, Any] = cronos_section.get("diary", {})
|
|
201
|
+
title_format: str = user_diary.get("title_format", f"{pack.title_prefix} - {{date}}")
|
|
202
|
+
|
|
203
|
+
# Git settings: user overrides > defaults.
|
|
204
|
+
# The [cronos] section may carry a top-level scalar `git = false/true` as a
|
|
205
|
+
# shorthand for git_enabled, or a full [cronos.git] sub-table. Both forms
|
|
206
|
+
# are supported; the sub-table form takes precedence when present.
|
|
207
|
+
raw_git = cronos_section.get("git", {})
|
|
208
|
+
if isinstance(raw_git, dict):
|
|
209
|
+
user_git: dict[str, Any] = raw_git
|
|
210
|
+
git_enabled_default: bool = True
|
|
211
|
+
else:
|
|
212
|
+
# Scalar shorthand: `git = false` under [cronos]
|
|
213
|
+
user_git = {}
|
|
214
|
+
git_enabled_default = bool(raw_git)
|
|
215
|
+
git_enabled: bool = bool(user_git.get("enabled", git_enabled_default))
|
|
216
|
+
auto_push: bool = bool(user_git.get("auto_push", True))
|
|
217
|
+
commit_message: str = user_git.get("commit_message", "diario: fine giornata {date}")
|
|
218
|
+
|
|
219
|
+
_config = CronosConfig(
|
|
220
|
+
lang=lang,
|
|
221
|
+
section_entries=section_entries,
|
|
222
|
+
section_blockers=section_blockers,
|
|
223
|
+
section_day_summary=section_day_summary,
|
|
224
|
+
section_tech_summary=section_tech_summary,
|
|
225
|
+
section_standup_message=section_standup_message,
|
|
226
|
+
blockers_default=pack.blockers_default,
|
|
227
|
+
title_format=title_format,
|
|
228
|
+
git_enabled=git_enabled,
|
|
229
|
+
auto_push=auto_push,
|
|
230
|
+
commit_message=commit_message,
|
|
231
|
+
)
|
|
232
|
+
return _config
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
ISTRUZIONI PER IL CONSOLIDAMENTO DEL DIARIO:
|
|
2
|
+
|
|
3
|
+
Hai ricevuto il contenuto completo del diario di oggi. Il file potrebbe contenere:
|
|
4
|
+
- Entry separate che trattano lo stesso argomento (es. analisi iniziale + approfondimento + verifica)
|
|
5
|
+
- Ripetizioni di dati e conclusioni tra entry diverse
|
|
6
|
+
- Informazioni sparse che andrebbero raggruppate
|
|
7
|
+
- Sezioni aggiunte in momenti diversi senza coerenza complessiva
|
|
8
|
+
|
|
9
|
+
Il tuo compito e' riscrivere il file consolidando tutto in modo coerente.
|
|
10
|
+
|
|
11
|
+
=== REGOLE DI CONSOLIDAMENTO ===
|
|
12
|
+
|
|
13
|
+
1. RAGGRUPPA PER PROGETTO E TEMA: entry diverse sullo stesso argomento vanno fuse in una
|
|
14
|
+
singola sezione. Ad esempio, "analisi ticket X", "approfondimento ticket X",
|
|
15
|
+
"verifica evidenze ticket X" diventano un'unica sezione "Ticket X" con la storia
|
|
16
|
+
completa dall'inizio alla fine.
|
|
17
|
+
|
|
18
|
+
2. ELIMINA RIPETIZIONI: se lo stesso dato, conclusione o evidenza appare in piu' entry,
|
|
19
|
+
tienilo una sola volta nel punto piu' logico.
|
|
20
|
+
|
|
21
|
+
3. MANTIENI TUTTI I DATI: non perdere informazioni. Se un'entry contiene un URL, un ID,
|
|
22
|
+
una query, un riferimento tecnico, deve restare nel file consolidato.
|
|
23
|
+
|
|
24
|
+
4. ORDINE CRONOLOGICO E LOGICO: organizza le sezioni seguendo il flusso della giornata.
|
|
25
|
+
Dentro ogni sezione, racconta la storia dall'inizio alla fine, non in ordine di
|
|
26
|
+
quando le entry sono state scritte.
|
|
27
|
+
|
|
28
|
+
5. FORMATO:
|
|
29
|
+
- Un H3 (###) per ogni progetto/tema principale
|
|
30
|
+
- Testo discorsivo, non elenchi puntati infiniti
|
|
31
|
+
- Sezioni "Dove verificare" con URL e query raggruppate alla fine della sezione
|
|
32
|
+
- Riferimenti (repository, branch, Jira, MR) alla fine della sezione
|
|
33
|
+
- Separatore --- tra sezioni di progetti diversi
|
|
34
|
+
|
|
35
|
+
6. NON AGGIUNGERE CONTENUTO: non inventare, non interpretare, non aggiungere
|
|
36
|
+
conclusioni che non erano nel diario originale. Solo riorganizzare.
|
|
37
|
+
|
|
38
|
+
7. PRESERVA LE SEZIONI DI CHIUSURA: se il diario ha gia' un "{section_day_summary}",
|
|
39
|
+
"{section_tech_summary}", "{section_standup_message}", lasciali invariati.
|
|
40
|
+
Se non li ha, non aggiungerli (per quelli c'e' il tool di fine giornata).
|
|
41
|
+
|
|
42
|
+
8. SEZIONE {section_blockers}: mantienila sempre alla fine.
|
|
43
|
+
|
|
44
|
+
=== PROCEDURA ===
|
|
45
|
+
|
|
46
|
+
1. Leggi tutto il contenuto
|
|
47
|
+
2. Identifica i temi/progetti trattati
|
|
48
|
+
3. Per ogni tema, raccogli tutte le informazioni sparse nel file
|
|
49
|
+
4. Riscrivi ogni tema come una sezione unica, coerente e completa
|
|
50
|
+
5. Scrivi il file consolidato al path indicato nel campo `file`
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
ISTRUZIONI PER LA CHIUSURA DI FINE GIORNATA:
|
|
2
|
+
|
|
3
|
+
Hai ricevuto le entry grezze del diario di oggi. Devi produrre un file markdown
|
|
4
|
+
completo con CINQUE sezioni, poi scriverlo al path indicato in `file`.
|
|
5
|
+
|
|
6
|
+
=== STRUTTURA DEL FILE DA SCRIVERE ===
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
# {titolo_standup}
|
|
10
|
+
|
|
11
|
+
## {section_day_summary}
|
|
12
|
+
|
|
13
|
+
{riassunto_giornata}
|
|
14
|
+
|
|
15
|
+
## {section_tech_summary}
|
|
16
|
+
|
|
17
|
+
{riassunto_tecnico}
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## {section_standup_message}
|
|
22
|
+
|
|
23
|
+
{messaggio_standup}
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## {section_entries}
|
|
28
|
+
|
|
29
|
+
{entries_riscritte}
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## {section_blockers}
|
|
34
|
+
|
|
35
|
+
{bloccanti}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
=== SEZIONE 1: ENTRIES RISCRITTE ===
|
|
39
|
+
|
|
40
|
+
Riscrivi le entry in ordine cronologico e logico. Le entry originali potrebbero essere
|
|
41
|
+
state aggiunte a casaccio durante la giornata — il tuo compito è riordinarle e
|
|
42
|
+
ristrutturarle in un racconto coerente della giornata.
|
|
43
|
+
|
|
44
|
+
Formato:
|
|
45
|
+
- Un unico H3 per progetto: `### {Progetto} - {Descrizione generale della giornata su quel progetto}`
|
|
46
|
+
- Paragrafo introduttivo che riassume il lavoro complessivo sul progetto
|
|
47
|
+
- Sotto-sezioni H4 (`####`) per ogni fase/attività distinta, in ordine cronologico:
|
|
48
|
+
`#### Fase 1 — {Titolo fase}`
|
|
49
|
+
- Dentro ogni fase: descrizione dettagliata con bullet points, blocchi codice se utili,
|
|
50
|
+
nomi di file, commit, comandi, config — tutto ciò che serve a ricostruire cosa è stato fatto
|
|
51
|
+
- Sezione `**Riferimenti:**` alla fine dell'entry con repository, branch, Jira, MR
|
|
52
|
+
- Separatore `---` tra entry di progetti diversi
|
|
53
|
+
|
|
54
|
+
Livello di dettaglio: MASSIMO. Questo è il log tecnico completo della giornata.
|
|
55
|
+
Includi commit hash, nomi file, classi, configurazioni, comandi eseguiti, errori
|
|
56
|
+
incontrati e come sono stati risolti.
|
|
57
|
+
|
|
58
|
+
=== SEZIONE 2: RIASSUNTO DELLA GIORNATA ===
|
|
59
|
+
|
|
60
|
+
Un paragrafo unico, denso e fluido che racconta l'intera giornata. Scritto come
|
|
61
|
+
se stessi raccontando a un collega tecnico cosa hai fatto, in modo scorrevole
|
|
62
|
+
ma completo.
|
|
63
|
+
|
|
64
|
+
Stile:
|
|
65
|
+
- Un solo paragrafo continuo (può essere lungo)
|
|
66
|
+
- Segui l'ordine cronologico della giornata
|
|
67
|
+
- Menziona i progetti, cosa è stato fatto e perché
|
|
68
|
+
- Includi problemi incontrati e come sono stati risolti
|
|
69
|
+
- Livello medio-alto: abbastanza tecnico da capire COSA è stato fatto,
|
|
70
|
+
senza entrare nel dettaglio di COME (niente nomi file, commit, config)
|
|
71
|
+
- Menziona ticket Jira e MR solo come riferimento generico ("ho creato i task Jira")
|
|
72
|
+
- Non usare elenchi puntati, solo prosa fluida
|
|
73
|
+
|
|
74
|
+
Esempio di tono (dal diario reale):
|
|
75
|
+
"Giornata interamente dedicata a Pollicino (RapsodiaTrace), proseguendo il lavoro
|
|
76
|
+
Keycloak del giorno precedente. La mattina è partita con un audit di tutti i README
|
|
77
|
+
del progetto per allinearli alle modifiche KC introdotte il giorno prima, seguito
|
|
78
|
+
dal merge di develop in master che era rimasto indietro di ~20 commit. Poi ho
|
|
79
|
+
affrontato l'analisi e implementazione di tre funzionalità KC avanzate..."
|
|
80
|
+
|
|
81
|
+
=== SEZIONE 3: RIASSUNTO TECNICO ===
|
|
82
|
+
|
|
83
|
+
Un riassunto estremamente denso e tecnico. Scritto per uno sviluppatore che deve
|
|
84
|
+
capire esattamente cosa è stato fatto, con tutti i dettagli implementativi.
|
|
85
|
+
|
|
86
|
+
Stile:
|
|
87
|
+
- Uno o due paragrafi densi (non elenchi puntati)
|
|
88
|
+
- Includi: commit hash, nomi file, classi, funzioni, configurazioni specifiche,
|
|
89
|
+
versioni, comandi, flag, variabili d'ambiente, endpoint API
|
|
90
|
+
- Includi errori specifici incontrati (messaggi di errore, status code)
|
|
91
|
+
- Includi workaround e soluzioni tecniche precise
|
|
92
|
+
- Includi nomi di tool, librerie, framework con versioni
|
|
93
|
+
- Usa parentesi e trattini per compattare le informazioni
|
|
94
|
+
- Non spiegare il "perché" — solo il "cosa" e il "come"
|
|
95
|
+
|
|
96
|
+
Esempio di tono (dal diario reale):
|
|
97
|
+
"Audit e allineamento di 6 README (commit `08687a8`), merge develop in master
|
|
98
|
+
(`85920da`). Analisi e piano per 3 funzionalità KC: SMTP AWS SES con placeholder
|
|
99
|
+
`$(env:VAR)` risolti a runtime da config-cli (`IMPORT_VARSUBSTITUTION_ENABLED=true`),
|
|
100
|
+
flow custom `browser-dashboard-mfa` con doppio nesting..."
|
|
101
|
+
|
|
102
|
+
=== SEZIONE 4: MESSAGGIO PER LO STANDUP ===
|
|
103
|
+
|
|
104
|
+
Messaggio discorsivo che può essere usato sia per lo standup che inviato
|
|
105
|
+
direttamente su Slack a un collega. Deve sembrare scritto da una persona,
|
|
106
|
+
non da un'AI. Seguire queste regole:
|
|
107
|
+
|
|
108
|
+
- Scritto in prima persona, tono naturale e colloquiale
|
|
109
|
+
- Continuità discorsiva assoluta: un flusso di frasi che scorrono l'una nell'altra,
|
|
110
|
+
MAI elenchi puntati, MAI strutture rigide con grassetto per progetto
|
|
111
|
+
- Alto livello — racconta cosa hai fatto e perché, non come
|
|
112
|
+
- Niente dettagli implementativi (niente nomi file, classi, funzioni, commit, MR, Jira)
|
|
113
|
+
- Niente strumenti interni (MCP, tool CLI, script, automazioni)
|
|
114
|
+
- Dettagli tecnici SOLO se servono a far capire il contesto o sono
|
|
115
|
+
interessanti per decisioni future
|
|
116
|
+
- Niente convenevoli, niente firme, niente saluti finali
|
|
117
|
+
- Se ci sono più progetti, collegali con transizioni naturali
|
|
118
|
+
("Finito quello...", "Nel pomeriggio...", "Sul fronte supporto...")
|
|
119
|
+
- Se ci sono bloccanti, menzionali alla fine in modo naturale
|
|
120
|
+
- Menziona le persone coinvolte quando rilevante (chi ha chiesto, chi lavora in parallelo)
|
|
121
|
+
- ATTENZIONE MASSIMA ad accenti e spaziature: usare sempre gli accenti
|
|
122
|
+
corretti (è, à, ò, ù, perché, cioè, può, già, più, ecc.), MAI apostrofi
|
|
123
|
+
al posto degli accenti (e' NO, è SÌ). Niente spazi mancanti o doppi,
|
|
124
|
+
punteggiatura italiana corretta. Rileggere il testo prima di produrlo.
|
|
125
|
+
|
|
126
|
+
Esempio di tono (messaggio reale inviato su Slack):
|
|
127
|
+
"Ieri ho lavorato tutto il giorno su IoPollicino. La mattina ho chiuso la feature
|
|
128
|
+
del codice referral facoltativo, mettendo il backend su stage presto così Matteo
|
|
129
|
+
poteva procedere in parallelo, e nel pomeriggio ho completato la parte dashboard
|
|
130
|
+
con le nuove metriche, i filtri per tipo utente e un warning che avvisa che siccome
|
|
131
|
+
gli utenti con referral code non compilano il questionario sull'app mentre gli altri
|
|
132
|
+
sì, le statistiche potrebbero essere sbilanciate verso gli utenti non-referral. La
|
|
133
|
+
situazione si normalizzerà quando verranno importati i dati dei questionari degli
|
|
134
|
+
utenti referral, ma nel frattempo il warning avvisa di leggere i numeri con cautela.
|
|
135
|
+
Finito quello, ho iniziato la nuova lavorazione sulle metriche della landing page.
|
|
136
|
+
Riccardo mi ha informato su quali statistiche servono — mezzo prevalente,
|
|
137
|
+
distribuzione modalità, motivo prevalente e tipo di mobilità attiva/motorizzata,
|
|
138
|
+
entro stamattina dovrei terminare."
|
|
139
|
+
|
|
140
|
+
=== PROCEDURA ===
|
|
141
|
+
|
|
142
|
+
1. Leggi attentamente tutte le entry grezze
|
|
143
|
+
2. Identifica l'ordine cronologico e i raggruppamenti logici
|
|
144
|
+
3. Genera le cinque sezioni (entries riscritte, riassunto giornata, riassunto tecnico, messaggio standup, bloccanti)
|
|
145
|
+
4. Assembla il file markdown completo seguendo la struttura indicata sopra
|
|
146
|
+
5. Chiama cronos_scrivi_fine_giornata con il contenuto generato per scrivere il file
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
ISTRUZIONI PER LA GENERAZIONE DEL RIASSUNTO:
|
|
2
|
+
|
|
3
|
+
Genera un messaggio discorsivo per lo standup o da inviare su Slack.
|
|
4
|
+
Deve sembrare scritto da una persona, non da un'AI.
|
|
5
|
+
|
|
6
|
+
REGOLE:
|
|
7
|
+
- Scritto in prima persona, tono naturale e colloquiale
|
|
8
|
+
- Continuità discorsiva assoluta: un flusso di frasi che scorrono l'una nell'altra,
|
|
9
|
+
MAI elenchi puntati, MAI strutture rigide con grassetto per progetto
|
|
10
|
+
- Alto livello — racconta cosa hai fatto e perché, non come
|
|
11
|
+
- Niente dettagli implementativi (niente nomi file, classi, funzioni, MR, Jira)
|
|
12
|
+
- Niente strumenti interni (MCP, tool CLI, script, automazioni)
|
|
13
|
+
- Dettagli tecnici solo se servono a far capire il contesto o sono interessanti
|
|
14
|
+
per decisioni future
|
|
15
|
+
- Se ci sono più progetti, collegali con transizioni naturali
|
|
16
|
+
("Finito quello...", "Nel pomeriggio...", "Sul fronte supporto...")
|
|
17
|
+
- Menziona le persone coinvolte quando rilevante
|
|
18
|
+
- Niente convenevoli, firme, saluti finali
|
|
19
|
+
- Se ci sono bloccanti, menzionali alla fine in modo naturale
|
|
20
|
+
- ATTENZIONE MASSIMA ad accenti e spaziature: usare sempre gli accenti
|
|
21
|
+
corretti (è, à, ò, ù, perché, cioè, può, già, più, ecc.), MAI apostrofi
|
|
22
|
+
al posto degli accenti (e' NO, è SI). Niente spazi mancanti o doppi,
|
|
23
|
+
punteggiatura italiana corretta. Rileggere il testo prima di produrlo.
|
|
24
|
+
|
|
25
|
+
ESEMPIO DI TONO (messaggio reale inviato su Slack):
|
|
26
|
+
"Ieri ho lavorato tutto il giorno su IoPollicino. La mattina ho chiuso la feature
|
|
27
|
+
del codice referral facoltativo, mettendo il backend su stage presto così Matteo
|
|
28
|
+
poteva procedere in parallelo, e nel pomeriggio ho completato la parte dashboard
|
|
29
|
+
con le nuove metriche, i filtri per tipo utente e un warning che avvisa che siccome
|
|
30
|
+
gli utenti con referral code non compilano il questionario sull'app mentre gli altri
|
|
31
|
+
sì, le statistiche potrebbero essere sbilanciate verso gli utenti non-referral. La
|
|
32
|
+
situazione si normalizzerà quando verranno importati i dati dei questionari degli
|
|
33
|
+
utenti referral, ma nel frattempo il warning avvisa di leggere i numeri con cautela.
|
|
34
|
+
Finito quello, ho iniziato la nuova lavorazione sulle metriche della landing page.
|
|
35
|
+
Riccardo mi ha informato su quali statistiche servono — mezzo prevalente,
|
|
36
|
+
distribuzione modalità, motivo prevalente e tipo di mobilità attiva/motorizzata,
|
|
37
|
+
entro stamattina dovrei terminare."
|
|
38
|
+
|
|
39
|
+
COSA EVITARE:
|
|
40
|
+
- Elenchi puntati (MAI)
|
|
41
|
+
- Strutture con **Progetto** in grassetto seguite da descrizione
|
|
42
|
+
- Dettagli di implementazione
|
|
43
|
+
- Linguaggio burocratico
|
|
44
|
+
- Convenevoli e formule di cortesia
|
|
45
|
+
- Riferimenti a strumenti interni o automazioni
|
mcp_cronos/i18n.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internationalisation support for mcp-cronos.
|
|
3
|
+
|
|
4
|
+
Provides a frozen LanguagePack dataclass that bundles all locale-specific
|
|
5
|
+
strings used when rendering diary entries and standup messages. Two packs are
|
|
6
|
+
shipped — Italian ("it", the application default) and English ("en").
|
|
7
|
+
|
|
8
|
+
Design notes
|
|
9
|
+
------------
|
|
10
|
+
- LanguagePack is frozen so it can be used as a dict key or cached safely.
|
|
11
|
+
- list fields (months, weekdays) use a plain list rather than a tuple so
|
|
12
|
+
callers can index them with the 0-based int values returned by datetime.
|
|
13
|
+
- format_date/format_title are thin convenience methods; heavy formatting
|
|
14
|
+
logic lives in the calling modules to keep this file declarative.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from datetime import date
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class LanguagePack:
|
|
25
|
+
"""Locale-specific strings and formatting rules for a single language."""
|
|
26
|
+
|
|
27
|
+
code: str
|
|
28
|
+
months: list[str] # 12 month names, index 0 = January
|
|
29
|
+
weekdays: list[str] # 7 weekday names, index 0 = Monday
|
|
30
|
+
title_prefix: str # e.g. "Per lo Stand-up"
|
|
31
|
+
date_format: str # template with {day}, {month}, {year} placeholders
|
|
32
|
+
sections: dict[str, str] # UI section labels; required keys documented below
|
|
33
|
+
blockers_default: str # default text when no blockers are present
|
|
34
|
+
temporal: dict[str, str] # relative-time expressions; required keys below
|
|
35
|
+
|
|
36
|
+
# sections keys: entries, blockers, day_summary, tech_summary, standup_message
|
|
37
|
+
# temporal keys: yesterday, day_before, last_weekday, from_to
|
|
38
|
+
|
|
39
|
+
def format_date(self, d: date) -> str:
|
|
40
|
+
"""Return a human-readable date string according to this language's date_format.
|
|
41
|
+
|
|
42
|
+
Uses 1-based day, a localised month name, and the four-digit year.
|
|
43
|
+
The month index is derived from d.month (1–12) mapped to months[0–11].
|
|
44
|
+
"""
|
|
45
|
+
return self.date_format.format(
|
|
46
|
+
day=d.day,
|
|
47
|
+
month=self.months[d.month - 1],
|
|
48
|
+
year=d.year,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def format_title(self, standup_date: date) -> str:
|
|
52
|
+
"""Return the full standup title for the given date.
|
|
53
|
+
|
|
54
|
+
Combines title_prefix with the formatted date, separated by " - ".
|
|
55
|
+
"""
|
|
56
|
+
return f"{self.title_prefix} - {self.format_date(standup_date)}"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Italian language pack (application default)
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
_IT = LanguagePack(
|
|
64
|
+
code="it",
|
|
65
|
+
months=[
|
|
66
|
+
"Gennaio",
|
|
67
|
+
"Febbraio",
|
|
68
|
+
"Marzo",
|
|
69
|
+
"Aprile",
|
|
70
|
+
"Maggio",
|
|
71
|
+
"Giugno",
|
|
72
|
+
"Luglio",
|
|
73
|
+
"Agosto",
|
|
74
|
+
"Settembre",
|
|
75
|
+
"Ottobre",
|
|
76
|
+
"Novembre",
|
|
77
|
+
"Dicembre",
|
|
78
|
+
],
|
|
79
|
+
weekdays=["lunedi", "martedi", "mercoledi", "giovedi", "venerdi", "sabato", "domenica"],
|
|
80
|
+
title_prefix="Per lo Stand-up",
|
|
81
|
+
date_format="{day} {month} {year}",
|
|
82
|
+
sections={
|
|
83
|
+
"entries": "Cosa ho fatto ieri",
|
|
84
|
+
"blockers": "Bloccanti",
|
|
85
|
+
"day_summary": "Riassunto della giornata",
|
|
86
|
+
"tech_summary": "Riassunto tecnico",
|
|
87
|
+
"standup_message": "Messaggio per lo standup",
|
|
88
|
+
},
|
|
89
|
+
blockers_default="Nessuno",
|
|
90
|
+
temporal={
|
|
91
|
+
"yesterday": "Ieri",
|
|
92
|
+
"day_before": "L'altro ieri",
|
|
93
|
+
"last_weekday": "{weekday} scorso",
|
|
94
|
+
"from_to": "Dal {start} al {end}",
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
# English language pack
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
_EN = LanguagePack(
|
|
103
|
+
code="en",
|
|
104
|
+
months=[
|
|
105
|
+
"January",
|
|
106
|
+
"February",
|
|
107
|
+
"March",
|
|
108
|
+
"April",
|
|
109
|
+
"May",
|
|
110
|
+
"June",
|
|
111
|
+
"July",
|
|
112
|
+
"August",
|
|
113
|
+
"September",
|
|
114
|
+
"October",
|
|
115
|
+
"November",
|
|
116
|
+
"December",
|
|
117
|
+
],
|
|
118
|
+
weekdays=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
|
|
119
|
+
title_prefix="For Stand-up",
|
|
120
|
+
date_format="{month} {day}, {year}",
|
|
121
|
+
sections={
|
|
122
|
+
"entries": "What I did yesterday",
|
|
123
|
+
"blockers": "Blockers",
|
|
124
|
+
"day_summary": "Daily summary",
|
|
125
|
+
"tech_summary": "Technical summary",
|
|
126
|
+
"standup_message": "Standup message",
|
|
127
|
+
},
|
|
128
|
+
blockers_default="None",
|
|
129
|
+
temporal={
|
|
130
|
+
"yesterday": "Yesterday",
|
|
131
|
+
"day_before": "Day before yesterday",
|
|
132
|
+
"last_weekday": "Last {weekday}",
|
|
133
|
+
"from_to": "From {start} to {end}",
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
# Public registry
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
LANGUAGES: dict[str, LanguagePack] = {
|
|
142
|
+
"it": _IT,
|
|
143
|
+
"en": _EN,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_language_pack(lang: str) -> LanguagePack:
|
|
148
|
+
"""Return the LanguagePack for the given language code.
|
|
149
|
+
|
|
150
|
+
Falls back to the Italian pack for any unrecognised or empty code, because
|
|
151
|
+
Italian is the application default language.
|
|
152
|
+
"""
|
|
153
|
+
return LANGUAGES.get(lang, _IT)
|