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.
- package/dist/src/frontend/diagram/main.js +6 -0
- package/dist/src/frontend/diagram/network.js +152 -32
- package/dist/starting-doc/.diagrams.json +165 -0
- package/dist/starting-doc/.metadata.json +12 -1
- package/dist/starting-doc/5_talks/2026_04_28_09_48_[CONFERENCE]_demo_living_documentation_mcp_en_conference.md +312 -0
- package/dist/starting-doc/images/living_documentation_context_demo_conf.png +0 -0
- package/package.json +1 -1
|
@@ -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(() => {
|
|
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.
|
|
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
|
-
|
|
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
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
1314
|
-
|
|
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
|
+
[](/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.
|