feat: referentiel projets dynamique + validation nommage by design

Durcit la convention de nommage des projets (dérive constatée : 'Sliding
Automation', 'code_versioning'... au lieu des formes canoniques).

- trilium_api.py : projets_canoniques() lit le référentiel = valeurs du label
  projet sur les notes de type=projet (source unique, pas de constante en dur).
  Note-projet CodeVersioning créée (manquait).
- mcp_server.py : _valider_projet() branché dans les 6 tools de création
  (add_decision/history/backlog, new_conversation, create_entite, add_skill).
  Refuse un projet non canonique (suggestion si faute) ou inconnu (renvoi au
  processus de création de projet). Ne verrouille pas si référentiel illisible.
- lint_audit.py : VAL-nommage aligné sur le référentiel (attrape casse, espace
  ET snake_case ; l'ancien 'contient un espace' ratait code_versioning).
- Données : 79 notes ré-étiquetées vers les 3 formes canoniques.

Quality by design : l'erreur de nommage devient impossible à l'écriture, le
Lint n'est plus que le filet de sécurité.
This commit is contained in:
2026-07-17 14:59:33 +02:00
parent d1e67431b6
commit 943acbc573
2425 changed files with 710525 additions and 6 deletions
@@ -0,0 +1,118 @@
#cython: language_level=3
from __future__ import print_function
from cpython.mem cimport PyMem_Malloc, PyMem_Free
from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
Py_buffer
from .errors import HttpParserInvalidURLError
cimport cython
from . cimport url_cparser as uparser
__all__ = ('parse_url',)
DEF MAX_URL_LENGTH = (1 << 16) - 1
@cython.freelist(250)
cdef class URL:
cdef readonly bytes schema
cdef readonly bytes host
cdef readonly object port
cdef readonly bytes path
cdef readonly bytes query
cdef readonly bytes fragment
cdef readonly bytes userinfo
def __cinit__(self, bytes schema, bytes host, object port, bytes path,
bytes query, bytes fragment, bytes userinfo):
self.schema = schema
self.host = host
self.port = port
self.path = path
self.query = query
self.fragment = fragment
self.userinfo = userinfo
def __repr__(self):
return ('<URL schema: {!r}, host: {!r}, port: {!r}, path: {!r}, '
'query: {!r}, fragment: {!r}, userinfo: {!r}>'
.format(self.schema, self.host, self.port, self.path,
self.query, self.fragment, self.userinfo))
def parse_url(url):
cdef:
Py_buffer py_buf
char* buf_data
uparser.http_parser_url* parsed
int res
bytes schema = None
bytes host = None
object port = None
bytes path = None
bytes query = None
bytes fragment = None
bytes userinfo = None
object result = None
int off
int ln
parsed = <uparser.http_parser_url*> \
PyMem_Malloc(sizeof(uparser.http_parser_url))
uparser.http_parser_url_init(parsed)
PyObject_GetBuffer(url, &py_buf, PyBUF_SIMPLE)
try:
if py_buf.len > MAX_URL_LENGTH:
# http_parser stores URL field offsets/lengths as uint16_t,
# so URLs longer than this will cause silent truncation.
# See https://github.com/MagicStack/httptools/issues/142
raise HttpParserInvalidURLError(
"url is too long: url length of {} bytes exceeds the "
"maximum of {} bytes".format(py_buf.len, MAX_URL_LENGTH))
buf_data = <char*>py_buf.buf
res = uparser.http_parser_parse_url(buf_data, py_buf.len, 0, parsed)
if res == 0:
if parsed.field_set & (1 << uparser.UF_SCHEMA):
off = parsed.field_data[<int>uparser.UF_SCHEMA].off
ln = parsed.field_data[<int>uparser.UF_SCHEMA].len
schema = buf_data[off:off+ln]
if parsed.field_set & (1 << uparser.UF_HOST):
off = parsed.field_data[<int>uparser.UF_HOST].off
ln = parsed.field_data[<int>uparser.UF_HOST].len
host = buf_data[off:off+ln]
if parsed.field_set & (1 << uparser.UF_PORT):
port = parsed.port
if parsed.field_set & (1 << uparser.UF_PATH):
off = parsed.field_data[<int>uparser.UF_PATH].off
ln = parsed.field_data[<int>uparser.UF_PATH].len
path = buf_data[off:off+ln]
if parsed.field_set & (1 << uparser.UF_QUERY):
off = parsed.field_data[<int>uparser.UF_QUERY].off
ln = parsed.field_data[<int>uparser.UF_QUERY].len
query = buf_data[off:off+ln]
if parsed.field_set & (1 << uparser.UF_FRAGMENT):
off = parsed.field_data[<int>uparser.UF_FRAGMENT].off
ln = parsed.field_data[<int>uparser.UF_FRAGMENT].len
fragment = buf_data[off:off+ln]
if parsed.field_set & (1 << uparser.UF_USERINFO):
off = parsed.field_data[<int>uparser.UF_USERINFO].off
ln = parsed.field_data[<int>uparser.UF_USERINFO].len
userinfo = buf_data[off:off+ln]
return URL(schema, host, port, path, query, fragment, userinfo)
else:
raise HttpParserInvalidURLError("invalid url {!r}".format(url))
finally:
PyBuffer_Release(&py_buf)
PyMem_Free(parsed)