@quolu/lattice 0.57.2 → 0.58.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.
- package/README.ja.md +32 -0
- package/README.md +33 -0
- package/docs/schemas/lattice.todo_structure_binding.v1.schema.json +47 -0
- package/docs/schemas/lattice.todo_structure_realization.v1.schema.json +55 -0
- package/docs/schemas/lattice.todo_structure_set.v1.schema.json +264 -0
- package/package.json +4 -1
- package/sensor/dist/bin/exact-traversal.d.ts +20 -0
- package/sensor/dist/bin/exact-traversal.d.ts.map +1 -0
- package/sensor/dist/bin/exact-traversal.js +17 -0
- package/sensor/dist/bin/exact-traversal.js.map +1 -0
- package/sensor/dist/bin/lattice-sensor.js +27 -8
- package/sensor/package.json +1 -1
- package/src/cli-help.mjs +16 -3
- package/src/dag-chain.mjs +37 -0
- package/src/project-cli.mjs +9 -3
- package/src/runtime-pull-intake.mjs +8 -4
- package/src/sensor-adapter.mjs +8 -2
- package/src/todo-chain.mjs +19 -5
- package/src/todo-cli.mjs +612 -7
- package/src/todo-gantt-html-independence.mjs +9 -1
- package/src/todo-gantt-html-style.mjs +8 -0
- package/src/todo-gantt-html.mjs +14 -4
- package/src/todo-gantt-structure.mjs +134 -0
- package/src/todo-status.mjs +32 -6
- package/src/todo-store.mjs +645 -27
- package/src/todo-structure-contracts.mjs +706 -0
- package/src/todo-structure-git-adapter.mjs +452 -0
- package/src/todo-structure-overlay.mjs +599 -0
- package/src/todo-structure-presentation.mjs +163 -0
- package/src/todo-structure-source-adapter.mjs +417 -0
- package/src/todo-structure-store.mjs +475 -0
package/src/cli-help.mjs
CHANGED
|
@@ -77,11 +77,13 @@ Read commands:
|
|
|
77
77
|
note list --plan <key> [--task <id>] --json # note全履歴の診断面(通常read/startでは不要)
|
|
78
78
|
bindings [--plan <key>] [--json] # compile_binding付きTaskをTODO identityつきで投影する
|
|
79
79
|
independence [--plan <key>] [--json] # readyを検証済み並列・要直列・未検査へ分けて投影する
|
|
80
|
+
structure --schema --json # plan定義後に入力する論理dataflow契約をstore非依存で返す
|
|
81
|
+
structure [--plan <key>] --json # 保存済み構造をsensor再実行なしでfreshness・finding付き投影する
|
|
80
82
|
seam-profile --plan <key> --file <path> [--json] # 係争fileの切断コスト内訳を投影する(read-only)
|
|
81
83
|
seam-proposal [--plan <key>] [--json] # 記録済みseam提案をsensor無しで投影する
|
|
82
84
|
verify [--plan <key>] [--json]
|
|
83
85
|
snapshot --rebuild --plan <key>
|
|
84
|
-
gantt serve --port <port> [--scope live|all] #
|
|
86
|
+
gantt serve --port <port> [--scope live|all] # 動的表示。opt-in済みplanは工程図と別の構造検査面を持つ
|
|
85
87
|
phase status --plan <key>
|
|
86
88
|
|
|
87
89
|
Write commands:
|
|
@@ -97,7 +99,7 @@ Write commands:
|
|
|
97
99
|
# 既定は全ready同時dispatch。直列にするには理由の申告後、再考を経て --serial-confirmed が要る
|
|
98
100
|
block --plan <key> --task <id> --reason <text>
|
|
99
101
|
unblock --plan <key> --task <id>
|
|
100
|
-
done --plan <key> --task <id> --evidence <file>
|
|
102
|
+
done --plan <key> --task <id> --evidence <file> # 構造対象taskはfresh realizationを要求する
|
|
101
103
|
reopen --plan <key> --task <id> --reason <text> [--override-reason <text>]
|
|
102
104
|
evidence promote --plan <key> --task <id> --evidence <file>
|
|
103
105
|
dependency connect --from-plan <key> --from-task <id> --to-plan <key> --to-task <id> --reason <text>
|
|
@@ -105,6 +107,16 @@ Write commands:
|
|
|
105
107
|
independence compile --plan <key> --input <file> # witness setとsensorから並列可否を記録する
|
|
106
108
|
independence witness migrate --plan <key> # revision後の宣言をtask migrationで写す
|
|
107
109
|
independence witness scaffold --plan <key> --input <draft> # 下書きとfresh観測から宣言を書き出す
|
|
110
|
+
structure input --plan <key> --input <file> --dry-run --json
|
|
111
|
+
# plan identity・topology・unfinished task coverage・baseline祖先を無変更で検査する
|
|
112
|
+
structure input --plan <key> --input <file>
|
|
113
|
+
# 検査済みplanned sourceをcanonical refへ保存する。compile成功までは有効化しない
|
|
114
|
+
structure compile --plan <key> --input <file>
|
|
115
|
+
# source graph・Git provenance・ToDo DAGを結合する。consistent時だけplanへimmutableに有効化する
|
|
116
|
+
structure realize --plan <key> --task <id> --input <file>
|
|
117
|
+
# 実装後の構造をappend-onlyで記録してからtodo doneへ進む
|
|
118
|
+
structure finalize --plan <key> --json
|
|
119
|
+
# 全対象task完了後、最終HEADと全realizationを再結合する。fresh consistentだけterminal受理へ進む
|
|
108
120
|
seam-proposal compile --plan <key> # 並列可否記録と実sensorからseam提案を記録する
|
|
109
121
|
seam-proposal apply --plan <key> # 記録済み提案を隔離worktreeで適用し五条件で採否を決める
|
|
110
122
|
seam-proposal land --plan <key> --names <file> # 採用された変換を本ツリーへ着地させる
|
|
@@ -112,7 +124,7 @@ Write commands:
|
|
|
112
124
|
revise --plan <key> --input <file>
|
|
113
125
|
revise-phase --plan <key> --input <file>
|
|
114
126
|
revise-set --input <file>
|
|
115
|
-
<revise|revise-phase|revise-set|migrate> --schema --json
|
|
127
|
+
<revise|revise-phase|revise-set|migrate|structure> --schema --json
|
|
116
128
|
# 実際に受理する最新契約のJSON Schemaを返す(storeを読まない)。
|
|
117
129
|
# 入力が契約に合わないときは、違反フィールドのpathがerror detailへ載る
|
|
118
130
|
phase review --plan <key> --phase <id> --reason <text>
|
|
@@ -224,6 +236,7 @@ const SUBCOMMAND_USAGE = Object.freeze({
|
|
|
224
236
|
'todo note list': 'todo note list --plan <key> [--task <id>] --json',
|
|
225
237
|
'todo bindings': 'todo bindings [--plan <key>] [--json]',
|
|
226
238
|
'todo independence': 'todo independence [--plan <key>] [--json] | compile --plan <key> --input <file> | witness migrate --plan <key>',
|
|
239
|
+
'todo structure': 'todo structure --schema --json | [--plan <key>] --json | input --plan <key> --input <file> [--dry-run --json] | compile --plan <key> --input <file> | realize --plan <key> --task <id> --input <file> | finalize --plan <key> --json',
|
|
227
240
|
'todo seam-profile': 'todo seam-profile --plan <key> --file <path> [--json]',
|
|
228
241
|
'todo seam-proposal': 'todo seam-proposal [--plan <key>] [--json] | compile --plan <key>',
|
|
229
242
|
'todo verify': 'todo verify [--plan <key>] [--json]',
|
package/src/dag-chain.mjs
CHANGED
|
@@ -259,3 +259,40 @@ export function analyzeDagChains(
|
|
|
259
259
|
topologicalOrder: order,
|
|
260
260
|
};
|
|
261
261
|
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 既存DAG正本と同じnormalize/cycle判定を使い、要求されたpairだけの到達性を返す。
|
|
265
|
+
* structure overlayが独自のnode/edge検証やcycle algorithmを持たないための狭い公開面。
|
|
266
|
+
*/
|
|
267
|
+
export function analyzeDagReachability(nodes, edges, pairs) {
|
|
268
|
+
if (!Array.isArray(pairs)) {
|
|
269
|
+
throw new DagChainError('DAG_INVALID_REACHABILITY_PAIRS', 'pairs must be an array');
|
|
270
|
+
}
|
|
271
|
+
const graph = normalizeGraph(nodes, edges);
|
|
272
|
+
topologicalOrder(nodes, graph.successors, graph.predecessors);
|
|
273
|
+
const nodeSet = new Set(nodes);
|
|
274
|
+
for (const pair of pairs) {
|
|
275
|
+
if (!Array.isArray(pair) || pair.length !== 2
|
|
276
|
+
|| !nodeSet.has(pair[0]) || !nodeSet.has(pair[1])) {
|
|
277
|
+
throw new DagChainError(
|
|
278
|
+
'DAG_INVALID_REACHABILITY_PAIRS',
|
|
279
|
+
'each pair must be a [from, to] using graph nodes',
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const reachedBySource = new Map();
|
|
284
|
+
for (const [from] of pairs) {
|
|
285
|
+
if (reachedBySource.has(from)) continue;
|
|
286
|
+
const reached = new Set([from]);
|
|
287
|
+
const pending = [from];
|
|
288
|
+
for (let cursor = 0; cursor < pending.length; cursor += 1) {
|
|
289
|
+
for (const successor of graph.successors.get(pending[cursor])) {
|
|
290
|
+
if (reached.has(successor)) continue;
|
|
291
|
+
reached.add(successor);
|
|
292
|
+
pending.push(successor);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
reachedBySource.set(from, reached);
|
|
296
|
+
}
|
|
297
|
+
return pairs.map(([from, to]) => reachedBySource.get(from).has(to));
|
|
298
|
+
}
|
package/src/project-cli.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import { projectTodoStatus } from './todo-status.mjs';
|
|
|
19
19
|
import { readTodoPlanNotesForStatus } from './todo-note-store.mjs';
|
|
20
20
|
import { projectIndependenceFrontier } from './todo-independence.mjs';
|
|
21
21
|
import { readTodoParallelCandidatesForStatus } from './todo-parallel-candidates.mjs';
|
|
22
|
+
import { readTodoStructureFinalizationsForStatus } from './todo-structure-store.mjs';
|
|
22
23
|
import { isTodoIndependenceLegacyMarker } from './todo-independence-contracts.mjs';
|
|
23
24
|
import { selectIndependenceGuidance } from './todo-independence-guidance.mjs';
|
|
24
25
|
import { ensureTodoDashboardActivity } from './todo-dashboard-registry.mjs';
|
|
@@ -273,6 +274,7 @@ async function resolveProjectState({ cwd, cliVersion }) {
|
|
|
273
274
|
const todo = projectTodoStatus(store, {
|
|
274
275
|
planNotes: await readTodoPlanNotesForStatus({ repoRoot, store }),
|
|
275
276
|
parallelCandidates: await readTodoParallelCandidatesForStatus({ repoRoot, store, gitHead }),
|
|
277
|
+
structureFinalizations: await readTodoStructureFinalizationsForStatus({ repoRoot, store }),
|
|
276
278
|
});
|
|
277
279
|
const activeRuns = todo.active_set.map((entry) => ({
|
|
278
280
|
plan_key: entry.plan_key, task_id: entry.task_id, label: entry.label,
|
|
@@ -283,14 +285,18 @@ async function resolveProjectState({ cwd, cliVersion }) {
|
|
|
283
285
|
: todo.next_ready.length > 0
|
|
284
286
|
? { command: `lattice todo start --plan ${todo.next_ready[0].plan_key} --task ${todo.next_ready[0].task_id}${todo.next_ready.length > 1 ? ' --parallel-frontier' : ''}`,
|
|
285
287
|
reason: todo.next_ready.length > 1 ? 'parallel_frontier_present' : 'next_ready_present' }
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
+
// 監査/構造finalization待ちが在る限り「残作業なし」と答えない。優先順位は
|
|
289
|
+
// active_run > next_ready > structure finalization > audit_pending > なし。
|
|
290
|
+
// ready frontierが在る間はADR 0063の並列開始コマンドが勝つ
|
|
288
291
|
// ——ここを監査で上書きするとdispatchが再直列化する。監査待ちはtodo statusの
|
|
289
292
|
// audit_pending欄に常在するので、順位を下げても消えない。
|
|
290
293
|
// commandはverbatim実行可能で読み取り専用のものにする。`phase review`は
|
|
291
294
|
// `--reason <text>`のplaceholderを含みjournalを書き換えるので置かない
|
|
292
295
|
// (`todo phase status`の結果には既に両分岐のguidanceが載っている)。
|
|
293
|
-
: todo.
|
|
296
|
+
: todo.structure_finalization_pending.length > 0
|
|
297
|
+
? { command: todo.structure_finalization_pending[0].next_commands[0],
|
|
298
|
+
reason: 'structure_finalization_pending' }
|
|
299
|
+
: todo.audit_pending.length > 0
|
|
294
300
|
? { command: `lattice todo phase status --plan ${todo.audit_pending[0].plan_key}`,
|
|
295
301
|
reason: 'audit_pending' }
|
|
296
302
|
: { command: 'lattice todo status', reason: 'no_ready_task' };
|
|
@@ -681,8 +681,10 @@ export async function intakePullTask({ repoRoot, runDir, taskId, environment = p
|
|
|
681
681
|
affected_tests: [], graph_evidence: [], witness_provenance: {}, manifest_digest: '',
|
|
682
682
|
};
|
|
683
683
|
manifest.manifest_digest = selfDigest(manifest, 'manifest_digest');
|
|
684
|
-
verdict.state
|
|
685
|
-
|
|
684
|
+
if (verdict.state !== 'hold') {
|
|
685
|
+
verdict.state = 'hold'; verdict.reason = 'boundary_unverified';
|
|
686
|
+
verdict.next_action = 'declare_and_recompile'; verdict.detail = { cause: error.code };
|
|
687
|
+
}
|
|
686
688
|
}
|
|
687
689
|
const constraint = verdict.state === 'none'
|
|
688
690
|
? planningConstraint(verdict.artifact, taskId, state, member) : null;
|
|
@@ -760,8 +762,10 @@ export async function intakePullTask({ repoRoot, runDir, taskId, environment = p
|
|
|
760
762
|
affected_tests: [], graph_evidence: [], witness_provenance: {}, manifest_digest: '',
|
|
761
763
|
};
|
|
762
764
|
manifest.manifest_digest = selfDigest(manifest, 'manifest_digest');
|
|
763
|
-
verdict.state
|
|
764
|
-
|
|
765
|
+
if (verdict.state !== 'hold') {
|
|
766
|
+
verdict.state = 'hold'; verdict.reason = 'boundary_unverified';
|
|
767
|
+
verdict.next_action = 'declare_and_recompile'; verdict.detail = { cause: error.code };
|
|
768
|
+
}
|
|
765
769
|
}
|
|
766
770
|
const packet = packetFor({ meta: current.meta, taskId, baseSha, manifest });
|
|
767
771
|
const worktreePath = await ensureScriptedWorktree({ repoRoot, runDir, packet });
|
package/src/sensor-adapter.mjs
CHANGED
|
@@ -277,7 +277,7 @@ async function resolveExactSymbol({ cwd, target, execute }) {
|
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
async function collectOne({ cwd, query, execute, inspectAffectedPath }) {
|
|
280
|
-
const { id, operation, target } = query;
|
|
280
|
+
const { id, operation, target, exact_path: exactPath } = query;
|
|
281
281
|
if (!OPERATIONS.has(operation)) {
|
|
282
282
|
return { id, operation, outcome: 'unsupported' };
|
|
283
283
|
}
|
|
@@ -332,6 +332,11 @@ async function collectOne({ cwd, query, execute, inspectAffectedPath }) {
|
|
|
332
332
|
if (operation !== 'status' && (typeof target !== 'string' || target.length === 0)) {
|
|
333
333
|
return { id, operation, outcome: 'unresolved' };
|
|
334
334
|
}
|
|
335
|
+
if (Object.hasOwn(query, 'exact_path')
|
|
336
|
+
&& (!['callers', 'callees'].includes(operation)
|
|
337
|
+
|| typeof exactPath !== 'string' || exactPath.length === 0 || path.isAbsolute(exactPath))) {
|
|
338
|
+
return { id, operation, target, outcome: 'unresolved' };
|
|
339
|
+
}
|
|
335
340
|
|
|
336
341
|
const command = {
|
|
337
342
|
operation,
|
|
@@ -343,7 +348,8 @@ async function collectOne({ cwd, query, execute, inspectAffectedPath }) {
|
|
|
343
348
|
args: operation === 'status'
|
|
344
349
|
? ['status', '.', '--json']
|
|
345
350
|
: ['callers', 'callees'].includes(operation)
|
|
346
|
-
? [operation, target, '--path', '.', '--limit', '200',
|
|
351
|
+
? [operation, target, '--path', '.', '--limit', '200',
|
|
352
|
+
...(exactPath === undefined ? [] : ['--exact-path', exactPath]), '--json']
|
|
347
353
|
: [operation, target, '--path', '.', '--json'],
|
|
348
354
|
};
|
|
349
355
|
const result = await executeCommand(execute, command);
|
package/src/todo-chain.mjs
CHANGED
|
@@ -90,11 +90,7 @@ function normalizeOptions(options) {
|
|
|
90
90
|
return { countCap, representativeLimit };
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
mergedTodoTopology,
|
|
95
|
-
options,
|
|
96
|
-
) {
|
|
97
|
-
const { countCap, representativeLimit } = normalizeOptions(options);
|
|
93
|
+
function normalizeTodoTopology(mergedTodoTopology) {
|
|
98
94
|
if (!isPlainObject(mergedTodoTopology)) {
|
|
99
95
|
fail('TODO_CHAIN_INVALID_TOPOLOGY', 'mergedTodoTopology must be an object');
|
|
100
96
|
}
|
|
@@ -145,6 +141,24 @@ export function projectTodoChainV1(
|
|
|
145
141
|
);
|
|
146
142
|
}
|
|
147
143
|
}
|
|
144
|
+
return { refsByKey, edges };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** structure等の消費側が同じToDo node/edge正本を使うためのlossless投影。 */
|
|
148
|
+
export function projectTodoTopologyDagV1(mergedTodoTopology) {
|
|
149
|
+
const { refsByKey, edges } = normalizeTodoTopology(mergedTodoTopology);
|
|
150
|
+
return {
|
|
151
|
+
nodes: [...refsByKey].map(([key, ref]) => ({ key, ref: { ...ref } })),
|
|
152
|
+
edges: edges.map(([from, to]) => ({ from, to })),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function projectTodoChainV1(
|
|
157
|
+
mergedTodoTopology,
|
|
158
|
+
options,
|
|
159
|
+
) {
|
|
160
|
+
const { countCap, representativeLimit } = normalizeOptions(options);
|
|
161
|
+
const { refsByKey, edges } = normalizeTodoTopology(mergedTodoTopology);
|
|
148
162
|
|
|
149
163
|
let analysis;
|
|
150
164
|
try {
|