@sentropic/track 0.19.4 → 0.20.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.
@@ -0,0 +1,312 @@
1
+ // preconisation-actionnable (DESIGN LOCKED) — the DIRECTIVE selector. Replaces the constant
2
+ // `préconisation` (which took `leaves[0]` and emitted one frozen phrase per WP) with a DIRECTIVE
3
+ // DERIVED from each item's real state, langue-neutre, and DELEGABLE as-is to a subagent.
4
+ //
5
+ // PURE & ADDITIVE: every fact a directive needs is already on `WpLeaf` (enriched in rollup.ts via
6
+ // `acceptanceStatus` + `effectiveOpenBlockersForItem`) — the selector recomputes NOTHING. No new event,
7
+ // INGEST unchanged. The enums are a GOVERNED vocabulary (additive only, never renamed; an unknown
8
+ // `step.code` degrades to `inspect-fallback` at the renderer — forward-compat, see §3/§7 of the DESIGN).
9
+ // ---- record-only commandHint allowlist (DESIGN §5) -------------------------------------------------
10
+ // `commandHint` = verbs of MEASURE / ATTENTION / FOCUS only. A directive NEVER hints at fabricating an
11
+ // outcome (`item realize <id> done`, `accept … pass|waived`). `affordances` say what is LEGAL; the hint
12
+ // never presumes a write. A runtime guard fails closed so a future edit cannot leak a write/pass verb.
13
+ const SAFE_HINT = /^track (focus|accept run|blocker raise|priority assess|query|report)\b/;
14
+ const FORBIDDEN_HINT = /\b(realize|done|pass|waived)\b/;
15
+ export function assertSafeCommandHint(hint) {
16
+ if (hint === undefined)
17
+ return;
18
+ if (!SAFE_HINT.test(hint) || FORBIDDEN_HINT.test(hint)) {
19
+ throw new Error(`unsafe commandHint (record-only allowlist violated, DESIGN §5): ${hint}`);
20
+ }
21
+ }
22
+ // The LEGAL next writes per step (a curated affordance set — what the human/AI MAY submit, never a
23
+ // presumed write). Mirrors the monotone facade machines coarsely; the facade re-checks legality.
24
+ const AFFORDANCES = {
25
+ 'focus-decision': ['decision.outcome'],
26
+ 'settle-decision': ['decision.outcome'],
27
+ 'resume-engagement': ['blocker.resolve-external'],
28
+ 'resolve-external-blocker': ['blocker.resolve', 'blocker.resolve-external'],
29
+ 'amend-spec': ['item.spec', 'item.spec-amend'],
30
+ 'fix-acceptance': ['acceptance.run'],
31
+ 'rerun-acceptance': ['acceptance.run'],
32
+ 'finish-increment': ['item.realize'],
33
+ 'start-increment': ['item.realize'],
34
+ 'prioritize-backlog': ['priority.assess'],
35
+ 'inspect-fallback': [],
36
+ };
37
+ /**
38
+ * Presentation heuristic (NOT a directive field): a decision is "complex enough to warrant a focus
39
+ * session" when it has ≥3 open questions / ≥3 options / any dossier artifact, or simply lacks a
40
+ * recommendation. Drives `focus-decision` vs `settle-decision`. Shared with the renderer (format.ts).
41
+ */
42
+ export function decisionNeedsFocus(d) {
43
+ return ((d.openQuestionCount ?? 0) >= 3 ||
44
+ (d.optionCount ?? 0) >= 3 ||
45
+ (d.artifacts?.length ?? 0) > 0 ||
46
+ d.hasRecommendation !== true);
47
+ }
48
+ function gateCodeOfDep(b) {
49
+ if (b.scope === 'extra')
50
+ return 'external-dependency'; // (extra deps are engagement-routed; defensive)
51
+ if (b.resolutionRule === 'manual')
52
+ return 'manual-blocker';
53
+ return 'linked-dependency';
54
+ }
55
+ function tierOf(l) {
56
+ const depBlocker = l.openBlockers.find((b) => b.kind === 'dependency');
57
+ const blockerRefs = l.openBlockers.map((b) => b.blockerId);
58
+ // 1. real blocking gate (dependency/manual blocker open) on a NON-in-progress leaf — P1_GATE.
59
+ if (depBlocker !== undefined && l.realization !== 'in-progress') {
60
+ return { order: 10, rank: 'P1_GATE', step: 'resolve-external-blocker', gateCode: gateCodeOfDep(depBlocker), gateRef: depBlocker.blockerId, blockerRefs };
61
+ }
62
+ // 2. acceptance == 'fail' — P2_ACCEPTANCE (fail sub-rank, primes in-progress).
63
+ if (l.acceptance === 'fail') {
64
+ return { order: 20, rank: 'P2_ACCEPTANCE', step: 'fix-acceptance', gateCode: 'acceptance-failed', blockerRefs: [] };
65
+ }
66
+ // 3. in-progress + open dependency blocker (WIP coincé) — P3_IN_PROGRESS.
67
+ if (depBlocker !== undefined && l.realization === 'in-progress') {
68
+ return { order: 30, rank: 'P3_IN_PROGRESS', step: 'finish-increment', gateCode: gateCodeOfDep(depBlocker), gateRef: depBlocker.blockerId, blockerRefs };
69
+ }
70
+ // 4. in-progress (flux — finish before starting new).
71
+ if (l.realization === 'in-progress') {
72
+ return { order: 40, rank: 'P3_IN_PROGRESS', step: 'finish-increment', blockerRefs: [] };
73
+ }
74
+ // 5. acceptance == 'stale' — P2_ACCEPTANCE (stale sub-rank < fail; soft debt, after WIP). D1.
75
+ if (l.acceptance === 'stale') {
76
+ return { order: 50, rank: 'P2_ACCEPTANCE', step: 'rerun-acceptance', gateCode: 'acceptance-stale', blockerRefs: [] };
77
+ }
78
+ // 6. specStatus demands a spec — spec gate.
79
+ if (l.specStatus === 'to-specify') {
80
+ return { order: 60, rank: 'P1_GATE', step: 'amend-spec', gateCode: 'spec-not-ready', blockerRefs: [] };
81
+ }
82
+ // 7. to-do, ordered by WSJF desc (the value proxy lives in the global tie-break).
83
+ if (l.realization === 'to-do') {
84
+ return { order: 70, rank: 'P4_TODO_WSJF', step: 'start-increment', blockerRefs: [] };
85
+ }
86
+ // 8. fallback — NEVER id-only (the facts always carry bucket/realization/acceptance/specStatus).
87
+ return { order: 90, rank: 'P5_FALLBACK', step: 'inspect-fallback', blockerRefs: [] };
88
+ }
89
+ function factsOf(l, blockerRefs) {
90
+ return {
91
+ bucket: l.bucket,
92
+ realization: l.realization,
93
+ acceptance: l.acceptance,
94
+ specStatus: l.specStatus,
95
+ ...(l.priority !== undefined ? { wsjf: l.priority } : {}),
96
+ ...(l.accountable !== undefined ? { accountable: l.accountable } : {}),
97
+ ...(blockerRefs.length > 0 ? { blockerRefs } : {}),
98
+ };
99
+ }
100
+ /** Stable directive id (deterministic — no flicker): each (kind,target) yields at most one directive. */
101
+ function directiveId(target) {
102
+ return `${target.kind}:${target.id}`;
103
+ }
104
+ function makeDirective(d) {
105
+ assertSafeCommandHint(d.commandHint);
106
+ return {
107
+ id: directiveId(d.target),
108
+ affordances: d.affordances ?? AFFORDANCES[d.step.code],
109
+ ...d,
110
+ };
111
+ }
112
+ // In-WP urgency comparator: order asc, then WSJF desc (undefined LAST), then id (deterministic).
113
+ function leafCompare(a, b) {
114
+ if (a.order !== b.order)
115
+ return a.order - b.order;
116
+ const aw = a.l.priority;
117
+ const bw = b.l.priority;
118
+ if (aw !== bw) {
119
+ if (aw === undefined)
120
+ return 1;
121
+ if (bw === undefined)
122
+ return -1;
123
+ return bw - aw;
124
+ }
125
+ return a.l.id.localeCompare(b.l.id);
126
+ }
127
+ // Global directive comparator (DESIGN §2.B item 8): urgency `order`, then WSJF desc (undefined LAST),
128
+ // then wp.id, then target.id. STRICT determinism — two identical runs yield identical output.
129
+ function directiveCompare(a, b) {
130
+ if (a.order !== b.order)
131
+ return a.order - b.order;
132
+ const aw = a.directive.facts.wsjf;
133
+ const bw = b.directive.facts.wsjf;
134
+ if (aw !== bw) {
135
+ if (aw === undefined)
136
+ return 1;
137
+ if (bw === undefined)
138
+ return -1;
139
+ return bw - aw;
140
+ }
141
+ const aWp = a.directive.scope.wpId ?? '';
142
+ const bWp = b.directive.scope.wpId ?? '';
143
+ if (aWp !== bWp)
144
+ return aWp.localeCompare(bWp);
145
+ return a.directive.target.id.localeCompare(b.directive.target.id);
146
+ }
147
+ /** Is this leaf in the DELEGABLE scope: open, OR done-but-acceptance∈{fail,stale} (the invisible debt). */
148
+ function inDelegableScope(l) {
149
+ if (l.bucket === 'TO-DO' || l.bucket === 'AWAITED')
150
+ return true;
151
+ // A `done` leaf with fail/stale acceptance is the most precious deletable debt (invisible in DONE when
152
+ // requireAccepted=false). `unknown`/`waived`/`n-a` short-circuit (nothing to re-run / intentional waiver).
153
+ return l.bucket === 'DONE' && (l.acceptance === 'fail' || l.acceptance === 'stale');
154
+ }
155
+ /**
156
+ * Build the DIRECTIVE set over the WP forest + the decision rows (DESIGN §2/§3). Two ORTHOGONAL axes:
157
+ * ROUTAGE (mode, per leaf): open `decision` blocker ⇒ `human-decision` (ref=decisionId); an
158
+ * `engagementRef` on a non-DONE leaf ⇒ `h2a-engagement`; else ⇒ `subagent`.
159
+ * URGENCE (which leaf, on the delegable subset of each WP): the DESIGN §2.B ladder (`tierOf`).
160
+ *
161
+ * Each leaf is attributed to its OWNING node (`node.leaves` — `directLeaves` already stops at sub-WP
162
+ * boundaries) so a directive maps 1:1 to a leaf with no parent/child double-count. One WORK directive per
163
+ * WP node (its most-urgent delegable leaf), PLUS one directive per decision-wait / engagement-wait /
164
+ * pending decision. The returned array is GLOBALLY sorted (deterministic).
165
+ */
166
+ export function buildDirectives(tree, decisions = []) {
167
+ const decisionById = new Map(decisions.map((d) => [d.id, d]));
168
+ const flat = [];
169
+ const collect = (n) => {
170
+ flat.push(n);
171
+ for (const c of n.children)
172
+ collect(c);
173
+ };
174
+ for (const n of tree)
175
+ collect(n);
176
+ const entries = [];
177
+ const seenDecisionRef = new Set(); // decisionIds already surfaced via a decision-wait leaf
178
+ for (const node of flat) {
179
+ const scope = { wpId: node.id, wpLabel: node.label };
180
+ const work = [];
181
+ for (const l of node.leaves) {
182
+ if (!inDelegableScope(l))
183
+ continue;
184
+ // ROUTAGE — decision wait first, then engagement wait; both leave as their OWN line (not in URGENCE).
185
+ const decisionBlocker = l.openBlockers.find((b) => b.kind === 'decision');
186
+ if (decisionBlocker !== undefined) {
187
+ const decisionId = decisionBlocker.ref ?? '';
188
+ const d = decisionId !== '' ? decisionById.get(decisionId) : undefined;
189
+ const focus = d !== undefined ? decisionNeedsFocus(d) : false;
190
+ entries.push({
191
+ order: 0,
192
+ directive: makeDirective({
193
+ target: { kind: 'item', id: l.id, title: l.title, workspace: l.workspace },
194
+ scope,
195
+ mode: 'human-decision',
196
+ ...(decisionId !== '' ? { gate: { code: 'decision-pending', ref: decisionId } } : { gate: { code: 'decision-pending' } }),
197
+ step: { code: focus ? 'focus-decision' : 'settle-decision' },
198
+ rank: 'P1_GATE',
199
+ facts: factsOf(l, decisionId !== '' ? [decisionId] : []),
200
+ ...(decisionId !== '' ? { commandHint: `track focus ${decisionId}` } : {}),
201
+ }),
202
+ });
203
+ if (decisionId !== '')
204
+ seenDecisionRef.add(decisionId);
205
+ continue;
206
+ }
207
+ const extraEng = l.openBlockers.find((b) => b.engagementRef !== undefined);
208
+ const engRef = extraEng?.engagementRef ?? (l.bucket !== 'DONE' ? l.engagementRef : undefined);
209
+ if (engRef !== undefined) {
210
+ entries.push({
211
+ order: 1,
212
+ directive: makeDirective({
213
+ target: { kind: 'engagement', id: engRef, title: l.title, workspace: l.workspace },
214
+ scope,
215
+ mode: 'h2a-engagement',
216
+ gate: { code: 'engagement-pending', ref: engRef },
217
+ step: { code: 'resume-engagement' },
218
+ rank: 'P1_GATE',
219
+ facts: factsOf(l, extraEng !== undefined ? [extraEng.blockerId] : []),
220
+ }),
221
+ });
222
+ continue;
223
+ }
224
+ work.push(l);
225
+ }
226
+ if (work.length === 0)
227
+ continue;
228
+ // Priorité mix (c) (DESIGN §6): when the WP's whole delegable subset is "all to-do, no WSJF, no
229
+ // discriminant", the to-do/WSJF tier degenerates ⇒ the MISSING priority becomes a delegable action.
230
+ const allPlainTodoNoWsjf = work.every((l) => l.realization === 'to-do' &&
231
+ l.specStatus !== 'to-specify' &&
232
+ l.acceptance !== 'fail' &&
233
+ l.acceptance !== 'stale' &&
234
+ l.openBlockers.length === 0 &&
235
+ l.priority === undefined);
236
+ if (allPlainTodoNoWsjf) {
237
+ const rep = [...work].sort((a, b) => a.id.localeCompare(b.id))[0];
238
+ entries.push({
239
+ order: 70,
240
+ directive: makeDirective({
241
+ target: { kind: 'item', id: rep.id, title: rep.title, workspace: rep.workspace },
242
+ scope,
243
+ mode: 'subagent',
244
+ gate: { code: 'priority-missing' },
245
+ step: { code: 'prioritize-backlog' },
246
+ rank: 'P4_TODO_WSJF',
247
+ facts: factsOf(rep, []),
248
+ commandHint: `track priority assess ${rep.id}`,
249
+ }),
250
+ });
251
+ continue;
252
+ }
253
+ // Most-urgent delegable leaf of this WP (URGENCE ladder + in-WP tie-break).
254
+ const ranked = work.map((l) => ({ l, ...tierOf(l) }));
255
+ ranked.sort(leafCompare);
256
+ const top = ranked[0];
257
+ entries.push({
258
+ order: top.order,
259
+ directive: makeDirective({
260
+ target: { kind: 'item', id: top.l.id, title: top.l.title, workspace: top.l.workspace },
261
+ scope,
262
+ mode: 'subagent',
263
+ ...(top.gateCode !== undefined
264
+ ? { gate: { code: top.gateCode, ...(top.gateRef !== undefined ? { ref: top.gateRef } : {}) } }
265
+ : {}),
266
+ step: { code: top.step },
267
+ rank: top.rank,
268
+ facts: factsOf(top.l, top.blockerRefs),
269
+ }),
270
+ });
271
+ }
272
+ // Pending DecisionRows NOT already surfaced as a decision-wait (a decision blocking a leaf is covered by
273
+ // that leaf's line). A pending decision with no blocked leaf still needs its own human-decision line.
274
+ for (const d of decisions) {
275
+ if (d.outcome !== 'pending')
276
+ continue;
277
+ if (seenDecisionRef.has(d.id))
278
+ continue;
279
+ const focus = decisionNeedsFocus(d);
280
+ entries.push({
281
+ order: 0,
282
+ directive: makeDirective({
283
+ target: { kind: 'decision', id: d.id, title: d.title, workspace: d.workspace },
284
+ scope: {},
285
+ mode: 'human-decision',
286
+ gate: { code: 'decision-pending', ref: d.id },
287
+ step: { code: focus ? 'focus-decision' : 'settle-decision' },
288
+ rank: 'P1_GATE',
289
+ facts: {
290
+ bucket: 'AWAITED',
291
+ realization: d.realization,
292
+ acceptance: 'n/a',
293
+ specStatus: 'n/a',
294
+ ...(d.accountable !== undefined ? { accountable: d.accountable } : {}),
295
+ blockerRefs: [d.id],
296
+ },
297
+ commandHint: `track focus ${d.id}`,
298
+ }),
299
+ });
300
+ }
301
+ entries.sort(directiveCompare);
302
+ return entries.map((e) => e.directive);
303
+ }
304
+ /**
305
+ * The flat, prioritized SUBAGENT dispatch queue (DESIGN §4): the directive ids that are delegable to a
306
+ * subagent, in urgency order. `human-decision` / `h2a-engagement` directives are surfaced in `directives[]`
307
+ * but NOT here — they need a human / an h2a peer, not a subagent. Input is already globally sorted.
308
+ */
309
+ export function dispatchQueueOf(directives) {
310
+ return directives.filter((d) => d.mode === 'subagent' || d.mode === 'local').map((d) => d.id);
311
+ }
312
+ //# sourceMappingURL=directive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directive.js","sourceRoot":"","sources":["../../src/report/directive.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { DecisionRow, Report, ReportRow } from './build.js';
2
2
  import type { WpNode } from './rollup.js';
