@quolu/lattice 0.58.0 → 0.58.1
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.58.
|
|
3
|
+
"version": "0.58.1",
|
|
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
|
@@ -77,8 +77,7 @@ import {
|
|
|
77
77
|
explainTodoStructureRealization,
|
|
78
78
|
explainTodoStructureSet,
|
|
79
79
|
} from './todo-structure-contracts.mjs';
|
|
80
|
-
import {
|
|
81
|
-
import { collectTodoStructureGitProvenance } from './todo-structure-git-adapter.mjs';
|
|
80
|
+
import { collectTodoStructureAuthoritativeObservation } from './todo-structure-authoritative-observation.mjs';
|
|
82
81
|
import { compileTodoStructureOverlay } from './todo-structure-overlay.mjs';
|
|
83
82
|
import {
|
|
84
83
|
buildTodoStructureCompileArtifact,
|
|
@@ -2024,8 +2023,9 @@ async function structureCompile({ repoRoot, env, planKey, inputRef }) {
|
|
|
2024
2023
|
});
|
|
2025
2024
|
}
|
|
2026
2025
|
const actor = mutationActor(env);
|
|
2027
|
-
const
|
|
2028
|
-
const
|
|
2026
|
+
const observation = await collectTodoStructureAuthoritativeObservation({ repoRoot, structureSet });
|
|
2027
|
+
const sourceEvidence = observation.source_evidence;
|
|
2028
|
+
const gitProvenance = observation.git_provenance;
|
|
2029
2029
|
const realizations = [];
|
|
2030
2030
|
for (const task of structureSet.tasks.filter(({ applicability }) => applicability === 'graph')) {
|
|
2031
2031
|
realizations.push(...await readTodoStructureRealizationChain({
|
|
@@ -2188,10 +2188,11 @@ async function structureFinalize({ repoRoot, env, planKey }) {
|
|
|
2188
2188
|
effectiveTransforms.set(task.task_id, latest.realized);
|
|
2189
2189
|
}
|
|
2190
2190
|
const actor = mutationActor(env);
|
|
2191
|
-
const
|
|
2192
|
-
|
|
2191
|
+
const observation = await collectTodoStructureAuthoritativeObservation({
|
|
2192
|
+
repoRoot, structureSet: source, effectiveTransforms,
|
|
2193
2193
|
});
|
|
2194
|
-
const
|
|
2194
|
+
const sourceEvidence = observation.source_evidence;
|
|
2195
|
+
const gitProvenance = observation.git_provenance;
|
|
2195
2196
|
const overlay = compileTodoStructureOverlay({
|
|
2196
2197
|
structureSet: source,
|
|
2197
2198
|
topology: mergedTopology(store),
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { gitSpawnSync } from './git-process.mjs';
|
|
6
|
+
import { runSensorCli } from './sensor-cli.mjs';
|
|
7
|
+
import { collectTodoStructureGitProvenance } from './todo-structure-git-adapter.mjs';
|
|
8
|
+
import { collectTodoStructureSourceEvidence } from './todo-structure-source-adapter.mjs';
|
|
9
|
+
|
|
10
|
+
const SHA = /^[0-9a-f]{40}$/u;
|
|
11
|
+
|
|
12
|
+
export class TodoStructureObservationError extends Error {
|
|
13
|
+
constructor(code, reason, detail = {}) {
|
|
14
|
+
super(reason);
|
|
15
|
+
this.name = 'TodoStructureObservationError';
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.detail = { reason, ...detail };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function fail(code, reason, detail = {}) {
|
|
22
|
+
throw new TodoStructureObservationError(code, reason, detail);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function git({ cwd, args, operation }) {
|
|
26
|
+
const result = gitSpawnSync(args, {
|
|
27
|
+
cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
28
|
+
});
|
|
29
|
+
if (result.error !== undefined || result.status !== 0 || result.signal !== null) {
|
|
30
|
+
fail('STRUCTURE_OBSERVATION_GIT_FAILED', 'observation_git_command_failed', {
|
|
31
|
+
operation, status: result.status ?? null, signal: result.signal ?? null,
|
|
32
|
+
cause: result.error?.message ?? null,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return result.stdout.trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function memoryStream() {
|
|
39
|
+
let value = '';
|
|
40
|
+
return {
|
|
41
|
+
stream: { write: (chunk) => { value += String(chunk); } },
|
|
42
|
+
read: () => value,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function initializeObservationSensor(observationRoot) {
|
|
47
|
+
const stdout = memoryStream();
|
|
48
|
+
const stderr = memoryStream();
|
|
49
|
+
const status = await runSensorCli({
|
|
50
|
+
argv: ['init', observationRoot, '--json'],
|
|
51
|
+
stdout: stdout.stream,
|
|
52
|
+
stderr: stderr.stream,
|
|
53
|
+
});
|
|
54
|
+
if (status !== 0) {
|
|
55
|
+
let diagnostic = null;
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(stderr.read().trim());
|
|
58
|
+
diagnostic = {
|
|
59
|
+
code: typeof parsed?.code === 'string' ? parsed.code : null,
|
|
60
|
+
detail: parsed?.detail ?? null,
|
|
61
|
+
};
|
|
62
|
+
} catch {
|
|
63
|
+
diagnostic = { code: null, detail: null };
|
|
64
|
+
}
|
|
65
|
+
fail('STRUCTURE_OBSERVATION_SENSOR_INIT_FAILED', 'observation_sensor_init_failed', {
|
|
66
|
+
sensor_error: diagnostic,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function errorSummary(error) {
|
|
72
|
+
return {
|
|
73
|
+
code: typeof error?.code === 'string' ? error.code : null,
|
|
74
|
+
message: error instanceof Error ? error.message : String(error),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 管理repoのdirtyなindex/worktreeを一切観測せず、current HEADだけを一時detached worktreeへ
|
|
80
|
+
* 展開してGit来歴とsource graphを収集する。store/planned source/artifactの読書きは呼出側が
|
|
81
|
+
* 管理repoで行うため、ここへは持ち込まない。
|
|
82
|
+
*/
|
|
83
|
+
export async function collectTodoStructureAuthoritativeObservation({
|
|
84
|
+
repoRoot, structureSet, effectiveTransforms = null,
|
|
85
|
+
initializeSensor = initializeObservationSensor,
|
|
86
|
+
} = {}) {
|
|
87
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0
|
|
88
|
+
|| structureSet === null || typeof structureSet !== 'object'
|
|
89
|
+
|| typeof initializeSensor !== 'function') {
|
|
90
|
+
fail('STRUCTURE_OBSERVATION_INPUT_INVALID', 'observation_input_invalid');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const headSha = git({
|
|
94
|
+
cwd: repoRoot, args: ['rev-parse', '--verify', 'HEAD^{commit}'], operation: 'resolve_head',
|
|
95
|
+
});
|
|
96
|
+
if (!SHA.test(headSha)) fail('STRUCTURE_OBSERVATION_HEAD_INVALID', 'observation_head_invalid');
|
|
97
|
+
|
|
98
|
+
const temporaryRoot = await mkdtemp(path.join(tmpdir(), 'lattice-structure-observation-'));
|
|
99
|
+
const observationRoot = path.join(temporaryRoot, 'worktree');
|
|
100
|
+
let registered = false;
|
|
101
|
+
let operationError = null;
|
|
102
|
+
let result;
|
|
103
|
+
try {
|
|
104
|
+
git({
|
|
105
|
+
cwd: repoRoot,
|
|
106
|
+
args: ['worktree', 'add', '--detach', '--quiet', observationRoot, headSha],
|
|
107
|
+
operation: 'worktree_add',
|
|
108
|
+
});
|
|
109
|
+
registered = true;
|
|
110
|
+
const observedHead = git({
|
|
111
|
+
cwd: observationRoot, args: ['rev-parse', '--verify', 'HEAD^{commit}'],
|
|
112
|
+
operation: 'verify_observation_head',
|
|
113
|
+
});
|
|
114
|
+
if (observedHead !== headSha) {
|
|
115
|
+
fail('STRUCTURE_OBSERVATION_HEAD_MISMATCH', 'observation_head_mismatch', {
|
|
116
|
+
expected_head_sha: headSha, actual_head_sha: observedHead,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// sensor DBを作る前にclean treeをGit provenanceへ束縛する。sensorの一時fileは証拠対象外で、
|
|
121
|
+
// source projection収集後にworktreeごと破棄する。
|
|
122
|
+
const gitProvenance = collectTodoStructureGitProvenance({
|
|
123
|
+
repoRoot: observationRoot, structureSet,
|
|
124
|
+
});
|
|
125
|
+
await initializeSensor(observationRoot);
|
|
126
|
+
const sourceEvidence = await collectTodoStructureSourceEvidence({
|
|
127
|
+
cwd: observationRoot, structureSet, effectiveTransforms,
|
|
128
|
+
observationStage: effectiveTransforms === null ? 'planned' : 'final',
|
|
129
|
+
});
|
|
130
|
+
result = {
|
|
131
|
+
head_sha: headSha,
|
|
132
|
+
git_provenance: gitProvenance,
|
|
133
|
+
source_evidence: sourceEvidence,
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
operationError = error;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let cleanupError = null;
|
|
140
|
+
try {
|
|
141
|
+
if (registered) {
|
|
142
|
+
git({
|
|
143
|
+
cwd: repoRoot,
|
|
144
|
+
args: ['worktree', 'remove', '--force', observationRoot],
|
|
145
|
+
operation: 'worktree_remove',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
await rm(temporaryRoot, { recursive: true, force: true });
|
|
149
|
+
} catch (error) {
|
|
150
|
+
cleanupError = error;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (cleanupError !== null) {
|
|
154
|
+
fail('STRUCTURE_OBSERVATION_CLEANUP_FAILED', 'observation_cleanup_failed', {
|
|
155
|
+
operation_error: operationError === null ? null : errorSummary(operationError),
|
|
156
|
+
cleanup_error: errorSummary(cleanupError),
|
|
157
|
+
observation_root: observationRoot,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (operationError !== null) throw operationError;
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
@@ -466,12 +466,6 @@ function realizationFindings(structureSet, states, realizations, gitProvenance,
|
|
|
466
466
|
observed: null, expected: 'fresh task realization',
|
|
467
467
|
nextAction: 'record_the_task_realization_before_done',
|
|
468
468
|
}));
|
|
469
|
-
} else if (state === 'in-progress') {
|
|
470
|
-
findings.push(finding({
|
|
471
|
-
code: 'STRUCTURE_COMMIT_UNBOUND', severity: 'unknown', taskIds: [task.task_id],
|
|
472
|
-
observed: { commits: [] }, expected: 'explicit realization commit OIDs when implementation exists',
|
|
473
|
-
nextAction: 'record_a_realization_when_the_task_has_committed_implementation',
|
|
474
|
-
}));
|
|
475
469
|
}
|
|
476
470
|
continue;
|
|
477
471
|
}
|
|
@@ -234,13 +234,26 @@ function edgeObservation(outcome, expectedNode, direction, expectedPath) {
|
|
|
234
234
|
};
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
function effectVerdict(anchor, existence) {
|
|
238
|
-
if (anchor.expected_at
|
|
237
|
+
function effectVerdict(anchor, existence, observationStage) {
|
|
238
|
+
if (anchor.expected_at === 'baseline') {
|
|
239
239
|
return { verdict: 'unknown', reason: 'STRUCTURE_CODE_ANCHOR_TIME_DEFERRED' };
|
|
240
240
|
}
|
|
241
|
+
if (anchor.expected_at === 'after_task' && observationStage === 'planned') {
|
|
242
|
+
return { verdict: 'consistent', reason: null, deferred: true };
|
|
243
|
+
}
|
|
241
244
|
if (existence === 'unknown') {
|
|
242
245
|
return { verdict: 'unknown', reason: 'STRUCTURE_CODE_ANCHOR_UNRESOLVED' };
|
|
243
246
|
}
|
|
247
|
+
if (anchor.expected_at === 'after_task' && observationStage === 'final') {
|
|
248
|
+
const expected = anchor.effect === 'delete' ? 'absent' : 'present';
|
|
249
|
+
return existence === expected
|
|
250
|
+
? { verdict: 'consistent', reason: null }
|
|
251
|
+
: { verdict: 'inconsistent', reason: expected === 'present'
|
|
252
|
+
? 'STRUCTURE_CODE_ANCHOR_ABSENT' : 'STRUCTURE_DELETE_STILL_EXISTS' };
|
|
253
|
+
}
|
|
254
|
+
if (anchor.expected_at !== 'current') {
|
|
255
|
+
return { verdict: 'unknown', reason: 'STRUCTURE_CODE_ANCHOR_TIME_DEFERRED' };
|
|
256
|
+
}
|
|
244
257
|
if (anchor.effect === 'create') {
|
|
245
258
|
return existence === 'absent'
|
|
246
259
|
? { verdict: 'consistent', reason: null }
|
|
@@ -274,7 +287,21 @@ function unknownAnchor(anchor, evidence, reason) {
|
|
|
274
287
|
};
|
|
275
288
|
}
|
|
276
289
|
|
|
277
|
-
function
|
|
290
|
+
function deferredAnchor(anchor, evidence) {
|
|
291
|
+
return {
|
|
292
|
+
...unknownAnchor(anchor, evidence, null),
|
|
293
|
+
verdict: 'consistent',
|
|
294
|
+
reason: null,
|
|
295
|
+
coverage: 'deferred',
|
|
296
|
+
edges: {
|
|
297
|
+
state: 'not_applicable',
|
|
298
|
+
incoming: [], outgoing: [], incoming_omitted: 0, outgoing_omitted: 0,
|
|
299
|
+
incoming_source_limit_reached: false, outgoing_source_limit_reached: false,
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function projectAnchor({ anchor, querySet, outcomes, statusReady, observationStage }) {
|
|
278
305
|
const pathQuery = queryFor('affected', anchor.path);
|
|
279
306
|
const pathOutcome = outcomes.get(pathQuery.id);
|
|
280
307
|
const evidence = {
|
|
@@ -284,11 +311,14 @@ function projectAnchor({ anchor, querySet, outcomes, statusReady }) {
|
|
|
284
311
|
callers: null,
|
|
285
312
|
callees: null,
|
|
286
313
|
};
|
|
314
|
+
if (anchor.expected_at === 'after_task' && observationStage === 'planned') {
|
|
315
|
+
return deferredAnchor(anchor, evidence);
|
|
316
|
+
}
|
|
287
317
|
if (!statusReady) return unknownAnchor(anchor, evidence, 'STRUCTURE_SENSOR_NOT_READY');
|
|
288
318
|
|
|
289
319
|
const pathResult = pathObservation(pathQuery, pathOutcome, anchor.path);
|
|
290
320
|
if (anchor.symbol === null) {
|
|
291
|
-
const decision = effectVerdict(anchor, pathResult.existence);
|
|
321
|
+
const decision = effectVerdict(anchor, pathResult.existence, observationStage);
|
|
292
322
|
return {
|
|
293
323
|
...unknownAnchor(anchor, evidence, decision.reason),
|
|
294
324
|
verdict: decision.verdict,
|
|
@@ -321,7 +351,7 @@ function projectAnchor({ anchor, querySet, outcomes, statusReady }) {
|
|
|
321
351
|
candidates: symbolResult.candidates,
|
|
322
352
|
};
|
|
323
353
|
|
|
324
|
-
const decision = effectVerdict(anchor, symbolResult.existence);
|
|
354
|
+
const decision = effectVerdict(anchor, symbolResult.existence, observationStage);
|
|
325
355
|
if (symbolResult.existence !== 'present') {
|
|
326
356
|
return {
|
|
327
357
|
...unknownAnchor(anchor, evidence, decision.reason),
|
|
@@ -366,15 +396,18 @@ function projectAnchor({ anchor, querySet, outcomes, statusReady }) {
|
|
|
366
396
|
|
|
367
397
|
/** 収集済みsensor outcomeから、anchor到達範囲だけのbounded projectionを作る。 */
|
|
368
398
|
export function projectTodoStructureSourceEvidence({
|
|
369
|
-
structureSet, collected, effectiveTransforms = null,
|
|
399
|
+
structureSet, collected, effectiveTransforms = null, observationStage = 'current',
|
|
370
400
|
} = {}) {
|
|
371
401
|
assertStructureSet(structureSet);
|
|
402
|
+
if (!['current', 'planned', 'final'].includes(observationStage)) {
|
|
403
|
+
throw new TypeError('observationStageが不正');
|
|
404
|
+
}
|
|
372
405
|
const querySet = buildTodoStructureSensorQuerySet(structureSet, { effectiveTransforms });
|
|
373
406
|
const outcomes = indexOutcomes(querySet, collected);
|
|
374
407
|
const statusQuery = querySet.queries[0];
|
|
375
408
|
const statusOutcome = outcomes.get(statusQuery.id);
|
|
376
409
|
const anchors = graphAnchors(structureSet, effectiveTransforms).map((anchor) => projectAnchor({
|
|
377
|
-
anchor, querySet, outcomes, statusReady: statusOutcome.outcome === 'ready',
|
|
410
|
+
anchor, querySet, outcomes, statusReady: statusOutcome.outcome === 'ready', observationStage,
|
|
378
411
|
}));
|
|
379
412
|
const projection = {
|
|
380
413
|
schema: TODO_STRUCTURE_SOURCE_PROJECTION_SCHEMA,
|
|
@@ -400,7 +433,7 @@ export function projectTodoStructureSourceEvidence({
|
|
|
400
433
|
|
|
401
434
|
/** 実sensor収集とpure projectionを同じ既存adapter経由で行う。 */
|
|
402
435
|
export async function collectTodoStructureSourceEvidence({
|
|
403
|
-
cwd, structureSet, effectiveTransforms = null,
|
|
436
|
+
cwd, structureSet, effectiveTransforms = null, observationStage = 'current',
|
|
404
437
|
execute = undefined, inspectAffectedPath = undefined,
|
|
405
438
|
} = {}) {
|
|
406
439
|
const querySet = buildTodoStructureSensorQuerySet(structureSet, { effectiveTransforms });
|
|
@@ -412,6 +445,8 @@ export async function collectTodoStructureSourceEvidence({
|
|
|
412
445
|
});
|
|
413
446
|
return {
|
|
414
447
|
query_set: querySet,
|
|
415
|
-
projection: projectTodoStructureSourceEvidence({
|
|
448
|
+
projection: projectTodoStructureSourceEvidence({
|
|
449
|
+
structureSet, collected, effectiveTransforms, observationStage,
|
|
450
|
+
}),
|
|
416
451
|
};
|
|
417
452
|
}
|