living-documentation 7.34.0 → 7.36.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.
@@ -44,6 +44,12 @@ function setTool(tool, shape) {
44
44
  }
45
45
  }
46
46
 
47
+ window.addEventListener('diagram:setTool', (e) => {
48
+ const detail = e.detail || {};
49
+ if (!detail.tool) return;
50
+ setTool(detail.tool, detail.shape);
51
+ });
52
+
47
53
  function selectAll() {
48
54
  if (!st.network || !st.nodes || !st.edges) return;
49
55
  const ids = st.nodes.getIds();
@@ -34,6 +34,7 @@ let _draggingAnchorIds = new Set();
34
34
  let _rehookEdgeId = null;
35
35
  let _rehookHoveredNodeId = null;
36
36
  let _rehookHoveredPortKey = null;
37
+ let _pointerDownSelection = { nodeIds: [], edgeIds: [] };
37
38
 
38
39
  // Returns true when an edge can enter rehook mode (at least one non-anchor endpoint).
39
40
  // Works for both port edges and native vis-network edges.
@@ -126,7 +127,11 @@ export function initNetwork(savedNodes, savedEdges, edgesStraight = false) {
126
127
  callback(data);
127
128
  markDirty();
128
129
  _addEdgeFromPort = null;
129
- setTimeout(() => { if (st.currentTool === 'addEdge') st.network.addEdgeMode(); }, 0);
130
+ setTimeout(() => {
131
+ if (st.currentTool === 'addEdge') {
132
+ window.dispatchEvent(new CustomEvent('diagram:setTool', { detail: { tool: 'select' } }));
133
+ }
134
+ }, 0);
130
135
  },
131
136
  },
132
137
  };