3
+ import { type Directive } from './directive.js';
3
4
  export type Format = 'json' | 'text' | 'md';
4
5
  export declare function formatReport(report: Report, format: Format): string;
5
6
  /**
@@ -55,7 +56,20 @@ export interface ReportView {
55
56
  locale: 'fr';
56
57
  tables: readonly ReportViewTable[];
57
58
  generalRecommendation: string;
59
+ /**
60
+ * preconisation-actionnable (DESIGN §4) — the langue-neutre DIRECTIVES the tables are DERIVED from
61
+ * (machine surface). `dispatchQueue` is the flat, prioritized list of SUBAGENT directive ids (the real
62
+ * delegation payload). Both are additive; the rendered tables + `generalRecommendation` stay back-compat.
63
+ */
64
+ directives: readonly Directive[];
65
+ dispatchQueue: readonly string[];
58
66
  }
67
+ /**
68
+ * Render ONE directive to a human FR phrase — RENDER-ONLY (no phrase is ever stored as data, DESIGN §3).
69
+ * Maps the langue-neutre `(mode, step, gate)` to a sentence. An UNKNOWN `step.code` (a future vocabulary
70
+ * entry this renderer predates) degrades to the `inspect-fallback` phrasing — forward-compat (DESIGN §7).
71
+ */
72
+ export declare function directivePhrase(d: Directive): string;
59
73
  export declare function buildWpConductorView(tree: readonly WpNode[], decisions?: readonly DecisionRow[]): ReportView;
