adr-kit 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -7
- package/README.zh.md +18 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +18 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +4 -3
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/graph.d.ts +3 -0
- package/dist/commands/graph.d.ts.map +1 -1
- package/dist/commands/graph.js +39 -6
- package/dist/commands/graph.js.map +1 -1
- package/dist/commands/supersede.js +1 -1
- package/dist/commands/supersede.js.map +1 -1
- package/dist/commands/tree.d.ts +1 -1
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +4 -1
- package/dist/commands/tree.js.map +1 -1
- package/dist/core/adr.d.ts +1 -0
- package/dist/core/adr.d.ts.map +1 -1
- package/dist/core/adr.js.map +1 -1
- package/dist/core/canvas-view.d.ts +21 -0
- package/dist/core/canvas-view.d.ts.map +1 -0
- package/dist/core/canvas-view.js +316 -0
- package/dist/core/canvas-view.js.map +1 -0
- package/dist/core/deliberation-html.d.ts +4 -0
- package/dist/core/deliberation-html.d.ts.map +1 -0
- package/dist/core/deliberation-html.js +153 -0
- package/dist/core/deliberation-html.js.map +1 -0
- package/dist/core/deliberation-view.d.ts +4 -0
- package/dist/core/deliberation-view.d.ts.map +1 -0
- package/dist/core/deliberation-view.js +341 -0
- package/dist/core/deliberation-view.js.map +1 -0
- package/dist/core/deliberation.d.ts +39 -7
- package/dist/core/deliberation.d.ts.map +1 -1
- package/dist/core/deliberation.js +175 -35
- package/dist/core/deliberation.js.map +1 -1
- package/dist/core/graph-html.d.ts +8 -0
- package/dist/core/graph-html.d.ts.map +1 -0
- package/dist/core/graph-html.js +299 -0
- package/dist/core/graph-html.js.map +1 -0
- package/dist/core/graph-view.d.ts +3 -0
- package/dist/core/graph-view.d.ts.map +1 -0
- package/dist/core/graph-view.js +80 -0
- package/dist/core/graph-view.js.map +1 -0
- package/dist/core/graph.d.ts +4 -1
- package/dist/core/graph.d.ts.map +1 -1
- package/dist/core/graph.js +11 -2
- package/dist/core/graph.js.map +1 -1
- package/dist/core/relation-focus.d.ts +4 -0
- package/dist/core/relation-focus.d.ts.map +1 -0
- package/dist/core/relation-focus.js +89 -0
- package/dist/core/relation-focus.js.map +1 -0
- package/dist/core/repository.d.ts.map +1 -1
- package/dist/core/repository.js +6 -2
- package/dist/core/repository.js.map +1 -1
- package/dist/core/share.d.ts +30 -0
- package/dist/core/share.d.ts.map +1 -0
- package/dist/core/share.js +420 -0
- package/dist/core/share.js.map +1 -0
- package/dist/core/templates.d.ts +10 -3
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +12 -2
- package/dist/core/templates.js.map +1 -1
- package/dist/core/tool-integrations.d.ts.map +1 -1
- package/dist/core/tool-integrations.js +55 -5
- package/dist/core/tool-integrations.js.map +1 -1
- package/dist/core/validate.d.ts +1 -1
- package/dist/core/validate.d.ts.map +1 -1
- package/dist/core/validate.js +56 -15
- package/dist/core/validate.js.map +1 -1
- package/dist/core/view-text.d.ts +21 -0
- package/dist/core/view-text.d.ts.map +1 -0
- package/dist/core/view-text.js +92 -0
- package/dist/core/view-text.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/** Inline assets for the offline tree viewer. No network or runtime dependency. */
|
|
2
|
+
export const TREE_STYLE = String.raw `
|
|
3
|
+
|
|
4
|
+
:root {
|
|
5
|
+
font-family: Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
|
6
|
+
color: #233c36;
|
|
7
|
+
background: #f6f8f5;
|
|
8
|
+
font-synthesis: none
|
|
9
|
+
}
|
|
10
|
+
* {
|
|
11
|
+
box-sizing: border-box
|
|
12
|
+
}
|
|
13
|
+
body {
|
|
14
|
+
margin: 0
|
|
15
|
+
}
|
|
16
|
+
button {
|
|
17
|
+
font: inherit;
|
|
18
|
+
color: inherit
|
|
19
|
+
}
|
|
20
|
+
button {
|
|
21
|
+
cursor: pointer
|
|
22
|
+
}
|
|
23
|
+
button:focus-visible,summary:focus-visible,main:focus-visible {
|
|
24
|
+
outline: 3px solid #9775ce;
|
|
25
|
+
outline-offset: 4px
|
|
26
|
+
}
|
|
27
|
+
button {
|
|
28
|
+
border: 1px solid #dce4dd;
|
|
29
|
+
background: white;
|
|
30
|
+
border-radius: 8px;
|
|
31
|
+
padding: 9px 13px;
|
|
32
|
+
font-size: 12px
|
|
33
|
+
}
|
|
34
|
+
button:hover {
|
|
35
|
+
background: #edf3ef
|
|
36
|
+
}
|
|
37
|
+
main {
|
|
38
|
+
position: relative;
|
|
39
|
+
background-image: radial-gradient(#cdd8cc .8px,transparent .8px);
|
|
40
|
+
background-size: 20px 20px;
|
|
41
|
+
}
|
|
42
|
+
#world {
|
|
43
|
+
position: absolute;
|
|
44
|
+
transform-origin: 0 0
|
|
45
|
+
}
|
|
46
|
+
#edges {
|
|
47
|
+
position: absolute;
|
|
48
|
+
inset: 0;
|
|
49
|
+
overflow: visible;
|
|
50
|
+
pointer-events: none
|
|
51
|
+
}
|
|
52
|
+
.card {
|
|
53
|
+
width: 292px;
|
|
54
|
+
background: #fff;
|
|
55
|
+
border: 1px solid #dce4dc;
|
|
56
|
+
border-radius: 13px;
|
|
57
|
+
box-shadow: 0 3px 8px #25403506;
|
|
58
|
+
overflow: visible
|
|
59
|
+
}
|
|
60
|
+
.card:hover {
|
|
61
|
+
border-color: #a6bcae
|
|
62
|
+
}
|
|
63
|
+
.cardhead {
|
|
64
|
+
padding: 19px 20px 15px
|
|
65
|
+
}
|
|
66
|
+
.label {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
justify-content: space-between;
|
|
70
|
+
gap: 8px;
|
|
71
|
+
font-size: 10px;
|
|
72
|
+
color: #78877e;
|
|
73
|
+
letter-spacing: 1px;
|
|
74
|
+
margin-bottom: 12px
|
|
75
|
+
}
|
|
76
|
+
.state {
|
|
77
|
+
color: #387456;
|
|
78
|
+
letter-spacing: 0;
|
|
79
|
+
background: #edf6ef;
|
|
80
|
+
border-radius: 4px;
|
|
81
|
+
padding: 3px 6px
|
|
82
|
+
}
|
|
83
|
+
.card h2 {
|
|
84
|
+
font-size: 16px;
|
|
85
|
+
line-height: 1.45;
|
|
86
|
+
letter-spacing: -.25px;
|
|
87
|
+
margin: 0;
|
|
88
|
+
font-weight: 600
|
|
89
|
+
}
|
|
90
|
+
.answer {
|
|
91
|
+
margin: 0 13px 13px;
|
|
92
|
+
padding: 13px;
|
|
93
|
+
background: #f1f7f1;
|
|
94
|
+
color: #264c38;
|
|
95
|
+
border: 1px solid #e2ece1;
|
|
96
|
+
border-radius: 8px;
|
|
97
|
+
position: relative
|
|
98
|
+
}
|
|
99
|
+
.answer p {
|
|
100
|
+
font-size: 13px;
|
|
101
|
+
line-height: 1.5;
|
|
102
|
+
margin: 8px 0 0
|
|
103
|
+
}
|
|
104
|
+
.badge {
|
|
105
|
+
font-size: 10px;
|
|
106
|
+
color: #407455
|
|
107
|
+
}
|
|
108
|
+
.answer .reason {
|
|
109
|
+
font-size: 11px;
|
|
110
|
+
color: #748576;
|
|
111
|
+
line-height: 1.55
|
|
112
|
+
}
|
|
113
|
+
.override {
|
|
114
|
+
background: #f4effb;
|
|
115
|
+
color: #7750a5;
|
|
116
|
+
font-size: 10px;
|
|
117
|
+
padding: 4px 6px;
|
|
118
|
+
border-radius: 4px;
|
|
119
|
+
letter-spacing: 0
|
|
120
|
+
}
|
|
121
|
+
.alts {
|
|
122
|
+
border-top: 1px solid #e7ece6;
|
|
123
|
+
padding: 12px 20px;
|
|
124
|
+
color: #748078;
|
|
125
|
+
font-size: 11px
|
|
126
|
+
}
|
|
127
|
+
.alts summary {
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
list-style: none;
|
|
130
|
+
display: flex;
|
|
131
|
+
justify-content: space-between
|
|
132
|
+
}
|
|
133
|
+
.alts summary:after {
|
|
134
|
+
content: '+';
|
|
135
|
+
font-size: 15px
|
|
136
|
+
}
|
|
137
|
+
.alts[open] summary:after {
|
|
138
|
+
content: '−'
|
|
139
|
+
}
|
|
140
|
+
.alt {
|
|
141
|
+
padding-top: 15px;
|
|
142
|
+
margin-top: 12px;
|
|
143
|
+
border-top: 1px solid #eef0ed;
|
|
144
|
+
line-height: 1.55;
|
|
145
|
+
font-size: 12px;
|
|
146
|
+
color: #59645d
|
|
147
|
+
}
|
|
148
|
+
.alt p {
|
|
149
|
+
margin: 5px 0;
|
|
150
|
+
font-size: 11px;
|
|
151
|
+
color: #788279
|
|
152
|
+
}
|
|
153
|
+
.root {
|
|
154
|
+
background: #294d3e;
|
|
155
|
+
border-color: #294d3e;
|
|
156
|
+
color: #fff;
|
|
157
|
+
width: 245px
|
|
158
|
+
}
|
|
159
|
+
.root .cardhead {
|
|
160
|
+
padding: 25px
|
|
161
|
+
}
|
|
162
|
+
.root .label {
|
|
163
|
+
color: #bacdbc
|
|
164
|
+
}
|
|
165
|
+
.root h2 {
|
|
166
|
+
font-size: 21px;
|
|
167
|
+
font-weight: 500;
|
|
168
|
+
line-height: 1.4
|
|
169
|
+
}
|
|
170
|
+
.virtual {
|
|
171
|
+
width: 168px;
|
|
172
|
+
background: #fbfdfb;
|
|
173
|
+
border-style: dashed;
|
|
174
|
+
border-color: #b6c7ba;
|
|
175
|
+
}
|
|
176
|
+
.virtual .cardhead {
|
|
177
|
+
padding: 14px 16px 12px;
|
|
178
|
+
}
|
|
179
|
+
.branch {
|
|
180
|
+
position: absolute;
|
|
181
|
+
right: -13px;
|
|
182
|
+
bottom: 25px;
|
|
183
|
+
padding: 0;
|
|
184
|
+
width: 26px;
|
|
185
|
+
height: 26px;
|
|
186
|
+
border-radius: 50%;
|
|
187
|
+
background: white;
|
|
188
|
+
color: #5a7d64;
|
|
189
|
+
font-size: 15px;
|
|
190
|
+
box-shadow: 0 2px 5px #274b3910;
|
|
191
|
+
z-index: 2
|
|
192
|
+
}
|
|
193
|
+
.card { overflow-wrap: anywhere; }
|
|
194
|
+
.cardhead .reason { font-size: 12px; color: #67786c; line-height: 1.6; }
|
|
195
|
+
.root .reason { color: #bed0c2; }
|
|
196
|
+
.state.open { color: #805f0e; background: #fff5d6; }
|
|
197
|
+
.state.rejected { color: #96554c; background: #faeeeb; }
|
|
198
|
+
.state.unrecorded { color: #647369; background: #eff2ee; }
|
|
199
|
+
.root .state { color: #264c38; }
|
|
200
|
+
.root .alts { color: #bacdbc; border-top-color: #3f6455; }
|
|
201
|
+
.root .alt { color: #cfdcd1; border-top-color: #3f6455; }
|
|
202
|
+
.root .alt .badge { color: #a3bba9; }
|
|
203
|
+
.root .alt p { color: #b4c4b6; }
|
|
204
|
+
.override-key { color: #7750a5; }
|
|
205
|
+
/* The legend's state keys quote the card chips, so they match their size. */
|
|
206
|
+
.canvas-legend .legend .state { font-size: 10px; }
|
|
207
|
+
/* The edge keys are line samples, drawn in the stroke the view actually uses:
|
|
208
|
+
a key that looks like a line but not like the line it names teaches nothing. */
|
|
209
|
+
.edge-key { width: 22px; height: 0; border-top: 1.5px solid #b6c7ba; }
|
|
210
|
+
.edge-key.unlocked { border-top: 2px solid #7ca38a; }
|
|
211
|
+
.option { border-style: dashed; }
|
|
212
|
+
.card[hidden] { display: none; }
|
|
213
|
+
.card { position: relative; margin: 18px 24px 18px calc(24px + var(--depth) * 24px); }
|
|
214
|
+
#world { position: relative; }
|
|
215
|
+
main { height: auto; min-height: 0; overflow: auto; touch-action: auto; cursor: auto; }
|
|
216
|
+
#edges, .branch, .js-control { display: none; }
|
|
217
|
+
|
|
218
|
+
.interactive #world { position: absolute; }
|
|
219
|
+
.interactive .card { position: absolute; margin: 0; }
|
|
220
|
+
.interactive #edges, .interactive .branch, .interactive .js-control { display: block; }
|
|
221
|
+
.no-script { padding: 20px; }
|
|
222
|
+
`;
|
|
223
|
+
// Kept as readable JavaScript: tsc ships this string unchanged in the CLI bundle.
|
|
224
|
+
// All record text is server-escaped HTML, never interpolated into this program.
|
|
225
|
+
export const TREE_SCRIPT = String.raw `
|
|
226
|
+
(() => {
|
|
227
|
+
const canvas = window.__adrCanvas;
|
|
228
|
+
if (!canvas) return;
|
|
229
|
+
const { viewport, world } = canvas;
|
|
230
|
+
const svg = document.getElementById('edges');
|
|
231
|
+
const cards = Array.from(document.querySelectorAll('.card'));
|
|
232
|
+
if (!cards.length) return;
|
|
233
|
+
const items = cards.map(card => ({
|
|
234
|
+
card, id: card.id, parentId: card.dataset.parent,
|
|
235
|
+
anchor: document.getElementById(card.dataset.anchor),
|
|
236
|
+
children: [], x: 0, y: 0, width: 0, height: 0, span: 0
|
|
237
|
+
}));
|
|
238
|
+
const byId = new Map(items.map(item => [item.id, item]));
|
|
239
|
+
const roots = items.filter(item => !item.parentId);
|
|
240
|
+
for (const item of items) {
|
|
241
|
+
if (item.parentId) byId.get(item.parentId).children.push(item);
|
|
242
|
+
}
|
|
243
|
+
const collapsed = new Set();
|
|
244
|
+
|
|
245
|
+
function updateVisibility(item, hidden) {
|
|
246
|
+
item.card.hidden = hidden;
|
|
247
|
+
for (const child of item.children) {
|
|
248
|
+
const disclosure = child.anchor && child.anchor.closest('details');
|
|
249
|
+
updateVisibility(child, hidden || collapsed.has(item.id) || !!(disclosure && !disclosure.open));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function measure(item) {
|
|
254
|
+
item.width = item.card.offsetWidth;
|
|
255
|
+
item.height = item.card.offsetHeight;
|
|
256
|
+
const children = item.children.filter(child => !child.card.hidden);
|
|
257
|
+
const childrenHeight = children.reduce((sum, child) => sum + measure(child), 0);
|
|
258
|
+
item.span = Math.max(item.height, childrenHeight + Math.max(0, children.length - 1) * 34);
|
|
259
|
+
return item.span;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function place(item, x, y) {
|
|
263
|
+
item.x = x;
|
|
264
|
+
item.y = y + (item.span - item.height) / 2;
|
|
265
|
+
item.card.style.left = item.x + 'px';
|
|
266
|
+
item.card.style.top = item.y + 'px';
|
|
267
|
+
let nextY = y;
|
|
268
|
+
for (const child of item.children) {
|
|
269
|
+
if (child.card.hidden) continue;
|
|
270
|
+
place(child, x + item.width + 94, nextY);
|
|
271
|
+
nextY += child.span + 34;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function layout() {
|
|
276
|
+
roots.forEach(root => updateVisibility(root, false));
|
|
277
|
+
roots.forEach(measure);
|
|
278
|
+
let nextY = 28;
|
|
279
|
+
for (const root of roots) {
|
|
280
|
+
place(root, 28, nextY);
|
|
281
|
+
nextY += root.span + 36;
|
|
282
|
+
}
|
|
283
|
+
const visible = items.filter(item => !item.card.hidden);
|
|
284
|
+
const width = visible.reduce((max, item) => Math.max(max, item.x + item.width), 0) + 28;
|
|
285
|
+
const height = visible.reduce((max, item) => Math.max(max, item.y + item.height), 0) + 28;
|
|
286
|
+
world.style.width = width + 'px';
|
|
287
|
+
world.style.height = height + 'px';
|
|
288
|
+
svg.setAttribute('width', width);
|
|
289
|
+
svg.setAttribute('height', height);
|
|
290
|
+
svg.replaceChildren();
|
|
291
|
+
for (const item of visible) {
|
|
292
|
+
if (!item.parentId) continue;
|
|
293
|
+
const parent = byId.get(item.parentId);
|
|
294
|
+
const source = item.anchor || parent.card.querySelector('.cardhead');
|
|
295
|
+
const sourceRect = source.getBoundingClientRect();
|
|
296
|
+
const parentRect = parent.card.getBoundingClientRect();
|
|
297
|
+
const startY = parent.y + (sourceRect.top - parentRect.top + sourceRect.height / 2) / canvas.scale;
|
|
298
|
+
const startX = parent.x + parent.width;
|
|
299
|
+
const endX = item.x, endY = item.y + Math.min(65, item.height / 2);
|
|
300
|
+
const midpoint = (startX + endX) / 2;
|
|
301
|
+
// The server settled whether this edge is an unlocked one, so the styler
|
|
302
|
+
// and the legend cannot disagree about which edges are frontier steps.
|
|
303
|
+
const unlocked = item.card.dataset.unlocked === 'true';
|
|
304
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
305
|
+
path.setAttribute('d', 'M' + startX + ',' + startY + ' C' + midpoint + ',' + startY
|
|
306
|
+
+ ' ' + midpoint + ',' + endY + ' ' + endX + ',' + endY);
|
|
307
|
+
path.setAttribute('fill', 'none');
|
|
308
|
+
path.setAttribute('stroke', unlocked ? '#7ca38a' : '#b6c7ba');
|
|
309
|
+
path.setAttribute('stroke-width', unlocked ? '2' : '1.5');
|
|
310
|
+
path.dataset.from = item.anchor ? item.anchor.id : parent.id;
|
|
311
|
+
path.dataset.to = item.id;
|
|
312
|
+
path.dataset.unlocked = String(unlocked);
|
|
313
|
+
svg.append(path);
|
|
314
|
+
}
|
|
315
|
+
canvas.paint();
|
|
316
|
+
world.dispatchEvent(new Event('adr-layout'));
|
|
317
|
+
}
|
|
318
|
+
window.__adrCanvasLayout = layout;
|
|
319
|
+
|
|
320
|
+
for (const item of items) {
|
|
321
|
+
const button = item.card.querySelector('.branch');
|
|
322
|
+
if (!button) continue;
|
|
323
|
+
button.addEventListener('click', () => {
|
|
324
|
+
if (collapsed.has(item.id)) collapsed.delete(item.id);
|
|
325
|
+
else collapsed.add(item.id);
|
|
326
|
+
button.textContent = collapsed.has(item.id) ? '+' : '−';
|
|
327
|
+
button.setAttribute('aria-expanded', String(!collapsed.has(item.id)));
|
|
328
|
+
const before = item.card.getBoundingClientRect();
|
|
329
|
+
layout();
|
|
330
|
+
const after = item.card.getBoundingClientRect();
|
|
331
|
+
canvas.addPan(before.left - after.left, before.top - after.top);
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
for (const detail of document.querySelectorAll('.alts')) detail.addEventListener('toggle', () => canvas.schedule());
|
|
335
|
+
|
|
336
|
+
layout();
|
|
337
|
+
canvas.fitWidth();
|
|
338
|
+
})();
|
|
339
|
+
|
|
340
|
+
`;
|
|
341
|
+
//# sourceMappingURL=deliberation-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliberation-view.js","sourceRoot":"","sources":["../../src/core/deliberation-view.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4NnC,CAAC;AAEF,kFAAkF;AAClF,gFAAgF;AAChF,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmHpC,CAAC"}
|
|
@@ -1,21 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The optional
|
|
2
|
+
* The optional ## Deliberation appendix: a grilling session's design tree,
|
|
3
3
|
* stored as a nested Markdown list so it stays diffable and readable, and
|
|
4
|
-
* rendered to mermaid or
|
|
4
|
+
* rendered to text, mermaid, or a single HTML document on demand. The stored
|
|
5
|
+
* outline is the source of truth; every renderer is a view.
|
|
6
|
+
*
|
|
7
|
+
* Grammar (ADR 5):
|
|
8
|
+
*
|
|
9
|
+
* - [Q: | A: ]<text>[ [status]][ (recommended)][ em-dash <reason>]
|
|
10
|
+
*
|
|
11
|
+
* Q:/A: marks a question or an option (inferred when omitted: a node with
|
|
12
|
+
* children is a question, a leaf is an option, the top level is the root).
|
|
13
|
+
* [settled], [rejected], [open] is the state. (recommended) marks the option
|
|
14
|
+
* the agent recommended. The em-dash reason explains the node.
|
|
15
|
+
*
|
|
16
|
+
* Dependency is nesting (ADR 7): a follow-up question is a child of the node
|
|
17
|
+
* whose settlement raised it, so related questions run deeper and unrelated
|
|
18
|
+
* ones stay flat. Nothing stores a round; depth is the frontier.
|
|
5
19
|
*/
|
|
6
20
|
export type DeliberationStatus = 'settled' | 'rejected' | 'open';
|
|
21
|
+
export type DeliberationType = 'question' | 'option';
|
|
7
22
|
export interface DeliberationNode {
|
|
8
23
|
text: string;
|
|
24
|
+
type?: DeliberationType;
|
|
9
25
|
status?: DeliberationStatus;
|
|
26
|
+
recommended?: boolean;
|
|
27
|
+
reason?: string;
|
|
10
28
|
children: DeliberationNode[];
|
|
11
29
|
}
|
|
30
|
+
export declare function parseDeliberation(body: string | undefined): DeliberationNode[];
|
|
12
31
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Non-bullet lines are commentary and ignored, so a session can interleave
|
|
16
|
-
* prose without breaking the tree.
|
|
32
|
+
* True when the body parses to at least one node: the same predicate `tree`
|
|
33
|
+
* renders by, so a marker can never promise a tree the command refuses.
|
|
17
34
|
*/
|
|
18
|
-
export declare function
|
|
35
|
+
export declare function hasDeliberationTree(body: string | undefined): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* A question whose answer (its settled child) is not the option the agent
|
|
38
|
+
* recommended. Unknown unless both a settled child and a recommended child
|
|
39
|
+
* exist.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Whether the node that raised a follow-up question unlocked it. In the outline
|
|
43
|
+
* a question nests under the node it depends on, so that parent is the node
|
|
44
|
+
* that raised it: a settled parent that is not the tree root turns the edge
|
|
45
|
+
* into frontier progress. The card tree, the drawn edges, and Mermaid all ask
|
|
46
|
+
* this one question rather than each deciding for itself.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isUnlockedQuestion(child: DeliberationNode, parent: DeliberationNode, parentIsRoot: boolean): boolean;
|
|
19
49
|
export declare function renderDeliberationText(nodes: DeliberationNode[]): string;
|
|
20
50
|
export declare function renderDeliberationMermaid(nodes: DeliberationNode[]): string;
|
|
51
|
+
/** An offline HTML view; the annotated outline remains the source. */
|
|
52
|
+
export declare function renderDeliberationHtml(nodes: DeliberationNode[], title: string): string;
|
|
21
53
|
//# sourceMappingURL=deliberation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deliberation.d.ts","sourceRoot":"","sources":["../../src/core/deliberation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deliberation.d.ts","sourceRoot":"","sources":["../../src/core/deliberation.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AACjE,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,QAAQ,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AA4DD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,EAAE,CAsB9E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAErE;AAQD;;;;GAIG;AACH;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,gBAAgB,EACxB,YAAY,EAAE,OAAO,GACpB,OAAO,CAET;AAkBD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAYxE;AAkBD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,MAAM,CA8D3E;AAED,sEAAsE;AACtE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvF"}
|
|
@@ -1,16 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* stored as a nested Markdown list so it stays diffable and readable, and
|
|
4
|
-
* rendered to mermaid or text on demand. Mermaid is never the source of truth.
|
|
5
|
-
*/
|
|
1
|
+
import { renderCardTree } from './deliberation-html.js';
|
|
2
|
+
import { wrapText } from './view-text.js';
|
|
6
3
|
const BULLET = /^(\s*)-\s+(.*?)\s*$/;
|
|
7
|
-
const STATUS = /\
|
|
4
|
+
const STATUS = /\[(settled|rejected|open)\]/i;
|
|
5
|
+
const RECOMMENDED = /\(recommended\)\s*$/i;
|
|
6
|
+
const TYPE = /^([QA]):\s+/i;
|
|
7
|
+
const EM_DASH = ' \u2014 ';
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Split a trailing ` — reason` off the node text. The em dash with spaces is
|
|
10
|
+
* the only separator the grammar reserves, which leaves a hyphen free for
|
|
11
|
+
* prose: a ` - ` inside the text can never cut the state or the recommendation
|
|
12
|
+
* into the reason.
|
|
13
13
|
*/
|
|
14
|
+
function splitReason(raw) {
|
|
15
|
+
const emDash = raw.lastIndexOf(EM_DASH);
|
|
16
|
+
if (emDash === -1)
|
|
17
|
+
return { content: raw };
|
|
18
|
+
return { content: raw.slice(0, emDash).trimEnd(), reason: raw.slice(emDash + EM_DASH.length).trim() };
|
|
19
|
+
}
|
|
20
|
+
/** Parse one bullet from the end inward, so a reason never hides the status. */
|
|
21
|
+
function parseNodeText(raw) {
|
|
22
|
+
const split = splitReason(raw);
|
|
23
|
+
let content = split.content;
|
|
24
|
+
const reason = split.reason;
|
|
25
|
+
let recommended;
|
|
26
|
+
if (RECOMMENDED.test(content)) {
|
|
27
|
+
recommended = true;
|
|
28
|
+
content = content.replace(RECOMMENDED, '').trimEnd();
|
|
29
|
+
}
|
|
30
|
+
let status;
|
|
31
|
+
const statusMatch = content.match(STATUS);
|
|
32
|
+
if (statusMatch !== null && statusMatch.index !== undefined) {
|
|
33
|
+
status = statusMatch[1].toLowerCase();
|
|
34
|
+
content = (content.slice(0, statusMatch.index) + content.slice(statusMatch.index + statusMatch[0].length)).trim();
|
|
35
|
+
}
|
|
36
|
+
let type;
|
|
37
|
+
const typeMatch = content.match(TYPE);
|
|
38
|
+
if (typeMatch !== null) {
|
|
39
|
+
type = typeMatch[1].toUpperCase() === 'Q' ? 'question' : 'option';
|
|
40
|
+
content = content.slice(typeMatch[0].length).trim();
|
|
41
|
+
}
|
|
42
|
+
const text = content.trim();
|
|
43
|
+
if (text.length === 0)
|
|
44
|
+
return undefined;
|
|
45
|
+
const node = { text };
|
|
46
|
+
if (type !== undefined)
|
|
47
|
+
node.type = type;
|
|
48
|
+
if (status !== undefined)
|
|
49
|
+
node.status = status;
|
|
50
|
+
if (recommended !== undefined)
|
|
51
|
+
node.recommended = recommended;
|
|
52
|
+
if (reason !== undefined && reason.length > 0)
|
|
53
|
+
node.reason = reason;
|
|
54
|
+
return node;
|
|
55
|
+
}
|
|
14
56
|
export function parseDeliberation(body) {
|
|
15
57
|
if (body === undefined)
|
|
16
58
|
return [];
|
|
@@ -21,18 +63,18 @@ export function parseDeliberation(body) {
|
|
|
21
63
|
if (match === null)
|
|
22
64
|
continue;
|
|
23
65
|
const indent = match[1].replace(/\t/g, ' ').length;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const statusMatch = text.match(STATUS);
|
|
27
|
-
if (statusMatch !== null && statusMatch.index !== undefined) {
|
|
28
|
-
status = statusMatch[1].toLowerCase();
|
|
29
|
-
text = text.slice(0, statusMatch.index).trim();
|
|
30
|
-
}
|
|
31
|
-
if (text.length === 0)
|
|
66
|
+
const parsed = parseNodeText(match[2]);
|
|
67
|
+
if (parsed === undefined)
|
|
32
68
|
continue;
|
|
33
|
-
const node = { text, children: [] };
|
|
34
|
-
if (
|
|
35
|
-
node.
|
|
69
|
+
const node = { text: parsed.text, children: [] };
|
|
70
|
+
if (parsed.type !== undefined)
|
|
71
|
+
node.type = parsed.type;
|
|
72
|
+
if (parsed.status !== undefined)
|
|
73
|
+
node.status = parsed.status;
|
|
74
|
+
if (parsed.recommended !== undefined)
|
|
75
|
+
node.recommended = parsed.recommended;
|
|
76
|
+
if (parsed.reason !== undefined)
|
|
77
|
+
node.reason = parsed.reason;
|
|
36
78
|
while (stack.length > 0 && stack[stack.length - 1].indent >= indent)
|
|
37
79
|
stack.pop();
|
|
38
80
|
const parent = stack[stack.length - 1];
|
|
@@ -44,39 +86,137 @@ export function parseDeliberation(body) {
|
|
|
44
86
|
}
|
|
45
87
|
return roots;
|
|
46
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* True when the body parses to at least one node: the same predicate `tree`
|
|
91
|
+
* renders by, so a marker can never promise a tree the command refuses.
|
|
92
|
+
*/
|
|
93
|
+
export function hasDeliberationTree(body) {
|
|
94
|
+
return parseDeliberation(body).length > 0;
|
|
95
|
+
}
|
|
96
|
+
/** Explicit Q:/A:, else children decide. */
|
|
97
|
+
function typeOf(node) {
|
|
98
|
+
if (node.type !== undefined)
|
|
99
|
+
return node.type;
|
|
100
|
+
return node.children.length > 0 ? 'question' : 'option';
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A question whose answer (its settled child) is not the option the agent
|
|
104
|
+
* recommended. Unknown unless both a settled child and a recommended child
|
|
105
|
+
* exist.
|
|
106
|
+
*/
|
|
107
|
+
/**
|
|
108
|
+
* Whether the node that raised a follow-up question unlocked it. In the outline
|
|
109
|
+
* a question nests under the node it depends on, so that parent is the node
|
|
110
|
+
* that raised it: a settled parent that is not the tree root turns the edge
|
|
111
|
+
* into frontier progress. The card tree, the drawn edges, and Mermaid all ask
|
|
112
|
+
* this one question rather than each deciding for itself.
|
|
113
|
+
*/
|
|
114
|
+
export function isUnlockedQuestion(child, parent, parentIsRoot) {
|
|
115
|
+
return typeOf(child) === 'question' && !parentIsRoot && parent.status === 'settled';
|
|
116
|
+
}
|
|
117
|
+
function isOverride(node) {
|
|
118
|
+
const settled = node.children.filter((child) => child.status === 'settled');
|
|
119
|
+
const recommended = node.children.filter((child) => child.recommended === true);
|
|
120
|
+
if (settled.length === 0 || recommended.length === 0)
|
|
121
|
+
return false;
|
|
122
|
+
return !settled.some((child) => child.recommended === true);
|
|
123
|
+
}
|
|
124
|
+
function markers(node) {
|
|
125
|
+
const parts = [];
|
|
126
|
+
if (node.status !== undefined)
|
|
127
|
+
parts.push('[' + node.status + ']');
|
|
128
|
+
if (node.recommended === true)
|
|
129
|
+
parts.push('(recommended)');
|
|
130
|
+
if (isOverride(node))
|
|
131
|
+
parts.push('(override)');
|
|
132
|
+
if (node.reason !== undefined)
|
|
133
|
+
parts.push('\u2014 ' + node.reason);
|
|
134
|
+
return parts.join(' ');
|
|
135
|
+
}
|
|
47
136
|
export function renderDeliberationText(nodes) {
|
|
48
137
|
const lines = [];
|
|
49
138
|
const walk = (list, depth) => {
|
|
50
139
|
for (const node of list) {
|
|
51
|
-
const
|
|
52
|
-
|
|
140
|
+
const prefix = depth === 0 && node.type === undefined ? '' : (typeOf(node) === 'question' ? 'Q: ' : 'A: ');
|
|
141
|
+
const suffix = markers(node);
|
|
142
|
+
lines.push(' '.repeat(depth) + '- ' + prefix + node.text + (suffix.length > 0 ? ' ' + suffix : ''));
|
|
53
143
|
walk(node.children, depth + 1);
|
|
54
144
|
}
|
|
55
145
|
};
|
|
56
146
|
walk(nodes, 0);
|
|
57
147
|
return lines.join('\n');
|
|
58
148
|
}
|
|
149
|
+
function escapeLabel(text) {
|
|
150
|
+
return text.replace(/"/g, "'");
|
|
151
|
+
}
|
|
152
|
+
/** Wrap a long label for mermaid's HTML labels. */
|
|
153
|
+
function wrapLabel(text, width = 48) {
|
|
154
|
+
return wrapText(text, width).join('<br/>');
|
|
155
|
+
}
|
|
59
156
|
export function renderDeliberationMermaid(nodes) {
|
|
60
|
-
const
|
|
61
|
-
const byStatus = { settled: [], rejected: [], open: [] };
|
|
157
|
+
const entries = [];
|
|
62
158
|
let counter = 0;
|
|
63
|
-
const
|
|
159
|
+
const collect = (list, parentId, depth) => {
|
|
64
160
|
for (const node of list) {
|
|
65
|
-
const id =
|
|
66
|
-
|
|
161
|
+
const id = 'n' + (++counter);
|
|
162
|
+
const entry = { id, node, isRoot: depth === 0 && node.type === undefined };
|
|
67
163
|
if (parentId !== undefined)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
walk(node.children, id);
|
|
164
|
+
entry.parentId = parentId;
|
|
165
|
+
entries.push(entry);
|
|
166
|
+
collect(node.children, id, depth + 1);
|
|
72
167
|
}
|
|
73
168
|
};
|
|
74
|
-
|
|
75
|
-
lines
|
|
169
|
+
collect(nodes, undefined, 0);
|
|
170
|
+
const lines = ['graph TD'];
|
|
171
|
+
const labelOf = (node) => wrapLabel(escapeLabel(node.reason === undefined ? node.text : node.text + ' \u2014 ' + node.reason));
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
const label = labelOf(entry.node);
|
|
174
|
+
const shape = entry.isRoot
|
|
175
|
+
? '(["' + label + '"])'
|
|
176
|
+
: (typeOf(entry.node) === 'question' ? '{{"' + label + '"}}' : '["' + label + '"]');
|
|
177
|
+
lines.push(' ' + entry.id + shape);
|
|
178
|
+
}
|
|
179
|
+
// A settled non-root node unlocks its follow-up questions. Unsettled
|
|
180
|
+
// branches retain ordinary edges rather than claiming frontier progress.
|
|
181
|
+
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
182
|
+
const unlockEdges = [];
|
|
183
|
+
let edgeIndex = 0;
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
if (entry.parentId === undefined)
|
|
186
|
+
continue;
|
|
187
|
+
const parent = byId.get(entry.parentId);
|
|
188
|
+
lines.push(' ' + entry.parentId + ' --> ' + entry.id);
|
|
189
|
+
if (isUnlockedQuestion(entry.node, parent.node, parent.isRoot)) {
|
|
190
|
+
unlockEdges.push(edgeIndex);
|
|
191
|
+
}
|
|
192
|
+
edgeIndex++;
|
|
193
|
+
}
|
|
194
|
+
const byStatus = { settled: [], rejected: [], open: [] };
|
|
195
|
+
const recommended = [];
|
|
196
|
+
const overridden = [];
|
|
197
|
+
for (const entry of entries) {
|
|
198
|
+
if (entry.node.status !== undefined)
|
|
199
|
+
byStatus[entry.node.status].push(entry.id);
|
|
200
|
+
if (entry.node.recommended === true)
|
|
201
|
+
recommended.push(entry.id);
|
|
202
|
+
if (isOverride(entry.node))
|
|
203
|
+
overridden.push(entry.id);
|
|
204
|
+
}
|
|
205
|
+
lines.push(' classDef settled fill:#dcfce7,stroke:#16a34a;', ' classDef rejected fill:#fee2e2,stroke:#dc2626;', ' classDef open fill:#fef9c3,stroke:#ca8a04;', ' classDef recommended stroke-width:3px;', ' classDef override stroke:#7c3aed,stroke-width:2px,stroke-dasharray:4 2;');
|
|
76
206
|
for (const status of ['settled', 'rejected', 'open']) {
|
|
77
207
|
if (byStatus[status].length > 0)
|
|
78
|
-
lines.push(
|
|
208
|
+
lines.push(' class ' + byStatus[status].join(',') + ' ' + status + ';');
|
|
79
209
|
}
|
|
210
|
+
if (recommended.length > 0)
|
|
211
|
+
lines.push(' class ' + recommended.join(',') + ' recommended;');
|
|
212
|
+
if (overridden.length > 0)
|
|
213
|
+
lines.push(' class ' + overridden.join(',') + ' override;');
|
|
214
|
+
if (unlockEdges.length > 0)
|
|
215
|
+
lines.push(' linkStyle ' + unlockEdges.join(',') + ' stroke:#7c3aed,stroke-width:3px;');
|
|
80
216
|
return lines.join('\n');
|
|
81
217
|
}
|
|
218
|
+
/** An offline HTML view; the annotated outline remains the source. */
|
|
219
|
+
export function renderDeliberationHtml(nodes, title) {
|
|
220
|
+
return renderCardTree(nodes, title);
|
|
221
|
+
}
|
|
82
222
|
//# sourceMappingURL=deliberation.js.map
|