@quolu/lattice 0.52.3 → 0.52.4
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/artifact-contracts-v2.mjs +325 -325
- package/src/artifact-contracts.mjs +895 -895
- package/src/boundary-compiler.mjs +712 -712
- package/src/boundary-observation-compiler-v2.mjs +344 -344
- package/src/bounded-seam.mjs +230 -230
- package/src/bridge-address.mjs +107 -107
- package/src/bridge-cli.mjs +297 -297
- package/src/bridge-config.mjs +346 -346
- package/src/bridge-daemon.mjs +378 -378
- package/src/bridge-hub-protocol.mjs +198 -0
- package/src/bridge-launch-agent.mjs +323 -323
- package/src/bridge-registrar.mjs +102 -102
- package/src/bridge-server.mjs +376 -376
- package/src/cli-help.mjs +308 -308
- package/src/cli-stdio.mjs +40 -40
- package/src/control-compiler.mjs +532 -532
- package/src/dag-chain.mjs +261 -261
- package/src/factory-diagnostics.mjs +188 -188
- package/src/git-process.mjs +72 -0
- package/src/hash-chain.mjs +76 -76
- package/src/hooks-cli.mjs +1057 -1057
- package/src/isolation-runner.mjs +409 -409
- package/src/node-version-guard.mjs +44 -44
- package/src/project-cli.mjs +3 -3
- package/src/project-identity.mjs +130 -130
- package/src/rc1-black-box-oracle.mjs +906 -906
- package/src/rc1-comparison.mjs +154 -154
- package/src/rc1-evidence-bundle.mjs +456 -456
- package/src/rc1-v4-campaign.mjs +708 -708
- package/src/rc1-v4-transform.mjs +467 -467
- package/src/rc1-v5-artifact-set.mjs +855 -855
- package/src/rc1-v5-behavior-evidence.mjs +594 -594
- package/src/rc1-v5-campaign.mjs +797 -797
- package/src/rc1-v5-transform.mjs +421 -421
- package/src/rc1-v6-artifact-set.mjs +807 -807
- package/src/rc1-v6-behavior-evidence.mjs +273 -273
- package/src/rc1-v6-campaign.mjs +623 -623
- package/src/rc1-v6-causal-binding.mjs +473 -473
- package/src/rc1-v6-measurement.mjs +313 -313
- package/src/rc2-artifact-set.mjs +1584 -1584
- package/src/rc2-campaign.mjs +1506 -1506
- package/src/rc2-delivery-policy-front-end.mjs +1079 -1079
- package/src/rc2-delivery-policy-oracle.mjs +134 -134
- package/src/rc2-delivery-policy-transform.mjs +1127 -1127
- package/src/rc2-rc1-transfer-front-end.mjs +511 -511
- package/src/rc3-actual-dogfood.mjs +652 -652
- package/src/rc3-dogfood-scaffold.mjs +315 -315
- package/src/rc3-scripted-campaign.mjs +1383 -1383
- package/src/rc4-stage1-dogfood.mjs +673 -673
- package/src/runtime-adapter-registry.mjs +524 -524
- package/src/runtime-cli.mjs +4588 -4588
- package/src/runtime-contracts.mjs +824 -824
- package/src/runtime-control-store.mjs +604 -604
- package/src/runtime-controller-protocol.mjs +587 -587
- package/src/runtime-decision-verifier.mjs +701 -701
- package/src/runtime-diff-observer.mjs +361 -361
- package/src/runtime-direct-os-observer.mjs +301 -301
- package/src/runtime-driver-state.mjs +166 -166
- package/src/runtime-engine.mjs +779 -779
- package/src/runtime-errors.mjs +356 -356
- package/src/runtime-event-store.mjs +189 -189
- package/src/runtime-front-end.mjs +925 -925
- package/src/runtime-gate-store.mjs +481 -481
- package/src/runtime-hold-recompile.mjs +916 -916
- package/src/runtime-io-sentinel.mjs +391 -391
- package/src/runtime-lifecycle-lock.mjs +294 -294
- package/src/runtime-managed-supervisor.mjs +1490 -1490
- package/src/runtime-multi-epoch-store.mjs +838 -838
- package/src/runtime-projection.mjs +269 -269
- package/src/runtime-pull-intake.mjs +1192 -1192
- package/src/runtime-scripted-adapter-controller.mjs +1160 -1160
- package/src/runtime-scripted-executor.mjs +163 -163
- package/src/runtime-scripted-worktree.mjs +104 -104
- package/src/runtime-seam-resolve.mjs +428 -428
- package/src/runtime-seam-treatment.mjs +173 -173
- package/src/runtime-socket-owner.mjs +125 -125
- package/src/runtime-work-order-contracts.mjs +91 -91
- package/src/runtime-work-order-controller.mjs +1171 -1171
- package/src/runtime-worktree-executor.mjs +199 -199
- package/src/schedulability-compiler-v2.mjs +303 -303
- package/src/schedulability-verifier-v2.mjs +317 -317
- package/src/seam-apply.mjs +549 -549
- package/src/seam-commit-shared.mjs +22 -22
- package/src/seam-commit-transform.mjs +81 -81
- package/src/seam-commit.mjs +18 -18
- package/src/seam-cost.mjs +322 -322
- package/src/seam-derivation.mjs +188 -188
- package/src/seam-gate.mjs +146 -146
- package/src/seam-proposal-contracts.mjs +446 -446
- package/src/seam-proposal-queries.mjs +521 -521
- package/src/seam-proposal.mjs +2011 -2011
- package/src/seam-ref.mjs +33 -33
- package/src/seam-rewrite.mjs +286 -286
- package/src/seam-transform.mjs +554 -554
- package/src/seam-verification.mjs +260 -260
- package/src/sensor-adapter.mjs +432 -432
- package/src/sensor-cli.mjs +139 -139
- package/src/sensor-diff.mjs +661 -661
- package/src/sensor-node-runtime.mjs +53 -53
- package/src/sensor-runtime.mjs +52 -52
- package/src/timestamp-contract.mjs +8 -8
- package/src/todo-audit-pending.mjs +91 -91
- package/src/todo-chain.mjs +178 -178
- package/src/todo-cli.mjs +8 -8
- package/src/todo-contracts.mjs +728 -728
- package/src/todo-dashboard-registry.mjs +573 -573
- package/src/todo-dispatch-shape.mjs +190 -190
- package/src/todo-gantt-html-independence.mjs +239 -239
- package/src/todo-gantt-html-shared.mjs +226 -226
- package/src/todo-gantt-html-style.mjs +131 -131
- package/src/todo-gantt-html.mjs +248 -248
- package/src/todo-gantt-layout.mjs +974 -974
- package/src/todo-gantt-live.mjs +361 -361
- package/src/todo-gantt-nested.mjs +263 -263
- package/src/todo-gantt-presentation.mjs +217 -217
- package/src/todo-gantt-scope.mjs +123 -123
- package/src/todo-gantt-svg.mjs +353 -353
- package/src/todo-independence-contracts.mjs +595 -595
- package/src/todo-independence-guidance.mjs +322 -322
- package/src/todo-independence.mjs +640 -640
- package/src/todo-markdown-renderer.mjs +260 -260
- package/src/todo-migration.mjs +448 -448
- package/src/todo-narrative-anchor.mjs +130 -130
- package/src/todo-note-store.mjs +629 -629
- package/src/todo-parallel-candidates.mjs +114 -114
- package/src/todo-revision.mjs +995 -995
- package/src/todo-split.mjs +472 -472
- package/src/todo-status.mjs +690 -690
- package/src/todo-store-git-transaction.mjs +418 -418
- package/src/todo-store.mjs +42 -21
- package/src/treatment-compiler.mjs +728 -728
- package/src/treatment-runner.mjs +656 -656
- package/src/witness-scaffold.mjs +180 -180
package/src/todo-chain.mjs
CHANGED
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
import { analyzeDagChains, DagCycleError } from './dag-chain.mjs';
|
|
2
|
-
|
|
3
|
-
const ASSUMPTIONS = Object.freeze({
|
|
4
|
-
unit_duration: true,
|
|
5
|
-
capacity_ignored: true,
|
|
6
|
-
conflict_ignored: true,
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
export class TodoChainProjectionError extends Error {
|
|
10
|
-
constructor(code, message, options) {
|
|
11
|
-
super(message, options);
|
|
12
|
-
this.name = 'TodoChainProjectionError';
|
|
13
|
-
this.code = code;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class TodoChainCycleError extends TodoChainProjectionError {
|
|
18
|
-
constructor(options) {
|
|
19
|
-
super('TODO_CHAIN_CYCLE', 'merged todo topology contains a cycle', options);
|
|
20
|
-
this.name = 'TodoChainCycleError';
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function fail(code, message) {
|
|
25
|
-
throw new TodoChainProjectionError(code, message);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function isPlainObject(value) {
|
|
29
|
-
return value !== null
|
|
30
|
-
&& typeof value === 'object'
|
|
31
|
-
&& !Array.isArray(value)
|
|
32
|
-
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function normalizeNodeRef(value, context) {
|
|
36
|
-
if (!isPlainObject(value)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} must be a node ref`);
|
|
37
|
-
const { project_id: projectId, plan_key: planKey, task_id: taskId } = value;
|
|
38
|
-
if (typeof projectId !== 'string' || typeof planKey !== 'string' || typeof taskId !== 'string') {
|
|
39
|
-
fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} node ref fields must be strings`);
|
|
40
|
-
}
|
|
41
|
-
return { project_id: projectId, plan_key: planKey, task_id: taskId };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function nodeKey(ref) {
|
|
45
|
-
return JSON.stringify([ref.project_id, ref.plan_key, ref.task_id]);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function compareRefs(left, right) {
|
|
49
|
-
for (const field of ['project_id', 'plan_key', 'task_id']) {
|
|
50
|
-
if (left[field] < right[field]) return -1;
|
|
51
|
-
if (left[field] > right[field]) return 1;
|
|
52
|
-
}
|
|
53
|
-
return 0;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function requireArray(value, context) {
|
|
57
|
-
if (!Array.isArray(value)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} must be an array`);
|
|
58
|
-
return value;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function normalizeOptions(options) {
|
|
62
|
-
if (options === undefined) options = {};
|
|
63
|
-
if (!isPlainObject(options)) {
|
|
64
|
-
fail('TODO_CHAIN_INVALID_OPTIONS', 'options must be an object');
|
|
65
|
-
}
|
|
66
|
-
const allowedKeys = new Set(['countCap', 'representativeLimit']);
|
|
67
|
-
for (const key of Reflect.ownKeys(options)) {
|
|
68
|
-
if (typeof key !== 'string' || !allowedKeys.has(key)) {
|
|
69
|
-
fail('TODO_CHAIN_INVALID_OPTIONS', `unknown option: ${String(key)}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const countCap = Object.hasOwn(options, 'countCap') ? options.countCap : 1_000_000;
|
|
74
|
-
const representativeLimit = Object.hasOwn(options, 'representativeLimit')
|
|
75
|
-
? options.representativeLimit
|
|
76
|
-
: 8;
|
|
77
|
-
if (!Number.isSafeInteger(countCap) || countCap < 1 || countCap >= Number.MAX_SAFE_INTEGER) {
|
|
78
|
-
fail(
|
|
79
|
-
'TODO_CHAIN_INVALID_OPTIONS',
|
|
80
|
-
'countCap must be a safe integer from 1 through Number.MAX_SAFE_INTEGER - 1',
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
if (
|
|
84
|
-
!Number.isSafeInteger(representativeLimit)
|
|
85
|
-
|| representativeLimit < 0
|
|
86
|
-
|| representativeLimit > 8
|
|
87
|
-
) {
|
|
88
|
-
fail('TODO_CHAIN_INVALID_OPTIONS', 'representativeLimit must be a safe integer from 0 through 8');
|
|
89
|
-
}
|
|
90
|
-
return { countCap, representativeLimit };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function projectTodoChainV1(
|
|
94
|
-
mergedTodoTopology,
|
|
95
|
-
options,
|
|
96
|
-
) {
|
|
97
|
-
const { countCap, representativeLimit } = normalizeOptions(options);
|
|
98
|
-
if (!isPlainObject(mergedTodoTopology)) {
|
|
99
|
-
fail('TODO_CHAIN_INVALID_TOPOLOGY', 'mergedTodoTopology must be an object');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const nodes = requireArray(mergedTodoTopology.nodes, 'nodes');
|
|
103
|
-
const hardEdges = requireArray(mergedTodoTopology.hard_edges, 'hard_edges');
|
|
104
|
-
const joins = requireArray(mergedTodoTopology.joins, 'joins');
|
|
105
|
-
const refsByKey = new Map();
|
|
106
|
-
|
|
107
|
-
for (let index = 0; index < nodes.length; index += 1) {
|
|
108
|
-
const ref = normalizeNodeRef(nodes[index], `nodes[${index}]`);
|
|
109
|
-
const key = nodeKey(ref);
|
|
110
|
-
if (refsByKey.has(key)) fail('TODO_CHAIN_DUPLICATE_NODE', `duplicate node ref at nodes[${index}]`);
|
|
111
|
-
refsByKey.set(key, ref);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const edgeKeys = new Set();
|
|
115
|
-
const edges = [];
|
|
116
|
-
const addEdge = (fromValue, toValue, context) => {
|
|
117
|
-
const fromRef = normalizeNodeRef(fromValue, `${context}.from`);
|
|
118
|
-
const toRef = normalizeNodeRef(toValue, `${context}.to`);
|
|
119
|
-
const from = nodeKey(fromRef);
|
|
120
|
-
const to = nodeKey(toRef);
|
|
121
|
-
if (!refsByKey.has(from) || !refsByKey.has(to)) {
|
|
122
|
-
fail('TODO_CHAIN_DANGLING_EDGE', `${context} references a node absent from nodes`);
|
|
123
|
-
}
|
|
124
|
-
const edgeKey = JSON.stringify([from, to]);
|
|
125
|
-
if (edgeKeys.has(edgeKey)) return;
|
|
126
|
-
edgeKeys.add(edgeKey);
|
|
127
|
-
edges.push([from, to]);
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
for (let index = 0; index < hardEdges.length; index += 1) {
|
|
131
|
-
const edge = hardEdges[index];
|
|
132
|
-
if (!isPlainObject(edge)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `hard_edges[${index}] must be an object`);
|
|
133
|
-
addEdge(edge.from, edge.to, `hard_edges[${index}]`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
for (let joinIndex = 0; joinIndex < joins.length; joinIndex += 1) {
|
|
137
|
-
const join = joins[joinIndex];
|
|
138
|
-
if (!isPlainObject(join)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `joins[${joinIndex}] must be an object`);
|
|
139
|
-
const after = requireArray(join.after, `joins[${joinIndex}].after`);
|
|
140
|
-
for (let afterIndex = 0; afterIndex < after.length; afterIndex += 1) {
|
|
141
|
-
addEdge(
|
|
142
|
-
after[afterIndex],
|
|
143
|
-
join.before,
|
|
144
|
-
`joins[${joinIndex}].after[${afterIndex}]`,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
let analysis;
|
|
150
|
-
try {
|
|
151
|
-
analysis = analyzeDagChains([...refsByKey.keys()], edges, {
|
|
152
|
-
countCap,
|
|
153
|
-
representativeLimit,
|
|
154
|
-
compare: (left, right) => compareRefs(refsByKey.get(left), refsByKey.get(right)),
|
|
155
|
-
});
|
|
156
|
-
} catch (error) {
|
|
157
|
-
if (error instanceof DagCycleError) throw new TodoChainCycleError({ cause: error });
|
|
158
|
-
throw error;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const toRef = (key) => ({ ...refsByKey.get(key) });
|
|
162
|
-
return {
|
|
163
|
-
schema: 'lattice.todo_chain.v1',
|
|
164
|
-
maximum_dependency_depth: analysis.maximumDepth,
|
|
165
|
-
longest_chain_node_refs: analysis.longestChainNodes.map(toRef),
|
|
166
|
-
longest_chain_edges: analysis.longestChainEdges.map(([from, to]) => ({
|
|
167
|
-
from: toRef(from),
|
|
168
|
-
to: toRef(to),
|
|
169
|
-
})),
|
|
170
|
-
longest_chain_count: analysis.longestChainCount,
|
|
171
|
-
limits: {
|
|
172
|
-
count_cap: countCap,
|
|
173
|
-
representative_limit: representativeLimit,
|
|
174
|
-
},
|
|
175
|
-
representative_chains: analysis.representativeChains.map((chain) => chain.map(toRef)),
|
|
176
|
-
assumptions: { ...ASSUMPTIONS },
|
|
177
|
-
};
|
|
178
|
-
}
|
|
1
|
+
import { analyzeDagChains, DagCycleError } from './dag-chain.mjs';
|
|
2
|
+
|
|
3
|
+
const ASSUMPTIONS = Object.freeze({
|
|
4
|
+
unit_duration: true,
|
|
5
|
+
capacity_ignored: true,
|
|
6
|
+
conflict_ignored: true,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export class TodoChainProjectionError extends Error {
|
|
10
|
+
constructor(code, message, options) {
|
|
11
|
+
super(message, options);
|
|
12
|
+
this.name = 'TodoChainProjectionError';
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class TodoChainCycleError extends TodoChainProjectionError {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
super('TODO_CHAIN_CYCLE', 'merged todo topology contains a cycle', options);
|
|
20
|
+
this.name = 'TodoChainCycleError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function fail(code, message) {
|
|
25
|
+
throw new TodoChainProjectionError(code, message);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isPlainObject(value) {
|
|
29
|
+
return value !== null
|
|
30
|
+
&& typeof value === 'object'
|
|
31
|
+
&& !Array.isArray(value)
|
|
32
|
+
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeNodeRef(value, context) {
|
|
36
|
+
if (!isPlainObject(value)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} must be a node ref`);
|
|
37
|
+
const { project_id: projectId, plan_key: planKey, task_id: taskId } = value;
|
|
38
|
+
if (typeof projectId !== 'string' || typeof planKey !== 'string' || typeof taskId !== 'string') {
|
|
39
|
+
fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} node ref fields must be strings`);
|
|
40
|
+
}
|
|
41
|
+
return { project_id: projectId, plan_key: planKey, task_id: taskId };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function nodeKey(ref) {
|
|
45
|
+
return JSON.stringify([ref.project_id, ref.plan_key, ref.task_id]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function compareRefs(left, right) {
|
|
49
|
+
for (const field of ['project_id', 'plan_key', 'task_id']) {
|
|
50
|
+
if (left[field] < right[field]) return -1;
|
|
51
|
+
if (left[field] > right[field]) return 1;
|
|
52
|
+
}
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function requireArray(value, context) {
|
|
57
|
+
if (!Array.isArray(value)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `${context} must be an array`);
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeOptions(options) {
|
|
62
|
+
if (options === undefined) options = {};
|
|
63
|
+
if (!isPlainObject(options)) {
|
|
64
|
+
fail('TODO_CHAIN_INVALID_OPTIONS', 'options must be an object');
|
|
65
|
+
}
|
|
66
|
+
const allowedKeys = new Set(['countCap', 'representativeLimit']);
|
|
67
|
+
for (const key of Reflect.ownKeys(options)) {
|
|
68
|
+
if (typeof key !== 'string' || !allowedKeys.has(key)) {
|
|
69
|
+
fail('TODO_CHAIN_INVALID_OPTIONS', `unknown option: ${String(key)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const countCap = Object.hasOwn(options, 'countCap') ? options.countCap : 1_000_000;
|
|
74
|
+
const representativeLimit = Object.hasOwn(options, 'representativeLimit')
|
|
75
|
+
? options.representativeLimit
|
|
76
|
+
: 8;
|
|
77
|
+
if (!Number.isSafeInteger(countCap) || countCap < 1 || countCap >= Number.MAX_SAFE_INTEGER) {
|
|
78
|
+
fail(
|
|
79
|
+
'TODO_CHAIN_INVALID_OPTIONS',
|
|
80
|
+
'countCap must be a safe integer from 1 through Number.MAX_SAFE_INTEGER - 1',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (
|
|
84
|
+
!Number.isSafeInteger(representativeLimit)
|
|
85
|
+
|| representativeLimit < 0
|
|
86
|
+
|| representativeLimit > 8
|
|
87
|
+
) {
|
|
88
|
+
fail('TODO_CHAIN_INVALID_OPTIONS', 'representativeLimit must be a safe integer from 0 through 8');
|
|
89
|
+
}
|
|
90
|
+
return { countCap, representativeLimit };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function projectTodoChainV1(
|
|
94
|
+
mergedTodoTopology,
|
|
95
|
+
options,
|
|
96
|
+
) {
|
|
97
|
+
const { countCap, representativeLimit } = normalizeOptions(options);
|
|
98
|
+
if (!isPlainObject(mergedTodoTopology)) {
|
|
99
|
+
fail('TODO_CHAIN_INVALID_TOPOLOGY', 'mergedTodoTopology must be an object');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const nodes = requireArray(mergedTodoTopology.nodes, 'nodes');
|
|
103
|
+
const hardEdges = requireArray(mergedTodoTopology.hard_edges, 'hard_edges');
|
|
104
|
+
const joins = requireArray(mergedTodoTopology.joins, 'joins');
|
|
105
|
+
const refsByKey = new Map();
|
|
106
|
+
|
|
107
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
108
|
+
const ref = normalizeNodeRef(nodes[index], `nodes[${index}]`);
|
|
109
|
+
const key = nodeKey(ref);
|
|
110
|
+
if (refsByKey.has(key)) fail('TODO_CHAIN_DUPLICATE_NODE', `duplicate node ref at nodes[${index}]`);
|
|
111
|
+
refsByKey.set(key, ref);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const edgeKeys = new Set();
|
|
115
|
+
const edges = [];
|
|
116
|
+
const addEdge = (fromValue, toValue, context) => {
|
|
117
|
+
const fromRef = normalizeNodeRef(fromValue, `${context}.from`);
|
|
118
|
+
const toRef = normalizeNodeRef(toValue, `${context}.to`);
|
|
119
|
+
const from = nodeKey(fromRef);
|
|
120
|
+
const to = nodeKey(toRef);
|
|
121
|
+
if (!refsByKey.has(from) || !refsByKey.has(to)) {
|
|
122
|
+
fail('TODO_CHAIN_DANGLING_EDGE', `${context} references a node absent from nodes`);
|
|
123
|
+
}
|
|
124
|
+
const edgeKey = JSON.stringify([from, to]);
|
|
125
|
+
if (edgeKeys.has(edgeKey)) return;
|
|
126
|
+
edgeKeys.add(edgeKey);
|
|
127
|
+
edges.push([from, to]);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
for (let index = 0; index < hardEdges.length; index += 1) {
|
|
131
|
+
const edge = hardEdges[index];
|
|
132
|
+
if (!isPlainObject(edge)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `hard_edges[${index}] must be an object`);
|
|
133
|
+
addEdge(edge.from, edge.to, `hard_edges[${index}]`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (let joinIndex = 0; joinIndex < joins.length; joinIndex += 1) {
|
|
137
|
+
const join = joins[joinIndex];
|
|
138
|
+
if (!isPlainObject(join)) fail('TODO_CHAIN_INVALID_TOPOLOGY', `joins[${joinIndex}] must be an object`);
|
|
139
|
+
const after = requireArray(join.after, `joins[${joinIndex}].after`);
|
|
140
|
+
for (let afterIndex = 0; afterIndex < after.length; afterIndex += 1) {
|
|
141
|
+
addEdge(
|
|
142
|
+
after[afterIndex],
|
|
143
|
+
join.before,
|
|
144
|
+
`joins[${joinIndex}].after[${afterIndex}]`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let analysis;
|
|
150
|
+
try {
|
|
151
|
+
analysis = analyzeDagChains([...refsByKey.keys()], edges, {
|
|
152
|
+
countCap,
|
|
153
|
+
representativeLimit,
|
|
154
|
+
compare: (left, right) => compareRefs(refsByKey.get(left), refsByKey.get(right)),
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (error instanceof DagCycleError) throw new TodoChainCycleError({ cause: error });
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const toRef = (key) => ({ ...refsByKey.get(key) });
|
|
162
|
+
return {
|
|
163
|
+
schema: 'lattice.todo_chain.v1',
|
|
164
|
+
maximum_dependency_depth: analysis.maximumDepth,
|
|
165
|
+
longest_chain_node_refs: analysis.longestChainNodes.map(toRef),
|
|
166
|
+
longest_chain_edges: analysis.longestChainEdges.map(([from, to]) => ({
|
|
167
|
+
from: toRef(from),
|
|
168
|
+
to: toRef(to),
|
|
169
|
+
})),
|
|
170
|
+
longest_chain_count: analysis.longestChainCount,
|
|
171
|
+
limits: {
|
|
172
|
+
count_cap: countCap,
|
|
173
|
+
representative_limit: representativeLimit,
|
|
174
|
+
},
|
|
175
|
+
representative_chains: analysis.representativeChains.map((chain) => chain.map(toRef)),
|
|
176
|
+
assumptions: { ...ASSUMPTIONS },
|
|
177
|
+
};
|
|
178
|
+
}
|
package/src/todo-cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { gitSync } from './git-process.mjs';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import {
|
|
4
4
|
lstat, mkdir, readFile, realpath, writeFile,
|
|
@@ -223,7 +223,7 @@ function resolveRepoRoot(cwd) {
|
|
|
223
223
|
try {
|
|
224
224
|
// gitはWindowsでもforward slashを返すため、OS nativeへ正規化する(runtime-cliと同じ規律)。
|
|
225
225
|
// trimは末尾空白のrepo rootを改変するのでnewlineだけを剥がす。
|
|
226
|
-
return path.resolve(
|
|
226
|
+
return path.resolve(gitSync(['rev-parse', '--show-toplevel'], {
|
|
227
227
|
cwd,
|
|
228
228
|
encoding: 'utf8',
|
|
229
229
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -1182,7 +1182,7 @@ async function migrate({ repoRoot, inputRef, serializationReviewed = false }) {
|
|
|
1182
1182
|
function extractionFreshnessViolations(repoRoot, extraction) {
|
|
1183
1183
|
let reachable;
|
|
1184
1184
|
try {
|
|
1185
|
-
reachable = new Set(
|
|
1185
|
+
reachable = new Set(gitSync(['rev-list', '--objects', '--all'], {
|
|
1186
1186
|
cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
1187
1187
|
}).split('\n').filter(Boolean).map((line) => line.split(' ')[0]));
|
|
1188
1188
|
} catch {
|
|
@@ -1203,7 +1203,7 @@ function extractionFreshnessViolations(repoRoot, extraction) {
|
|
|
1203
1203
|
}
|
|
1204
1204
|
let markdown;
|
|
1205
1205
|
try {
|
|
1206
|
-
markdown =
|
|
1206
|
+
markdown = gitSync(['show', `${source.source_commit}:${source.origin_plan_ref}`], {
|
|
1207
1207
|
cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 8_388_608,
|
|
1208
1208
|
});
|
|
1209
1209
|
} catch {
|
|
@@ -1653,7 +1653,7 @@ async function bindings({ repoRoot, requestedPlanKey }) {
|
|
|
1653
1653
|
function currentHeadSha(repoRoot) {
|
|
1654
1654
|
let head;
|
|
1655
1655
|
try {
|
|
1656
|
-
head =
|
|
1656
|
+
head = gitSync(['rev-parse', 'HEAD'], {
|
|
1657
1657
|
cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
1658
1658
|
}).trim();
|
|
1659
1659
|
} catch {
|
|
@@ -1674,7 +1674,7 @@ function currentHeadSha(repoRoot) {
|
|
|
1674
1674
|
*/
|
|
1675
1675
|
function changedPathsSince(repoRoot, baseSha) {
|
|
1676
1676
|
try {
|
|
1677
|
-
|
|
1677
|
+
gitSync(['cat-file', '-e', `${baseSha}^{commit}`], {
|
|
1678
1678
|
cwd: repoRoot, stdio: ['ignore', 'ignore', 'ignore'],
|
|
1679
1679
|
});
|
|
1680
1680
|
} catch {
|
|
@@ -1682,7 +1682,7 @@ function changedPathsSince(repoRoot, baseSha) {
|
|
|
1682
1682
|
}
|
|
1683
1683
|
let output;
|
|
1684
1684
|
try {
|
|
1685
|
-
output =
|
|
1685
|
+
output = gitSync(['diff', '--name-only', '--no-renames', `${baseSha}..HEAD`], {
|
|
1686
1686
|
cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
1687
1687
|
});
|
|
1688
1688
|
} catch {
|
|
@@ -1695,7 +1695,7 @@ function changedPathsSince(repoRoot, baseSha) {
|
|
|
1695
1695
|
function requireCleanWorktree(repoRoot) {
|
|
1696
1696
|
let porcelain;
|
|
1697
1697
|
try {
|
|
1698
|
-
porcelain =
|
|
1698
|
+
porcelain = gitSync(['status', '--porcelain'], {
|
|
1699
1699
|
cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
1700
1700
|
});
|
|
1701
1701
|
} catch {
|