@quolu/lattice 0.68.1 → 0.68.2
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/package.json +1 -1
- package/src/todo-cli.mjs +2 -6
- package/src/todo-gantt-layout.mjs +19 -30
- package/src/todo-gantt-nested.mjs +1 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.68.
|
|
3
|
+
"version": "0.68.2",
|
|
4
4
|
"description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Quo / クオ at kitepon.dev",
|
package/src/todo-cli.mjs
CHANGED
|
@@ -32,7 +32,6 @@ import {
|
|
|
32
32
|
todoSelfDigest,
|
|
33
33
|
validateEvidenceDescriptor,
|
|
34
34
|
} from './todo-contracts.mjs';
|
|
35
|
-
import { projectTodoChainV1 } from './todo-chain.mjs';
|
|
36
35
|
import {
|
|
37
36
|
adoptTodoDashboardActivity,
|
|
38
37
|
ensureTodoDashboardActivity,
|
|
@@ -3381,19 +3380,17 @@ export async function renderTodoGanttForProject({
|
|
|
3381
3380
|
? await resolveProjectIdentity({ repoRoot, projectId: store.project_id, env })
|
|
3382
3381
|
: { displayName };
|
|
3383
3382
|
const presentation = await loadTodoGanttPresentation({ repoRoot, readModel: store });
|
|
3384
|
-
const topology = mergedTopology(store);
|
|
3385
|
-
const chain = projectTodoChainV1(topology);
|
|
3386
3383
|
const [independence, seamProposals, notes, structures] = await Promise.all([
|
|
3387
3384
|
independenceForGantt({ repoRoot, store }),
|
|
3388
3385
|
seamProposalsForGantt({ repoRoot, store }),
|
|
3389
3386
|
notesForGantt({ repoRoot, store }),
|
|
3390
3387
|
loadTodoStructurePresentation({ repoRoot, readModel: store }),
|
|
3391
3388
|
]);
|
|
3392
|
-
const layout = layoutTodoGantt(store,
|
|
3389
|
+
const layout = layoutTodoGantt(store, { scope, independence, seamProposals });
|
|
3393
3390
|
// When the diagram hides history, the page also carries the full diagram so
|
|
3394
3391
|
// the reader can bring it back in place. Nothing is hidden under `all`.
|
|
3395
3392
|
const expandedLayout = layout.scope.folded_task_count === 0
|
|
3396
|
-
? null : layoutTodoGantt(store,
|
|
3393
|
+
? null : layoutTodoGantt(store, {
|
|
3397
3394
|
scope: 'all', independence, seamProposals,
|
|
3398
3395
|
});
|
|
3399
3396
|
const narrative = await loadNarratives(store, repoRoot);
|
|
@@ -3419,7 +3416,6 @@ export async function renderTodoGanttForProject({
|
|
|
3419
3416
|
member_bindings: memberBindings,
|
|
3420
3417
|
narrative_bindings_digest: digestTodoArtifact(narrativeBindings),
|
|
3421
3418
|
presentation_digest: presentation.presentation_digest,
|
|
3422
|
-
chain_digest: digestTodoArtifact(chain),
|
|
3423
3419
|
layout_digest: digestTodoArtifact(layout),
|
|
3424
3420
|
note_bindings_digest: digestTodoArtifact(notes.headBindings),
|
|
3425
3421
|
structure_projection_digest: structures.projection_digest,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TODO_GANTT_SCOPES, projectTodoGanttScope } from './todo-gantt-scope.mjs';
|
|
2
2
|
import { buildTodoGanttHierarchy } from './todo-gantt-nested.mjs';
|
|
3
3
|
import { projectTodoCrossPlanDependencies } from './todo-store.mjs';
|
|
4
|
+
import { projectTodoChainV1 } from './todo-chain.mjs';
|
|
4
5
|
|
|
5
6
|
const TASK_LIMIT = 2_000;
|
|
6
7
|
const EDGE_LIMIT = 8_000;
|
|
@@ -128,16 +129,11 @@ function groupKey(planKey, lane) {
|
|
|
128
129
|
return JSON.stringify([planKey, lane]);
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
function normalizeInput(readModel
|
|
132
|
+
function normalizeInput(readModel) {
|
|
132
133
|
if (!plain(readModel) || readModel.schema !== 'lattice.todo_store_read.v1'
|
|
133
134
|
|| !Array.isArray(readModel.members)) {
|
|
134
135
|
fail('TODO_LAYOUT_INVALID_INPUT', 'read model must be lattice.todo_store_read.v1');
|
|
135
136
|
}
|
|
136
|
-
if (!plain(chainProjection) || chainProjection.schema !== 'lattice.todo_chain.v1'
|
|
137
|
-
|| !Array.isArray(chainProjection.longest_chain_node_refs)
|
|
138
|
-
|| !Array.isArray(chainProjection.longest_chain_edges)) {
|
|
139
|
-
fail('TODO_LAYOUT_INVALID_INPUT', 'chain projection must be lattice.todo_chain.v1');
|
|
140
|
-
}
|
|
141
137
|
|
|
142
138
|
const nodesByKey = new Map();
|
|
143
139
|
const retiredKeys = new Set(); // 撤去済みtask。端点に持つ辺は図から一緒に消す
|
|
@@ -248,10 +244,10 @@ function normalizeInput(readModel, chainProjection) {
|
|
|
248
244
|
|
|
249
245
|
const nodes = [...nodesByKey.values()].sort((left, right) => compareRefs(left.ref, right.ref));
|
|
250
246
|
const edges = [...edgeMap.values()].sort((left, right) => compareText(left.key, right.key));
|
|
251
|
-
return { nodes, nodesByKey, edges };
|
|
247
|
+
return { nodes, nodesByKey, edges, retiredKeys };
|
|
252
248
|
}
|
|
253
249
|
|
|
254
|
-
function readyTaskKeys(readModel, nodes, nodesByKey, incoming) {
|
|
250
|
+
function readyTaskKeys(readModel, nodes, nodesByKey, incoming, retiredKeys) {
|
|
255
251
|
const phaseStatuses = new Map();
|
|
256
252
|
const phaseAcceptIncoming = new Map(nodes.map(({ key }) => [key, new Set()]));
|
|
257
253
|
for (const member of readModel.members) {
|
|
@@ -264,6 +260,7 @@ function readyTaskKeys(readModel, nodes, nodesByKey, incoming) {
|
|
|
264
260
|
if (['lattice.todo_plan.v5', 'lattice.todo_plan.v7'].includes(member.plan.schema)) {
|
|
265
261
|
for (const edge of member.plan.phase_accept_dependencies ?? []) {
|
|
266
262
|
const target = refKey(refOf(edge.to, 'phase_accept_dependencies.to'));
|
|
263
|
+
if (retiredKeys.has(target)) continue;
|
|
267
264
|
if (!nodesByKey.has(target)) {
|
|
268
265
|
fail('TODO_LAYOUT_INVALID_INPUT', 'phase accept dependency targets an absent task');
|
|
269
266
|
}
|
|
@@ -531,7 +528,7 @@ function normalizeSeamProposals(value) {
|
|
|
531
528
|
return { summary: { plans } };
|
|
532
529
|
}
|
|
533
530
|
|
|
534
|
-
function layoutTodoGanttFlat(readModel,
|
|
531
|
+
function layoutTodoGanttFlat(readModel, options = {}) {
|
|
535
532
|
const scope = options.scope ?? 'live';
|
|
536
533
|
if (!TODO_GANTT_SCOPES.includes(scope)) {
|
|
537
534
|
fail('TODO_LAYOUT_INVALID_INPUT', `scope must be one of ${TODO_GANTT_SCOPES.join(', ')}`);
|
|
@@ -540,25 +537,17 @@ function layoutTodoGanttFlat(readModel, chainProjection, options = {}) {
|
|
|
540
537
|
// Every structural number below is measured on the FULL graph: the dependency
|
|
541
538
|
// waves, the longest dependency chain and the ready frontier describe the real
|
|
542
539
|
// plan. Folding first and measuring second would make all three lie.
|
|
543
|
-
const full = normalizeInput(readModel
|
|
540
|
+
const full = normalizeInput(readModel);
|
|
544
541
|
const fullWaves = assignWaves(full.nodes, full.nodesByKey, full.edges);
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
})
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
const to = refKey(refOf(edge.to, `longest_chain_edges[${index}].to`));
|
|
555
|
-
const key = JSON.stringify([from, to]);
|
|
556
|
-
if (!full.edges.some((candidate) => candidate.key === key)) {
|
|
557
|
-
fail('TODO_LAYOUT_INVALID_INPUT', 'chain projection edge is absent from the read model');
|
|
558
|
-
}
|
|
559
|
-
return key;
|
|
560
|
-
}));
|
|
561
|
-
const readyKeys = readyTaskKeys(readModel, full.nodes, full.nodesByKey, fullWaves.incoming);
|
|
542
|
+
// 経路と配置は同じ正規化済みgraphから導出し、退役前の別graphを持ち込まない。
|
|
543
|
+
const chain = projectTodoChainV1({
|
|
544
|
+
nodes: full.nodes.map(node => node.ref),
|
|
545
|
+
hard_edges: full.edges.map(edge => ({ from: full.nodesByKey.get(edge.from).ref, to: full.nodesByKey.get(edge.to).ref })),
|
|
546
|
+
joins: [],
|
|
547
|
+
});
|
|
548
|
+
const longestNodeKeys = new Set(chain.longest_chain_node_refs.map(refKey));
|
|
549
|
+
const longestEdgeKeys = new Set(chain.longest_chain_edges.map(edge => JSON.stringify([refKey(edge.from), refKey(edge.to)])));
|
|
550
|
+
const readyKeys = readyTaskKeys(readModel, full.nodes, full.nodesByKey, fullWaves.incoming, full.retiredKeys);
|
|
562
551
|
const independence = normalizeIndependence(options.independence ?? null, full.nodesByKey);
|
|
563
552
|
const seamProposals = normalizeSeamProposals(options.seamProposals ?? null);
|
|
564
553
|
|
|
@@ -933,8 +922,8 @@ function descendantNodes(level) {
|
|
|
933
922
|
]);
|
|
934
923
|
}
|
|
935
924
|
|
|
936
|
-
export function layoutTodoGantt(readModel,
|
|
937
|
-
const fullLayout = layoutTodoGanttFlat(readModel,
|
|
925
|
+
export function layoutTodoGantt(readModel, options = {}) {
|
|
926
|
+
const fullLayout = layoutTodoGanttFlat(readModel, options);
|
|
938
927
|
let nested;
|
|
939
928
|
try {
|
|
940
929
|
const visibleTaskKeys = (options.scope ?? 'live') === 'all' ? null
|
|
@@ -951,7 +940,7 @@ export function layoutTodoGantt(readModel, chainProjection, options = {}) {
|
|
|
951
940
|
// 階層ごとの縮約graphは座標だけを決める。ready/最長鎖/独立性まで縮約graphで
|
|
952
941
|
// 再計算すると、未完の子を持つdone親が後続をreadyへ進めるなど、実graphと違う判断を描く。
|
|
953
942
|
const semanticLayout = options.scope === 'all'
|
|
954
|
-
? fullLayout : layoutTodoGanttFlat(readModel,
|
|
943
|
+
? fullLayout : layoutTodoGanttFlat(readModel, { ...options, scope: 'all' });
|
|
955
944
|
const semanticByKey = new Map(semanticLayout.nodes.map((node) => [refKey(node.ref), node]));
|
|
956
945
|
const semanticRoot = applyHierarchySemantics(nested.root, semanticByKey);
|
|
957
946
|
const rootLayout = semanticRoot.layout;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { projectTodoChainV1 } from './todo-chain.mjs';
|
|
2
|
-
import { projectTodoCrossPlanDependencies } from './todo-store.mjs';
|
|
3
1
|
|
|
4
2
|
const ROOT = Symbol('todo-gantt-root');
|
|
5
3
|
|
|
@@ -189,19 +187,6 @@ function filterIndependence(independence, selectedKeys) {
|
|
|
189
187
|
});
|
|
190
188
|
}
|
|
191
189
|
|
|
192
|
-
function topologyOf(readModel) {
|
|
193
|
-
const crossPlanDependencies = projectTodoCrossPlanDependencies(readModel.members);
|
|
194
|
-
return {
|
|
195
|
-
nodes: readModel.members.flatMap((member) => member.plan.tasks
|
|
196
|
-
.map(({ task_id: taskId }) => taskRef(member, taskId))),
|
|
197
|
-
hard_edges: [
|
|
198
|
-
...readModel.members.flatMap((member) => member.plan.hard_dependencies),
|
|
199
|
-
...crossPlanDependencies.map(({ from, to }) => ({ from, to })),
|
|
200
|
-
],
|
|
201
|
-
joins: readModel.members.flatMap((member) => member.plan.joins),
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
190
|
function buildLevel(readModel, hierarchy, containerKey, options, layoutFlat, includesSubtree) {
|
|
206
191
|
const selected = (hierarchy.childrenByParent.get(containerKey) ?? []).filter(includesSubtree);
|
|
207
192
|
const selectedKeys = new Set(selected);
|
|
@@ -216,7 +201,7 @@ function buildLevel(readModel, hierarchy, containerKey, options, layoutFlat, inc
|
|
|
216
201
|
independence: filterIndependence(options.independence ?? null, selectedKeys),
|
|
217
202
|
seamProposals: containerKey === ROOT ? options.seamProposals ?? null : null,
|
|
218
203
|
};
|
|
219
|
-
const layout = layoutFlat(projectedRead,
|
|
204
|
+
const layout = layoutFlat(projectedRead, levelOptions);
|
|
220
205
|
const children = selected.flatMap((parentKey) => {
|
|
221
206
|
if ((hierarchy.childrenByParent.get(parentKey) ?? []).length === 0) return [];
|
|
222
207
|
const childLevel = buildLevel(
|