@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.68.1",
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, chain, { scope, independence, seamProposals });
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, chain, {
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, chainProjection) {
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, chainProjection, options = {}) {
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, chainProjection);
540
+ const full = normalizeInput(readModel);
544
541
  const fullWaves = assignWaves(full.nodes, full.nodesByKey, full.edges);
545
-
546
- const longestNodeKeys = new Set(chainProjection.longest_chain_node_refs.map((ref, index) => {
547
- const key = refKey(refOf(ref, `longest_chain_node_refs[${index}]`));
548
- if (!full.nodesByKey.has(key)) fail('TODO_LAYOUT_INVALID_INPUT', 'chain projection references an absent task');
549
- return key;
550
- }));
551
- const longestEdgeKeys = new Set(chainProjection.longest_chain_edges.map((edge, index) => {
552
- if (!plain(edge)) fail('TODO_LAYOUT_INVALID_INPUT', `longest_chain_edges[${index}] is invalid`);
553
- const from = refKey(refOf(edge.from, `longest_chain_edges[${index}].from`));
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, chainProjection, options = {}) {
937
- const fullLayout = layoutTodoGanttFlat(readModel, chainProjection, options);
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, chainProjection, { ...options, scope: 'all' });
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, projectTodoChainV1(topologyOf(projectedRead)), levelOptions);
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(