@@ -138,6 +143,13 @@ export function initNetwork(savedNodes, savedEdges, edgesStraight = false) {
138
143
  // Must be registered BEFORE any other capture-phase mousedown listeners on
139
144
  // the container so it can stopImmediatePropagation for locked targets.
140
145
  installUnlockHold(container);
146
+ container.addEventListener('mousedown', (e) => {
147
+ if (e.button !== 0) return;
148
+ _pointerDownSelection = {
149
+ nodeIds: [...(st.selectedNodeIds || [])],
150
+ edgeIds: [...(st.selectedEdgeIds || [])],
151
+ };
152
+ }, { capture: true });
141
153
 
142
154
  // ── Z-order patch ──────────────────────────────────────────────────────────
143
155
  // vis.js renders in 3 passes (normal → selected → hovered), which breaks
@@ -189,7 +201,7 @@ export function initNetwork(savedNodes, savedEdges, edgesStraight = false) {
189
201
  } else if (fromIsAnchor && !toIsAnchor) {
190
202
  level = toLevel;
191
203
  } else {
192
- level = Math.min(fromLevel, toLevel);
204
+ level = Math.max(fromLevel, toLevel);
193
205
  }
194
206
  if (!edgesByLevel.has(level)) edgesByLevel.set(level, []);
195
207
  edgesByLevel.get(level).push(edge);
@@ -1037,26 +1049,121 @@ function _distToSegment(px, py, ax, ay, bx, by) {
1037
1049
  }
1038
1050
 
1039
1051
 
1040
- // Returns the topmost (highest z-order) node whose bounding box contains canvasPos.
1052
+ function nodeContainsCanvasPoint(id, canvasPos) {
1053
+ const n = st.nodes.get(id);
1054
+ const bn = st.network && st.network.body.nodes[id];
1055
+ if (!n || !bn || n.shapeType === 'anchor') return false;
1056
+
1057
+ const shapeType = n.shapeType || 'box';
1058
+ const defaults = SHAPE_DEFAULTS[shapeType] || [60, 28];
1059
+ const W = n.nodeWidth || defaults[0];
1060
+ const H = shapeType === 'circle' ? W : (n.nodeHeight || defaults[1]);
1061
+
1062
+ const dx = canvasPos.x - bn.x;
1063
+ const dy = canvasPos.y - bn.y;
1064
+ const rot = n.rotation || 0;
1065
+ const lx = rot ? dx * Math.cos(-rot) - dy * Math.sin(-rot) : dx;
1066
+ const ly = rot ? dx * Math.sin(-rot) + dy * Math.cos(-rot) : dy;
1067
+
1068
+ if (shapeType === 'circle' || shapeType === 'ellipse') {
1069
+ const rx = W / 2;
1070
+ const ry = H / 2;
1071
+ return rx > 0 && ry > 0 && ((lx * lx) / (rx * rx) + (ly * ly) / (ry * ry)) <= 1;
1072
+ }
1073
+
1074
+ return Math.abs(lx) <= W / 2 && Math.abs(ly) <= H / 2;
1075
+ }
1076
+
1077
+ // Returns the topmost (highest z-order) node containing canvasPos.
1041
1078
  // Ignores anchor nodes and respects st.canonicalOrder.
1042
1079
  function topmostNodeAt(canvasPos) {
1043
- let topmost = null;
1044
- let topmostIdx = -1;
1045
- for (const id of st.canonicalOrder) {
1046
- const n = st.nodes.get(id);
1047
- const bn = st.network && st.network.body.nodes[id];
1048
- if (!n || !bn || n.shapeType === 'anchor') continue;
1049
- const defaults = SHAPE_DEFAULTS[n.shapeType || 'box'] || [60, 28];
1050
- const W = n.nodeWidth || defaults[0];
1051
- const H = n.nodeHeight || defaults[1];
1052
- const cx = bn.x, cy = bn.y;
1053
- if (canvasPos.x >= cx - W / 2 && canvasPos.x <= cx + W / 2 &&
1054
- canvasPos.y >= cy - H / 2 && canvasPos.y <= cy + H / 2) {
1055
- const idx = st.canonicalOrder.indexOf(id);
1056
- if (idx > topmostIdx) { topmostIdx = idx; topmost = id; }
1080
+ for (let i = st.canonicalOrder.length - 1; i >= 0; i--) {
1081
+ const id = st.canonicalOrder[i];
1082
+ if (nodeContainsCanvasPoint(id, canvasPos)) return id;
1083
+ }
1084
+ return null;
1085
+ }
1086
+
1087
+ function edgeDrawLevel(edgeData) {
1088
+ if (!edgeData) return -1;
1089
+ const fromLevel = st.canonicalOrder.indexOf(edgeData.from);
1090
+ const toLevel = st.canonicalOrder.indexOf(edgeData.to);
1091
+ if (fromLevel === -1 && toLevel === -1) return -1;
1092
+ if (fromLevel === -1) return toLevel;
1093
+ if (toLevel === -1) return fromLevel;
1094
+
1095
+ const fromNode = st.nodes.get(edgeData.from);
1096
+ const toNode = st.nodes.get(edgeData.to);
1097
+ const fromIsAnchor = fromNode && fromNode.shapeType === 'anchor';
1098
+ const toIsAnchor = toNode && toNode.shapeType === 'anchor';
1099
+ if (toIsAnchor && !fromIsAnchor) return fromLevel;
1100
+ if (fromIsAnchor && !toIsAnchor) return toLevel;
1101
+ return Math.max(fromLevel, toLevel);
1102
+ }
1103
+
1104
+ function nearestPortEdgeAt(canvasPos, threshold = 8) {
1105
+ const portEdges = st.edges.get({ filter: (e) => e.fromPort || e.toPort });
1106
+ let nearest = null;
1107
+ let nearestDist = Infinity;
1108
+ for (const edge of portEdges) {
1109
+ const d = distanceToPortEdge(edge, canvasPos);
1110
+ if (d < nearestDist) {
1111
+ nearestDist = d;
1112
+ nearest = edge;
1057
1113
  }
1058
1114
  }
1059
- return topmost;
1115
+ return nearest && nearestDist <= threshold ? nearest : null;
1116
+ }
1117
+
1118
+ function selectableEdgesForNodes(nodeIds) {
1119
+ const selectedSet = new Set(nodeIds);
1120
+ return st.edges.get().filter((e) => {
1121
+ if (!selectedSet.has(e.from) || !selectedSet.has(e.to)) return false;
1122
+ const fromN = st.nodes.get(e.from);
1123
+ const toN = st.nodes.get(e.to);
1124
+ const isFreeArrow = fromN && fromN.shapeType === 'anchor' && toN && toN.shapeType === 'anchor';
1125
+ return isFreeArrow ? !(fromN.locked && toN.locked) : !e.edgeLocked;
1126
+ }).map((e) => e.id);
1127
+ }
1128
+
1129
+ function selectNodesFromClick(nodeId, srcEvent) {
1130
+ const additive = !!(srcEvent && (srcEvent.metaKey || srcEvent.ctrlKey));
1131
+ const clicked = expandSelectionToGroup([nodeId]).filter((id) => {
1132
+ const n = st.nodes.get(id);
1133
+ return n && !n.locked && n.shapeType !== 'anchor';
1134
+ });
1135
+ if (!clicked.length) return;
1136
+
1137
+ let nodeIds;
1138
+ if (additive) {
1139
+ const next = new Set(_pointerDownSelection.nodeIds || []);
1140
+ const allAlreadySelected = clicked.every((id) => next.has(id));
1141
+ clicked.forEach((id) => {
1142
+ if (allAlreadySelected) next.delete(id);
1143
+ else next.add(id);
1144
+ });
1145
+ nodeIds = Array.from(next).filter((id) => {
1146
+ const n = st.nodes.get(id);
1147
+ return n && !n.locked && n.shapeType !== 'anchor';
1148
+ });
1149
+ } else {
1150
+ nodeIds = clicked;
1151
+ }
1152
+
1153
+ const edgeIds = selectableEdgesForNodes(nodeIds);
1154
+ _addingEdgesToSelection = true;
1155
+ st.network.setSelection({ nodes: nodeIds, edges: edgeIds });
1156
+ _addingEdgesToSelection = false;
1157
+ st.selectedNodeIds = nodeIds;
1158
+ st.selectedEdgeIds = edgeIds;
1159
+ _rehookEdgeId = _rehookHoveredNodeId = _rehookHoveredPortKey = null;
1160
+
1161
+ hideEdgePanel();
1162
+ if (nodeIds.length) showNodePanel();
1163
+ else {
1164
+ hideNodePanel();
1165
+ hideSelectionOverlay();
1166
+ }
1060
1167
  }
1061
1168
 
1062
1169
  function onDoubleClick(params) {
@@ -1229,8 +1336,8 @@ function onClickNode(params) {
1229
1336
  update.color = { color: 'rgba(0,0,0,0)', highlight: 'rgba(0,0,0,0)', hover: 'rgba(0,0,0,0)' };
1230
1337
  update.arrows = { to: { enabled: false }, from: { enabled: false } };
1231
1338
  }
1232
- st.edges.update(update);
1233
1339
  pushSnapshot();
1340
+ st.edges.update(update);
1234
1341
  markDirty();
1235
1342
  const edgeId = edgeData.id;
1236
1343
  _rehookHoveredNodeId = _rehookHoveredPortKey = null;
@@ -1244,14 +1351,34 @@ function onClickNode(params) {
1244
1351
  }
1245
1352
  }
1246
1353
 
1247
- // When a node is reported, params.edges is always empty — vis-network short-circuits
1354
+ // When a node is reported, params.edges is usually empty — vis-network short-circuits
1248
1355
  // edge detection once a node is found. Fix: call getEdgeAt() directly with CLIENT
1249
1356
  // coordinates. params.pointer.DOM is already offset-relative to the container, so
1250
1357
  // passing it to getEdgeAt() causes a double-subtraction of the container rect and
1251
1358
  // returns null. Passing clientX/Y lets vis-network do its own pixel-perfect detection.
1252
1359
  if (params.nodes.length > 0) {
1253
1360
  const clientPos = { x: params.event.srcEvent.clientX, y: params.event.srcEvent.clientY };
1254
- const edgeId = st.network.getEdgeAt(clientPos);
1361
+ const nativeEdgeId = st.network.getEdgeAt(clientPos);
1362
+ const portEdge = nearestPortEdgeAt(params.pointer.canvas);
1363
+ const edgeId = nativeEdgeId || (portEdge && portEdge.id);
1364
+
1365
+ // First honour the app-managed z-order. vis-network may report an edge or a
1366
+ // lower node at the click point; compare draw levels so the visibly top
1367
+ // element gets the click.
1368
+ const top = topmostNodeAt(params.pointer.canvas);
1369
+ if (top) {
1370
+ const topNode = st.nodes.get(top);
1371
+ const topLevel = st.canonicalOrder.indexOf(top);
1372
+ const edgeLevel = edgeId ? edgeDrawLevel(st.edges.get(edgeId)) : -1;
1373
+ if (topNode && !topNode.locked && topNode.shapeType !== 'anchor') {
1374
+ if (!edgeId || topLevel >= edgeLevel) {
1375
+ setTimeout(() => {
1376
+ selectNodesFromClick(top, params.event.srcEvent);
1377
+ }, 0);
1378
+ return;
1379
+ }
1380
+ }
1381
+ }
1255
1382
  if (edgeId) {
1256
1383
  const edge = st.edges.get(edgeId);
1257
1384
  const fromN = edge && st.nodes.get(edge.from);
@@ -1277,12 +1404,12 @@ function onClickNode(params) {
1277
1404
  return;
1278
1405
  }
1279
1406
  // No edge at click position — apply z-order correction for the topmost node.
1280
- const top = topmostNodeAt(params.pointer.canvas);
1407
+ const fallbackTop = topmostNodeAt(params.pointer.canvas);
1281
1408
  const clickable = params.nodes.filter(id => {
1282
1409
  const n = st.nodes.get(id);
1283
1410
  return n && !n.locked && n.shapeType !== 'anchor';
1284
1411
  });
1285
- if (!top || !clickable.includes(top)) {
1412
+ if (!fallbackTop || !clickable.includes(fallbackTop)) {
1286
1413
  const fallbackEdgeId = params.edges.length > 0 ? params.edges[0] : null;
1287
1414
  if (fallbackEdgeId) {
1288
1415
  setTimeout(() => {
@@ -1305,19 +1432,12 @@ function onClickNode(params) {
1305
1432
  // Also check the canvas for port-edge proximity when nothing was hit.
1306
1433
  if (params.nodes.length === 0 && params.edges.length === 0) {
1307
1434
  const cp = params.pointer.canvas;
1308
- const THRESHOLD = 8;
1309
1435
 
1310
1436
  // ── Port edge proximity check ──────────────────────────────────────────
1311
1437
  // vis-network's hit detection uses the invisible centre-to-centre ghost,
1312
1438
  // so port edges that diverge visually from that path are not selectable.
1313
- // We scan all port edges and pick the one whose bezier path is closest.
1314
- const portEdges = st.edges.get({ filter: (e) => e.fromPort || e.toPort });
1315
- let nearest = null, nearestDist = Infinity;
1316
- for (const edge of portEdges) {
1317
- const d = distanceToPortEdge(edge, cp);
1318
- if (d < nearestDist) { nearestDist = d; nearest = edge; }
1319
- }
1320
- if (nearest && nearestDist <= THRESHOLD) {
1439
+ const nearest = nearestPortEdgeAt(cp);
1440
+ if (nearest) {
1321
1441
  st.network.selectEdges([nearest.id]);
1322
1442
  st.selectedEdgeIds = [nearest.id];
1323
1443
  st.selectedNodeIds = [];
@@ -1880,5 +1880,170 @@
1880
1880
  "from": "n9"
1881
1881
  }
1882
1882
  ]
1883
+ },
1884
+ {
1885
+ "id": "d1777363627693",
1886
+ "title": "Living Documentation — context (demo conf)",
1887
+ "nodes": [
1888
+ {
1889
+ "id": "n1777367924055",
1890
+ "label": "",
1891
+ "shapeType": "box",
1892
+ "colorKey": "c-gray",
1893
+ "nodeWidth": 828,
1894
+ "nodeHeight": 661,
1895
+ "fontSize": 19,
1896
+ "textAlign": "right",
1897
+ "textValign": "bottom",
1898
+ "bgOpacity": null,
1899
+ "rotation": 0,
1900
+ "labelRotation": 0,
1901
+ "imageSrc": null,
1902
+ "groupId": null,
1903
+ "nodeLink": null,
1904
+ "locked": false,
1905
+ "x": -173,
1906
+ "y": 51
1907
+ },
1908
+ {
1909
+ "id": "n1",
1910
+ "label": "Living Documentation\n[Software System]\nMarkdown viewer + Express API\n+ MCP server (create-adr,\naudit-doc-drift)",
1911
+ "shapeType": "box",
1912
+ "colorKey": "c-blue",
1913
+ "nodeWidth": 240,
1914
+ "nodeHeight": 112,
1915
+ "fontSize": null,
1916
+ "textAlign": null,
1917
+ "textValign": null,
1918
+ "bgOpacity": null,
1919
+ "rotation": 0,
1920
+ "labelRotation": 0,
1921
+ "imageSrc": null,
1922
+ "groupId": null,
1923
+ "nodeLink": null,
1924
+ "locked": false,
1925
+ "x": 0,
1926
+ "y": 0
1927
+ },
1928
+ {
1929
+ "id": "n2",
1930
+ "label": "Coding agent\n[Person]\nClaude Code, Cursor…\nCodes the project and\nco-maintains its docs",
1931
+ "shapeType": "actor",
1932
+ "colorKey": "c-gray",
1933
+ "nodeWidth": 30,
1934
+ "nodeHeight": 52,
1935
+ "fontSize": null,
1936
+ "textAlign": null,
1937
+ "textValign": null,
1938
+ "bgOpacity": null,
1939
+ "rotation": 0,
1940
+ "labelRotation": 0,
1941
+ "imageSrc": null,
1942
+ "groupId": null,
1943
+ "nodeLink": null,
1944
+ "locked": false,
1945
+ "x": -426,
1946
+ "y": -176
1947
+ },
1948
+ {
1949
+ "id": "n3",
1950
+ "label": "Developer / Reader\n[Person]\nBrowses ADRs, validates\nstatuses, reads diagrams",
1951
+ "shapeType": "actor",
1952
+ "colorKey": "c-gray",
1953
+ "nodeWidth": 30,
1954
+ "nodeHeight": 52,
1955
+ "fontSize": null,
1956
+ "textAlign": null,
1957
+ "textValign": null,
1958
+ "bgOpacity": null,
1959
+ "rotation": 0,
1960
+ "labelRotation": 0,
1961
+ "imageSrc": null,
1962
+ "groupId": null,
1963
+ "nodeLink": null,
1964
+ "locked": false,
1965
+ "x": -426,
1966
+ "y": 170
1967
+ },
1968
+ {
1969
+ "id": "n4",
1970
+ "label": "Local filesystem\n[Datastore]\n.md docs, .metadata.json,\n.living-doc.json",
1971
+ "shapeType": "database",
1972
+ "colorKey": "c-teal",
1973
+ "nodeWidth": 217,
1974
+ "nodeHeight": 151,
1975
+ "fontSize": null,
1976
+ "textAlign": null,
1977
+ "textValign": null,
1978
+ "bgOpacity": null,
1979
+ "rotation": 0,
1980
+ "labelRotation": 0,
1981
+ "imageSrc": null,
1982
+ "groupId": null,
1983
+ "nodeLink": null,
1984
+ "locked": false,
1985
+ "x": 1,
1986
+ "y": 256
1987
+ }
1988
+ ],
1989
+ "edges": [
1990
+ {
1991
+ "id": "e1",
1992
+ "from": "n2",
1993
+ "to": "n1",
1994
+ "label": "invokes MCP tools (create-adr, audit-doc-drift, add_metadata)",
1995
+ "arrowDir": "to",
1996
+ "dashes": false,
1997
+ "fontSize": null,
1998
+ "labelRotation": 0,
1999
+ "edgeLabelOffsetX": -31,
2000
+ "edgeLabelOffsetY": -23,
2001
+ "fromPort": null,
2002
+ "toPort": null,
2003
+ "edgeColor": null,
2004
+ "edgeWidth": null,
2005
+ "edgeLocked": false,
2006
+ "edgeLabelWidth": 209.01385498046875
2007
+ },
2008
+ {
2009
+ "id": "e2",
2010
+ "from": "n3",
2011
+ "to": "n1",
2012
+ "label": "browses and edits docs via",
2013
+ "arrowDir": "to",
2014
+ "dashes": false,
2015
+ "fontSize": null,
2016
+ "labelRotation": 0,
2017
+ "edgeLabelOffsetX": -53,
2018
+ "edgeLabelOffsetY": 45,
2019
+ "fromPort": null,
2020
+ "toPort": null,
2021
+ "edgeColor": null,
2022
+ "edgeWidth": null,
2023
+ "edgeLocked": false,
2024
+ "edgeLabelWidth": 209.98614501953125
2025
+ },
2026
+ {
2027
+ "id": "e3",
2028
+ "from": "n1",
2029
+ "to": "n4",
2030
+ "label": "reads from / writes to",
2031
+ "arrowDir": "both",
2032
+ "dashes": false,
2033
+ "fontSize": null,
2034
+ "labelRotation": 0,
2035
+ "edgeLabelOffsetX": 0,
2036
+ "edgeLabelOffsetY": 0,
2037
+ "fromPort": null,
2038
+ "toPort": null,
2039
+ "edgeColor": null,
2040
+ "edgeWidth": null,
2041
+ "edgeLocked": false,
2042
+ "edgeLabelWidth": 126.26385498046875
2043
+ }
2044
+ ],
2045
+ "edgesStraight": false,
2046
+ "gridEnabled": false,
2047
+ "alignGuides": true
1883
2048
  }
1884
2049
  ]
@@ -1 +1,12 @@
1
- {}
1
+ {
2
+ "5_talks/2026_04_28_09_48_[CONFERENCE]_demo_living_documentation_mcp_en_conference": [
3
+ {
4
+ "path": "src/mcp/server.ts",
5
+ "hash": "c4197cbc1358618b783fdd7977c3c37354466562f0ac3c9c216145b2a8d23030"
6
+ },
7
+ {
8
+ "path": "README.md",
9
+ "hash": "f34a8945708d2fb963191781218920d0a8ce1edf0999da10b8685cf34ae1b939"
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,312 @@
1
+ ---
2
+ **date:** 2026-04-28
3
+ **status:** Draft
4
+ **description:** Support de présentation conférence — démo live de Living Documentation en mode IA-Assisted, scénarios A (création d'ADR) et B (audit de dérive) avec un agent codant connecté au MCP
5
+ **tags:** conference, talk, demo, mcp, scenario-a, scenario-b, create-adr, audit-doc-drift, live-coding, claude-code, nextjs, shadcn, drift-detection, presentation
6
+ ---
7
+
8
+ # Living Documentation — co-maintenir la doc avec un agent codant
9
+
10
+ > Support de présentation pour une conférence (~15 min de démo + 5 min de Q&A). Le but : montrer **en live** comment la documentation cesse d'être une dette quand on la confie à un agent codant connecté au bon MCP.
11
+
12
+ ## Le pitch en 3 lignes
13
+
14
+ 1. **Le constat** : sur tout projet qui vit, la doc dérive plus vite qu'on ne la met à jour. Au bout d'un cycle, on ne lui fait plus confiance.
15
+ 2. **La proposition** : un agent codant qui **co-maintient** la doc — un ADR par feature à la création, un audit de dérive à la demande.
16
+ 3. **Le levier technique** : un serveur MCP qui sérialise le contrat entre le LLM et la base documentaire, avec des garde-fous *enforced côté serveur* qui empêchent l'agent d'inventer des ADR fictifs ou des diagrammes hallucinés.
17
+
18
+ ---
19
+
20
+ ## Le problème — la doc, dette ou actif ?
21
+
22
+ Trois forces tirent les docs vers le bas sur tout projet qui dure :
23
+
24
+ 1. **Le code change plus vite que l'attention humaine.** Personne ne relit un ADR de 2024 en 2026 pour vérifier qu'il décrit encore le module.
25
+ 2. **La provenance s'efface.** Six mois après une décision, plus personne ne sait quel commit l'a implémentée, ni quels fichiers source sont en jeu.
26
+ 3. **L'audit manuel ne passe pas à l'échelle.** Au-delà de 50 ADR, *« tout relire »* n'est plus un workflow, c'est un mardi soir mort.
27
+
28
+ L'effet final : la doc devient **passive**. On la consulte par habitude, on lui fait de moins en moins confiance, et au bout d'un cycle elle finit en cimetière de bonnes intentions.
29
+
30
+ ---
31
+
32
+ ## La proposition — deux scénarios
33
+
34
+ ### Scénario A — un ADR par feature
35
+
36
+ À la fin de chaque feature, l'agent invoque le prompt MCP `create-adr` qui orchestre :
37
+
38
+ 1. **Recherche d'un ADR à supersede** — `list_documents` puis lecture des frontmatter (`description`, `tags`) pour repérer une décision rendue obsolète. Si trouvée → `update_document` pour basculer son `**status:**` à `SuperSeeded` (jamais delete — on garde la traçabilité).
39
+ 2. **Écriture du nouvel ADR** avec un frontmatter normalisé qui rend les recherches futures triviales :
40
+
41
+ ```
42
+ ---
43
+ **date:** YYYY-MM-DD
44
+ **status:** To be validated
45
+ **description:** Une phrase technique factuelle.
46
+ **tags:** 5-10 tags incluant librairies, concepts, symboles clés
47
+ ---
48
+ ```
49
+
50
+ 3. **Liaison metadata** — `add_metadata` sur **chaque fichier source** matériellement modifié, avec exclusion explicite des god files (`package.json`, `tsconfig.json`, barrels, codegen). Le SHA-256 stocké est ce qui permettra l'audit drift plus tard.
51
+
52
+ > Le `**status: To be validated**` à la création est volontaire : l'agent ne **promeut jamais** un ADR à `Accepted`. C'est l'humain qui valide.
53
+
54
+ ### Scénario B — auditer la dérive
55
+
56
+ Sur invocation de l'utilisateur (*« audite la doc »* / *« vérifie la fiabilité de la doc »*), l'agent invoque le prompt MCP `audit-doc-drift` :
57
+
58
+ 1. `list_documents_below_accuracy` — liste les ADR dont la fiabilité < 80 % (fichier source modifié vs hash stocké).
59
+ 2. Pour chaque doc dérivé : `get_accuracy` + `read_document` + `read_source_file` sur les fichiers `modified`.
60
+ 3. **Décision** :
61
+
62
+ | Outcome | Diagnostic | Action |
63
+ |---|---|---|
64
+ | **A** — ADR toujours correct | Refactor cosmétique (rename, formatting, helpers internes) | `refresh_metadata` — re-baseline |
65
+ | **B** — ADR out of sync | Changement sémantique du comportement décrit | Réécrit le doc via `update_document`, puis `refresh_metadata` |
66
+ | **C** — Fichier source manquant | Suppression ou rename | Escaladé à l'humain (l'agent ne décide pas seul du nettoyage) |
67
+
68
+ Les changements **structurels** (la décision elle-même est remise en cause) sont escaladés explicitement — ils déclenchent un nouveau Scénario A, pas une modification silencieuse.
69
+
70
+ ---
71
+
72
+ ## Architecture en un coup d'œil
73
+
74
+ [![Diagramme context — Living Documentation](./images/living_documentation_context_demo_conf.png)](/diagram?id=d1777363627693)
75
+
76
+ Le diagramme contient quatre nœuds : *Coding agent* (acteur), *Developer / Reader* (acteur), *Living Documentation* (système central) et *Local filesystem* (datastore). Les edges décrivent les trois interactions clés : invocation MCP par l'agent, navigation/édition par le développeur, et lecture/écriture sur le filesystem.
77
+
78
+ Le serveur MCP expose **16 tools** et **8 prompts**. Les deux qu'on déclenche en démo :
79
+
80
+ - **`create-adr`** — auto-invoqué sur *« feature terminée »*
81
+ - **`audit-doc-drift`** — auto-invoqué sur *« audite la doc »*
82
+
83
+ ---
84
+
85
+ ## Pourquoi c'est différent (vs wiki classique)
86
+
87
+ | Critère | Wiki classique | Living Documentation |
88
+ |---|---|---|
89
+ | Source de vérité | Le wiki | Le code, vérifié par hash |
90
+ | Création | Humain post-feature | Agent pendant la feature |
91
+ | Détection de dérive | Aucune (on espère) | Mécanique (SHA-256, jauge visuelle) |
92
+ | Diagrammes | À jour quand quelqu'un s'en souvient | Garde-fous serveur — impossible d'inventer un acteur absent des docs |
93
+ | Vendor lock-in | Total | Zéro — `.md` sur disque, git-friendly |
94
+ | Onboarding nouveau dev | *« Lis le wiki »* | *« Lance `npx living-documentation .`, puis branche ton agent »* |
95
+
96
+ ---
97
+
98
+ ## Plan de démo — 15 min
99
+
100
+ ### Phase 1 — Setup (2 min)
101
+ - Montrer le projet `mon-test` vide.
102
+ - Lancer `npx living-documentation ./documentation --port 4321` dans un terminal.
103
+ - Brancher Claude Code sur le MCP : `claude mcp add --transport http living-documentation http://localhost:4321/mcp`.
104
+ - Ouvrir `http://localhost:4321` côté grand écran : sidebar vide, message *« no docs yet »*.
105
+
106
+ ### Phase 2 — Scénario A (5 min)
107
+
108
+ Le prompt à coller dans Claude Code (à préparer sur slide pour copy-paste live) :
109
+
110
+ ```
111
+ # Pre-flight — MCP check
112
+
113
+ Le serveur MCP `living-documentation` doit être branché et accessible.
114
+ Avant de coder quoi que ce soit :
115
+
116
+ 1. Appelle son tool `list_documents` (nom complet côté client :
117
+ `mcp__living-documentation__list_documents`).
118
+ Si le tool n'est pas exposé OU s'il renvoie une erreur réseau :
119
+ **ARRÊTE-TOI ET DEMANDE-MOI** de lancer le serveur. Ne contourne pas
120
+ en utilisant Write ou en éditant des `.md` directement.
121
+
122
+ 2. Vérifie que les tools suivants sont bien dans ton inventaire MCP :
123
+ `create_document`, `update_document`, `add_metadata`,
124
+ `list_documents_below_accuracy`, `refresh_metadata`,
125
+ `read_source_file`. Si l'un manque, le serveur n'est pas à jour —
126
+ stoppe et préviens-moi.
127
+
128
+ 3. Appelle `mcp__living-documentation__get_server_guide` pour charger
129
+ en contexte le workflow officiel (création d'ADR, conventions
130
+ Markdown, garde-fous diagrammes). Ce guide est ta source de vérité
131
+ pour tout ce qui suit, *y compris si ton client n'expose pas les
132
+ prompts MCP au modèle* (cas fréquent — dans ce cas tu orchestres
133
+ le workflow toi-même en appelant les tools un par un, comme décrit
134
+ dans le guide).
135
+
136
+ 4. Confirme-moi en une ligne (a) combien de tools sont exposés,
137
+ (b) que tu as bien chargé le server guide.
138
+
139
+ Note : si ton client expose en plus les **prompts** MCP `create-adr`
140
+ et `audit-doc-drift`, tu peux les invoquer directement quand le moment
141
+ viendra ; c'est équivalent au workflow tools-by-hand mais plus court.
142
+ S'il ne les expose pas, suis simplement le server guide.
143
+
144
+ # Projet à implémenter
145
+
146
+ Create a visually impressive 2D browser game using a single HTML file with embedded JavaScript and HTML5 Canvas. No external dependencies.
147
+
148
+ Game concept:
149
+ - A small glowing player (circle or sprite) that can move with arrow keys or WASD
150
+ - The player must avoid falling obstacles from the top of the screen
151
+
152
+ Visual style (VERY IMPORTANT):
153
+ - Dark background (almost black)
154
+ - Neon / cyberpunk aesthetic
155
+ - Smooth animations (use requestAnimationFrame)
156
+ - Add particle effects:
157
+ - trail behind the player
158
+ - explosion particles when the player dies
159
+ - Add screen shake on collision
160
+ - Add glow effects (shadowBlur, gradients, etc.)
161
+ - Obstacles should have slight variations and glow
162
+
163
+ Gameplay:
164
+ - Smooth movement (use delta time)
165
+ - Increasing difficulty over time (more obstacles, faster speed)
166
+ - Score that increases over time
167
+ - Game over screen with restart (press space)
168
+
169
+ Code quality:
170
+ - Keep everything in one HTML file
171
+ - Structure code clearly (separate update, render, input)
172
+ - Comment important parts briefly
173
+
174
+ Polish:
175
+ - Add a start screen ("Press space to start")
176
+ - Add subtle animations even when idle
177
+ - Make the game feel responsive and dynamic
178
+
179
+ Make sure the result is immediately playable by opening the HTML file in a browser.
180
+
181
+
182
+ **IMPORTANT** : A chaque fois qu'une partie est terminée (feature), dis "feature terminée" — je m'attends à ce que tu déclenches le prompt MCP `create-adr` du serveur `living-documentation`,
183
+ qui doit appeler `create_document` puis `add_metadata` :
184
+
185
+ - `create_document` avec :
186
+ - `folder: "adrs"`
187
+ - `category: "FRONTEND"` (ou "ARCHITECTURE" si tu juges que la décision
188
+ porte sur la structure du projet plus que sur la feature UI)
189
+ - `content` : frontmatter complet — date du jour, `status: To be validated`,
190
+ description factuelle d'une phrase, ≥5 tags (incluant nextjs, shadcn,
191
+ recharts, dashboard, app-router, et les noms de symboles pertinents) +
192
+ corps Contexte / Décision / Conséquences.
193
+ - `add_metadata` sur CHAQUE fichier source qui porte la feature (page,
194
+ composant Card consommateur, mock-data).
195
+ - PAS sur `package.json`, `tailwind.config.ts`, `tsconfig.json`,
196
+ `components.json`, ni les barrels shadcn — ce sont des god files.
197
+
198
+ # Règle dure
199
+
200
+ Tu n'écris JAMAIS un `.md` avec ton outil Write, Edit, ou via shell. Toute
201
+ création / modification de doc passe **exclusivement** par les tools MCP
202
+ `create_document` / `update_document` du serveur `living-documentation`.
203
+ Si à un moment ces tools deviennent inaccessibles, arrête-toi et signale-le
204
+ — ne fallback pas sur Write.
205
+ ```
206
+
207
+ Pendant que l'agent travaille, ce qu'il faut pointer à l'audience :
208
+
209
+ - L'agent code la page, lance `npm run dev`, vérifie que ça tourne.
210
+ - À la fin il prononce *« feature terminée »* → déclenchement automatique de `create-adr`.
211
+ - **Montrer en live** : appel à `create_document` → frontmatter conforme → `add_metadata` sur les bons fichiers, **pas** sur les god files (insister visuellement).
212
+ - Refresh du viewer (`Cmd+R`) : nouvelle entrée dans la sidebar, jauge de fiabilité à **100 %** (vert plein).
213
+
214
+ ### Phase 3 — Drift volontaire + Scénario B (5 min)
215
+ - Modifier *à la main* un composant dans le code (ajouter un tooltip Recharts + axe Y formaté en `$`).
216
+ - Refresh du viewer : la jauge tombe à ~50 % (orange).
217
+ - Dire *« audite la doc »*.
218
+ - **Montrer en live** : `audit-doc-drift` → l'agent lit le code modifié, conclut *« modification sémantique »*, réécrit l'ADR via `update_document`, re-baseline.
219
+ - Jauge revient à **100 %**.
220
+
221
+ ### Phase 4 — Diagrammes garde-fous (2 min)
222
+ - Demander *« fais-moi un container diagram »* → l'agent refuse / propose un Context d'abord (règle de progression C4).
223
+ - Demander *« fais-moi un context diagram »* → l'agent le génère **uniquement** à partir des ADR existants, pas en inventant.
224
+ - Tenter manuellement de créer un container diagram via le MCP sans `userRequestedExplicitly: true` → erreur explicite côté serveur.
225
+
226
+ ### Phase 5 — Q&A (1 min de buffer)
227
+
228
+ ---
229
+
230
+ ## Q&A anticipées
231
+
232
+ <details>
233
+ <summary>« Mais l'agent va halluciner des choses dans les ADR, non ? »</summary>
234
+
235
+ Le risque est réel mais minimisé par trois mécanismes convergents :
236
+
237
+ 1. Le frontmatter `description:` est **une phrase factuelle**, pas un essai. L'humain valide (`status: To be validated` → `Accepted`).
238
+ 2. Les fichiers source attachés via `add_metadata` créent une **boucle de vérification** : si l'ADR décrit autre chose que ce que fait le code, l'audit drift le révélera au prochain changement.
239
+ 3. Les diagrammes ont des **garde-fous serveur** (`userRequestedExplicitly: true` requis pour container/UML, type `image` rejeté sans `imageSrc`, contract docs-source-of-truth enforcé).
240
+
241
+ Et fondamentalement : le coût d'une hallucination dans un ADR auto-corrigé au prochain audit < le coût d'une doc périmée qu'on traîne pendant deux ans.
242
+ </details>
243
+
244
+ <details>
245
+ <summary>« Pourquoi pas juste utiliser le LLM directement sans MCP ? »</summary>
246
+
247
+ Sans MCP, chaque session démarre vierge : le LLM ne sait pas où sont les ADR, ni leur format, ni quelles décisions ont déjà été prises. Il va :
248
+
249
+ - proposer un emplacement aléatoire,
250
+ - inventer un format de frontmatter,
251
+ - oublier de chercher un ADR à supersede,
252
+ - ne jamais faire `add_metadata` (puisqu'il ne sait pas que ça existe).
253
+
254
+ Le MCP **sérialise le contrat** entre le LLM et la base documentaire : un seul format, un seul workflow, des garde-fous *enforced côté serveur*. C'est ce qui permet à un projet de tenir 200 ADR sans dériver.
255
+ </details>
256
+
257
+ <details>
258
+ <summary>« Combien de tokens ça coûte ? »</summary>
259
+
260
+ L'audit drift d'un projet de 50 ADR avec 2-3 fichiers source attachés en moyenne consomme typiquement **< 50k tokens d'input** par cycle complet. Soit quelques cents par audit.
261
+
262
+ C'est conçu pour : `list_documents_below_accuracy` retourne max 10 docs par batch, chaque `read_source_file` est borné à 512 KB, et l'agent décide quels fichiers lire — pas un balayage exhaustif. Les `tags` du frontmatter permettent un filtrage cheap avant lecture du corps.
263
+ </details>
264
+
265
+ <details>
266
+ <summary>« Et si je n'utilise pas Claude / un agent ? »</summary>
267
+
268
+ Le **mode standalone** reste pleinement utilisable : éditeur Markdown avec live-save, snippets, image paste, file attachments, diagram editor, word cloud, recherche full-text, export PDF / Notion / Confluence. Tu y gagnes la jauge de fiabilité (en mode manuel : tu cliques *Refresh* après avoir validé que le doc colle au code) et le format ADR portable.
269
+
270
+ Mais tu rates le **co-maintain**, qui est le cœur de la proposition. Le mode standalone est un bon point d'entrée ; le mode AI-Assisted est ce qui fait que la doc reste vivante.
271
+ </details>
272
+
273
+ <details>
274
+ <summary>« Pourquoi les ADR sont en `To be validated` et pas en `Accepted` à la création ? »</summary>
275
+
276
+ Parce que **l'agent ne décide pas seul** des décisions architecturales. Il rédige une proposition factuelle, l'humain valide.
277
+
278
+ Concrètement, le statut n'a *aucun effet* côté LLM (il traite `To be validated` et `Accepted` exactement pareil). C'est purement un signal pour l'humain : *« voici une décision à relire. »*
279
+
280
+ Seul `SuperSeeded` est filtré côté MCP — les ADR superseded ne remontent jamais dans les recherches contextuelles, ce qui évite que l'agent ne se réfère à des décisions abandonnées.
281
+ </details>
282
+
283
+ <details>
284
+ <summary>« Est-ce que ça marche avec un projet existant qui a déjà 100 ADR ? »</summary>
285
+
286
+ Oui, et c'est un cas d'usage prévu. Le démarrage typique sur un projet existant :
287
+
288
+ 1. Pointer `living-documentation` sur le dossier qui contient déjà les ADR.
289
+ 2. Lancer un audit `audit-doc-drift` à blanc — il flaggera tout ADR qui n'a aucune `metadata` attachée (donc qui échappe à l'audit drift) et proposera de les binder un par un.
290
+ 3. Pour chaque ADR existant : l'agent lit le code mentionné dans le corps de l'ADR, propose les `add_metadata` pertinents, l'humain valide.
291
+
292
+ Une fois cette migration faite, le projet entre dans le cycle normal : nouveaux ADR via Scénario A, audit régulier via Scénario B.
293
+ </details>
294
+
295
+ ---
296
+
297
+ ## Pour aller plus loin (docs internes)
298
+
299
+ - Concept général : [Living Documentation](?doc=3_concept%252F2026_04_08_22_15_%255BDOCUMENTING%255D_living_documentation)
300
+ - Concept ADR : [ADRs](?doc=3_concept%252F2026_04_08_20_58_%255BDOCUMENTING%255D_ADRS)
301
+ - Méthodologie d'organisation : [Diataxis Architecture du Contenu](?doc=3_concept%252F2026_04_08_22_46_%255BMETHODOLOGY%255D_diataxis_architecture_du_contenu)
302
+ - Outil sous-jacent : [The Living Documentation Tool](?doc=4_reference%252F2026_04_08_23_14_%255BFUNDAMENTALS%255D_the_living_documentation_tool)
303
+ - Snippets disponibles : [Types De Snippets](?doc=4_reference%252F2026_04_09_03_00_%255BREFERENCE%255D_types_de_snippets)
304
+ - Diagrammes liés : [Creer Et Lier Un Diagramme](?doc=2_guide%252F2026_04_09_14_00_%255BDIAGRAM%255D_creer_et_lier_un_diagramme)
305
+
306
+ ---
307
+
308
+ ## Notes de présentation
309
+
310
+ > ⚠️ Avant la démo : **redémarrer le serveur living-documentation** pour partir d'un état propre, et **vider le cache Claude Code** (relancer la session) pour montrer une découverte du MCP en live.
311
+
312
+ > 💡 Si Internet est instable : préparer un fallback offline avec un projet `mon-test` déjà bootstrappé (Next.js installé, dépendances cachées) — la phase 1 risque le `npm install` qui peut prendre 90 s en wifi conférence.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "living-documentation",
3
- "version": "7.34.0",
3
+ "version": "7.36.0",
4
4
  "description": "A CLI tool that serves a local Markdown documentation viewer",
5
5
  "main": "dist/src/server.js",
6
6
  "bin": {