60
74
  export declare function formatWpConductor(tree: readonly WpNode[], format: Format, decisions?: readonly DecisionRow[]): string;
61
75
  export declare function formatRows(rows: ReportRow[], format: Format): string;
@@ -1 +1 @@
1
- {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/report/format.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;AAoD3C,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAwBnE;AAsFD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA6CzE;AAaD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,GAAE,MAAa,GAAG,MAAM,CAenF;AAED,+GAA+G;AAC/G,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,CAiB1D;AAwBD;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACjD,IAAI,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAA;CACxC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,qBAAqB,CAAA;IAC3B,MAAM,EAAE,IAAI,CAAA;IACZ,MAAM,EAAE,SAAS,eAAe,EAAE,CAAA;IAClC,qBAAqB,EAAE,MAAM,CAAA;CAC9B;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,GAAE,SAAS,WAAW,EAAO,GAAG,UAAU,CAqEhH;AAgBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAE,SAAS,WAAW,EAAO,GAAG,MAAM,CAEzH;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAYpE"}
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/report/format.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAIL,KAAK,SAAS,EAEf,MAAM,gBAAgB,CAAA;AAEvB,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;AAoD3C,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAwBnE;AAkFD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA6CzE;AAaD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,GAAE,MAAa,GAAG,MAAM,CAenF;AAED,+GAA+G;AAC/G,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,CAiB1D;AAwBD;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACjD,IAAI,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAA;CACxC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,qBAAqB,CAAA;IAC3B,MAAM,EAAE,IAAI,CAAA;IACZ,MAAM,EAAE,SAAS,eAAe,EAAE,CAAA;IAClC,qBAAqB,EAAE,MAAM,CAAA;IAC7B;;;;OAIG;IACH,UAAU,EAAE,SAAS,SAAS,EAAE,CAAA;IAChC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;CACjC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CA2BpD;AAOD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,GAAE,SAAS,WAAW,EAAO,GAAG,UAAU,CAyDhH;AAoBD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAE,SAAS,WAAW,EAAO,GAAG,MAAM,CAEzH;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAYpE"}
@@ -1,4 +1,5 @@
1
1
  import { BUCKETS } from './buckets.js';
2
+ import { buildDirectives, decisionNeedsFocus, dispatchQueueOf, } from './directive.js';
2
3
  const BACKSLASH = String.fromCharCode(92);
3
4
  // Markdown metacharacters escaped in `md` titles so a user title can't inject formatting.
4
5
  const MD_META = new Set([
@@ -80,9 +81,6 @@ function actionDisposition(r) {
80
81
  return 'terminer ou expliciter blocage';
81
82
  return 'exécuter prochain incrément';
82
83
  }
83
- function decisionNeedsFocus(d) {
84
- return (d.openQuestionCount ?? 0) >= 3 || (d.optionCount ?? 0) >= 3 || (d.artifacts?.length ?? 0) > 0 || d.hasRecommendation !== true;
85
- }
86
84
  function decisionDisposition(d) {
87
85
  if (decisionNeedsFocus(d)) {
88
86
  return `focus décision HTML conseillé: track focus ${d.id} --format html`;
@@ -305,60 +303,82 @@ function droppedLeaves(node) {
305
303
  walk(node);
306
304
  return out;
307
305
  }
306
+ /**
307
+ * Render ONE directive to a human FR phrase — RENDER-ONLY (no phrase is ever stored as data, DESIGN §3).
308
+ * Maps the langue-neutre `(mode, step, gate)` to a sentence. An UNKNOWN `step.code` (a future vocabulary
309
+ * entry this renderer predates) degrades to the `inspect-fallback` phrasing — forward-compat (DESIGN §7).
310
+ */
311
+ export function directivePhrase(d) {
312
+ if (d.mode === 'human-decision') {
313
+ const who = d.facts.accountable ?? 'owner';
314
+ return d.step.code === 'focus-decision'
315
+ ? `décision (${who}): focus dossier (questions/options) puis trancher outcome`
316
+ : `décision (${who}): trancher outcome`;
317
+ }
318
+ if (d.mode === 'h2a-engagement') {
319
+ return 'engagement (h2a): relancer engagement puis intégrer le retour';
320
+ }
321
+ const mode = d.mode === 'local' ? 'local' : 'subagent';
322
+ const phrase = {
323
+ 'focus-decision': `action (${mode}): focus dossier puis trancher outcome`,
324
+ 'settle-decision': `action (${mode}): trancher outcome`,
325
+ 'resume-engagement': `action (${mode}): relancer engagement puis intégrer le retour`,
326
+ 'resolve-external-blocker': `action (${mode}): lever le blocage puis reprendre`,
327
+ 'amend-spec': `action (${mode}): spécifier avant de démarrer`,
328
+ 'fix-acceptance': `action (${mode}): corriger puis revalider acceptance (fail)`,
329
+ 'rerun-acceptance': `action (${mode}): relancer acceptance (stale) sur le commit courant`,
330
+ 'finish-increment': `action (${mode}): terminer l'incrément en cours`,
331
+ 'start-increment': `action (${mode}): continuer le premier item ouvert puis enregistrer preuve/acceptance`,
332
+ 'prioritize-backlog': `action (${mode}): prioriser le backlog (WSJF absent) puis reprendre`,
333
+ 'inspect-fallback': `action (${mode}): inspecter l'état puis décider la suite`,
334
+ };
335
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition — `step.code` may be an UNKNOWN
336
+ // future vocabulary entry (governed/additive enums): fall back rather than render `undefined`.
337
+ return phrase[d.step.code] ?? `action (${mode}): inspecter l'état puis décider la suite`;
338
+ }
339
+ /** The `scope/gate` cell for a directive row: its gate code if any, else the WP label, else `-`. */
340
+ function directiveScopeLabel(d) {
341
+ return d.gate?.code ?? d.scope.wpLabel ?? '-';
342
+ }
308
343
  export function buildWpConductorView(tree, decisions = []) {
309
344
  const wpName = (n) => `${n.label} · ${clean(stripWpPrefix(n.title))}`;
310
- const flat = [];
311
- const collect = (n) => {
312
- flat.push(n);
313
- for (const c of n.children)
314
- collect(c);
315
- };
316
- for (const n of tree)
317
- collect(n);
318
345
  const totals = wpTotals(tree);
319
- const pendingDecisions = decisions.filter((d) => d.outcome === 'pending');
320
- const complexDecisionCount = pendingDecisions.filter(decisionNeedsFocus).length;
321
- const generalRecommendation = complexDecisionCount >= 2 || pendingDecisions.length >= 4
346
+ // preconisation-actionnable (DESIGN §4): the DÉCISIONS/ACTIONS table + generalRecommendation are now
347
+ // DERIVED from the directive set (each directive ⇒ one row, phrase rendered, never stored). FAIT/À-FAIRE
348
+ // stay the same rollup-driven tables (unchanged back-compat).
349
+ const directives = buildDirectives(tree, decisions);
350
+ const dispatchQueue = dispatchQueueOf(directives);
351
+ const humanDecisions = directives.filter((d) => d.mode === 'human-decision');
352
+ const focusNeeded = humanDecisions.filter((d) => d.step.code === 'focus-decision').length;
353
+ const generalRecommendation = focusNeeded >= 2 || humanDecisions.length >= 4
322
354
  ? 'Prévoir un temps de focus HTML pour trancher les décisions accumulées, puis reprendre les WP par premier item ouvert.'
323
355
  : 'Avancer par premier item ouvert, enregistrer preuve/acceptance, et escalader uniquement les décisions réellement bloquantes.';
324
356
  const doneRows = [
325
357
  { scope: 'global', progress: `${totals.done}/${totals.active} (${pctStr(totals.pct)})`, lastActions: `${totals.done} items faits; poursuivre les WP ouverts` },
326
358
  ...tree.filter((n) => n.pct === 100).map((n) => ({ scope: wpName(n), progress: `${n.done}/${n.active} (100%)`, lastActions: 'WP clos; preuve/acceptance enregistrée' })),
327
359
  ];
328
- const todoRows = tree.filter((n) => n.pct !== 100).map((n) => ({
329
- wp: wpName(n),
330
- progress: `${n.done}/${n.active} (${pctStr(n.pct)})`,
331
- todo: openLeaves(n).slice(0, 2).map((l) => clean(l.title)).join(' / ') || 'aucun item ouvert direct',
332
- }));
333
- const attendus = flat.flatMap((n) => n.leaves
334
- .filter((l) => l.bucket === 'AWAITED' || (l.engagementRef !== undefined && l.bucket !== 'DONE' && l.bucket !== 'DROPPED'))
335
- .map((l) => ({ wp: n, leaf: l })));
336
- const decisionWaits = attendus.filter(({ leaf }) => leaf.awaitedOnDecision === true || leaf.engagementRef !== undefined);
360
+ const todoRows = tree.filter((n) => n.pct !== 100).map((n) => {
361
+ const open = openLeaves(n);
362
+ const shown = open.slice(0, 2).map((l) => clean(l.title)).join(' / ');
363
+ // NEVER truncate silently: surface the count of items not shown so the report can't read as complete.
364
+ const more = open.length > 2 ? `${shown} (+${open.length - 2} autres)` : shown;
365
+ return { wp: wpName(n), progress: `${n.done}/${n.active} (${pctStr(n.pct)})`, todo: more || 'aucun item ouvert direct' };
366
+ });
367
+ // DÉCISIONS/ACTIONS rows — derived from the directives. Decisions first (capped at 8 + an omission count
368
+ // so the table never reads as complete when it isn't), then engagement/work directives.
337
369
  const actionRows = [];
338
- if (decisionWaits.length >= 3 || pendingDecisions.length >= 4 || complexDecisionCount >= 2) {
370
+ if (focusNeeded >= 2 || humanDecisions.length >= 4) {
339
371
  actionRows.push({ scope: '-', subject: 'décisions accumulées', recommendation: 'focus (humain+MCP): lancer focus HTML local; retour outcome via vue interactive/MCP' });
340
372
  }
341
- for (const d of pendingDecisions.slice(0, 8)) {
342
- actionRows.push({ scope: d.decisionKind, subject: clean(d.title), recommendation: `décision (${d.accountable ?? 'owner'}): ${decisionDisposition(d)}` });
373
+ for (const d of humanDecisions.slice(0, 8)) {
374
+ actionRows.push({ scope: directiveScopeLabel(d), subject: clean(d.target.title ?? d.target.id), recommendation: directivePhrase(d) });
343
375
  }
344
- for (const { wp, leaf } of decisionWaits.slice(0, 8)) {
345
- const focus = leaf.awaitedOnDecision === true ? 'focus décision si dossier/questions non évidents; sinon trancher outcome' : 'relancer engagement/subagent puis intégrer retour';
346
- actionRows.push({ scope: wp.label, subject: clean(leaf.title), recommendation: `décision (owner/subagent): ${focus}` });
376
+ for (const d of directives.filter((x) => x.mode !== 'human-decision')) {
377
+ actionRows.push({ scope: directiveScopeLabel(d), subject: clean(d.target.title ?? d.target.id), recommendation: directivePhrase(d) });
347
378
  }
348
- const decisionWaitIds = new Set(decisionWaits.map(({ leaf }) => leaf.id));
349
- const candidates = flat
350
- .filter((n) => n.pct !== 100)
351
- .map((n) => ({ wp: n, leaves: openLeaves(n).filter((l) => !decisionWaitIds.has(l.id)) }))
352
- .filter((x) => x.leaves.length > 0);
353
- for (const { wp, leaves } of candidates) {
354
- const first = leaves[0];
355
- const mode = first.awaitedOnDecision === true ? 'human decision' : first.engagementRef !== undefined ? 'h2a/subagent' : 'local/subagent';
356
- const action = first.awaitedOnDecision === true
357
- ? 'trancher décision bloquante puis relancer WP'
358
- : first.engagementRef !== undefined
359
- ? 'relancer engagement/subagent et suivre retour'
360
- : 'continuer premier item ouvert puis enregistrer preuve/acceptance';
361
- actionRows.push({ scope: wpName(wp), subject: clean(first.title), recommendation: `action (${mode}): ${action}` });
379
+ const omitted = Math.max(0, humanDecisions.length - 8);
380
+ if (omitted > 0) {
381
+ actionRows.push({ scope: '-', subject: `+${omitted} entrées non listées`, recommendation: 'voir `track query` / `track report --flat` pour le détail complet' });
362
382
  }
363
383
  return {
364
384
  kind: 'wp-conductor-report',
@@ -369,20 +389,26 @@ export function buildWpConductorView(tree, decisions = []) {
369
389
  { id: 'decisions-actions', title: 'DÉCISIONS/ACTIONS', columns: [{ id: 'scope', label: 'scope/gate' }, { id: 'subject', label: 'sujet' }, { id: 'recommendation', label: 'préconisation' }], rows: actionRows.length > 0 ? actionRows : [{ scope: '-', subject: 'aucune action ouverte dans les WP actifs', recommendation: '-' }] },
370
390
  ],
371
391
  generalRecommendation,
392
+ directives,
393
+ dispatchQueue,
372
394
  };
373
395
  }
374
396
  function renderReportView(view, format) {
375
397
  if (format === 'json')
376
398
  return JSON.stringify(view, null, 2) + '\n';
399
+ // User-originated cell content (titles) is escaped per-format: `md` escapes markdown metacharacters so a
400
+ // crafted item title cannot inject formatting (parity with the legacy `formatReport`/`title` path); `text`
401
+ // is clean. The view model itself stays RAW (escaping is a render-only concern).
402
+ const esc = (s) => title(s, format);
377
403
  const h = (label) => (format === 'md' ? `## ${label}` : label);
378
404
  const lines = [];
379
405
  for (const section of view.tables) {
380
406
  lines.push(h(section.title));
381
- lines.push(...table(section.columns.map((c) => c.label), section.rows.map((row) => section.columns.map((c) => row[c.id] ?? ''))));
407
+ lines.push(...table(section.columns.map((c) => c.label), section.rows.map((row) => section.columns.map((c) => esc(row[c.id] ?? '')))));
382
408
  lines.push('');
383
409
  }
384
410
  lines.push(h('RECOMMANDATION'));
385
- lines.push(view.generalRecommendation);
411
+ lines.push(esc(view.generalRecommendation));
386
412
  return lines.join('\n').trimEnd() + '\n';
387
413
  }
388
414
  export function formatWpConductor(tree, format, decisions = []) {