@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/todo-cli.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import { readProjectExternalPane, resolveProjectIdentity } from './project-ident
|
|
|
31
31
|
import { layoutTodoGantt } from './todo-gantt-layout.mjs';
|
|
32
32
|
import { TODO_GANTT_SCOPES } from './todo-gantt-scope.mjs';
|
|
33
33
|
import { loadTodoGanttPresentation } from './todo-gantt-presentation.mjs';
|
|
34
|
+
import { loadTodoStructurePresentation } from './todo-structure-presentation.mjs';
|
|
34
35
|
import { startTodoGanttLiveServer } from './todo-gantt-live.mjs';
|
|
35
36
|
import {
|
|
36
37
|
renderTodoGanttHtml,
|
|
@@ -39,6 +40,7 @@ import {
|
|
|
39
40
|
import { verifyNarrativeAnchors } from './todo-narrative-anchor.mjs';
|
|
40
41
|
import {
|
|
41
42
|
appendTodoEvent,
|
|
43
|
+
appendTodoStructureRealization,
|
|
42
44
|
applyPhaseTodoRevision,
|
|
43
45
|
applyTodoRevision,
|
|
44
46
|
applyTodoRevisionSet,
|
|
@@ -51,6 +53,10 @@ import {
|
|
|
51
53
|
readTodoIndependenceArtifact,
|
|
52
54
|
readTodoSeamProposalArtifact,
|
|
53
55
|
readTodoStore,
|
|
56
|
+
readTodoStructureBinding,
|
|
57
|
+
readTodoStructureRealizationChain,
|
|
58
|
+
readTodoStructureSource,
|
|
59
|
+
todoStructureSourceRef,
|
|
54
60
|
resolveTodoStartRetractionBinding,
|
|
55
61
|
readTodoWitnessSet,
|
|
56
62
|
todoWitnessRef,
|
|
@@ -59,9 +65,28 @@ import {
|
|
|
59
65
|
rebuildTodoSnapshot,
|
|
60
66
|
writeTodoIndependenceArtifact,
|
|
61
67
|
writeTodoSeamProposalArtifact,
|
|
68
|
+
writeTodoStructureBinding,
|
|
69
|
+
writeTodoStructureCompileArtifact,
|
|
70
|
+
writeTodoStructureFinalization,
|
|
71
|
+
writeTodoStructureSource,
|
|
62
72
|
verifyEffectivePhaseTodoRevisionSources,
|
|
63
73
|
verifyTodoRevisionSources,
|
|
64
74
|
} from './todo-store.mjs';
|
|
75
|
+
import {
|
|
76
|
+
TODO_STRUCTURE_BINDING_SCHEMA,
|
|
77
|
+
explainTodoStructureRealization,
|
|
78
|
+
explainTodoStructureSet,
|
|
79
|
+
} from './todo-structure-contracts.mjs';
|
|
80
|
+
import { collectTodoStructureSourceEvidence } from './todo-structure-source-adapter.mjs';
|
|
81
|
+
import { collectTodoStructureGitProvenance } from './todo-structure-git-adapter.mjs';
|
|
82
|
+
import { compileTodoStructureOverlay } from './todo-structure-overlay.mjs';
|
|
83
|
+
import {
|
|
84
|
+
buildTodoStructureCompileArtifact,
|
|
85
|
+
projectTodoStructureEffective,
|
|
86
|
+
readTodoStructureFinalizationState,
|
|
87
|
+
readTodoStructureFinalizationsForStatus,
|
|
88
|
+
readTodoStructureState,
|
|
89
|
+
} from './todo-structure-store.mjs';
|
|
65
90
|
import { withStartRetractionGuard } from './runtime-pull-intake.mjs';
|
|
66
91
|
import {
|
|
67
92
|
appendTodoExtraction,
|
|
@@ -159,6 +184,9 @@ const TODO_SCHEMA_COMMANDS = Object.freeze({
|
|
|
159
184
|
title: 'lattice.phase_todo_revision.v3', file: 'lattice.phase_todo_revision.v3.schema.json',
|
|
160
185
|
},
|
|
161
186
|
migrate: { title: 'lattice.todo_extraction.v3', file: 'lattice.todo_extraction.v3.schema.json' },
|
|
187
|
+
structure: {
|
|
188
|
+
title: 'lattice.todo_structure_set.v1', file: 'lattice.todo_structure_set.v1.schema.json',
|
|
189
|
+
},
|
|
162
190
|
});
|
|
163
191
|
|
|
164
192
|
async function runTodoSchemaCommand(command, stdout) {
|
|
@@ -207,7 +235,7 @@ function internalFailure(stderr, error) {
|
|
|
207
235
|
}
|
|
208
236
|
|
|
209
237
|
const TODO_COMMAND_NAMES = Object.freeze([
|
|
210
|
-
'status', 'show', 'note', 'bindings', 'independence', 'seam-profile', 'seam-proposal',
|
|
238
|
+
'status', 'show', 'note', 'bindings', 'independence', 'structure', 'seam-profile', 'seam-proposal',
|
|
211
239
|
'verify', 'snapshot', 'gantt', 'dashboard', 'phase', 'migrate', 'start', 'block',
|
|
212
240
|
'unblock', 'done', 'reopen', 'evidence', 'split', 'revise', 'revise-phase', 'revise-set',
|
|
213
241
|
]);
|
|
@@ -553,6 +581,14 @@ async function readWitnessSetInput(repoRoot, inputRef) {
|
|
|
553
581
|
return witnessSet;
|
|
554
582
|
}
|
|
555
583
|
|
|
584
|
+
/** structure draftはpointer診断のためcontract違反でもparseし、判定はdry-runへ返す。 */
|
|
585
|
+
async function readStructureSetDraft(repoRoot, inputRef) {
|
|
586
|
+
return readJsonInput(repoRoot, inputRef, {
|
|
587
|
+
validate: () => true,
|
|
588
|
+
invalidCode: 'INVALID_TODO_STRUCTURE_SET',
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
556
592
|
function mutationActor(env) {
|
|
557
593
|
const entries = ACTOR_ENV_KEYS.map((key) => ({ key, value: env[key] }));
|
|
558
594
|
const missingEnvironment = entries
|
|
@@ -1016,6 +1052,9 @@ async function dependencyConnect({
|
|
|
1016
1052
|
async function phaseStatus({ repoRoot, planKey }) {
|
|
1017
1053
|
const store = await readTodoStore({ repoRoot });
|
|
1018
1054
|
const [member] = selectMembers(store, planKey);
|
|
1055
|
+
const structureFinalization = await readTodoStructureFinalizationState({
|
|
1056
|
+
repoRoot, store, planKey: member.plan.plan_key,
|
|
1057
|
+
});
|
|
1019
1058
|
// ADR 0147以降、phase無しplan(v1/v2/v3)もreadTodoStoreが導出済みの暗黙terminal-audit
|
|
1020
1059
|
// Phaseをmember.phasesへ積んでいる(snapshot artifactの形式は変えない・v1にはphasesキーが
|
|
1021
1060
|
// 無いのでsnapshot.phasesは直接読まない)。ここでPHASE_UNAVAILABLEへ拒否せず、その暗黙Phase
|
|
@@ -1027,13 +1066,26 @@ async function phaseStatus({ repoRoot, planKey }) {
|
|
|
1027
1066
|
const phases = member.phases.map((phase) => ({
|
|
1028
1067
|
...phase,
|
|
1029
1068
|
guidance: phase.status === 'gate_ready'
|
|
1030
|
-
?
|
|
1069
|
+
? [
|
|
1070
|
+
auditGateGuidance(member.plan.plan_key, phase.phase_id),
|
|
1071
|
+
...(structureFinalization.required && structureFinalization.status !== 'fresh'
|
|
1072
|
+
? [`構造finalizationが${structureFinalization.status}です。先に lattice todo structure finalize --plan ${member.plan.plan_key} --json を実行してください。`]
|
|
1073
|
+
: []),
|
|
1074
|
+
].join('\n') : null,
|
|
1031
1075
|
}));
|
|
1032
1076
|
const result = {
|
|
1033
|
-
schema: 'lattice.phase_status_result.
|
|
1077
|
+
schema: 'lattice.phase_status_result.v2', project_id: store.project_id,
|
|
1034
1078
|
plan_key: member.plan.plan_key, plan_version: member.plan.plan_version,
|
|
1035
1079
|
journal_head_digest: member.journal.events.at(-1).event_digest,
|
|
1036
|
-
implicit, phases,
|
|
1080
|
+
implicit, phases,
|
|
1081
|
+
structure_finalization: {
|
|
1082
|
+
enabled: structureFinalization.enabled, required: structureFinalization.required,
|
|
1083
|
+
status: structureFinalization.status, reason: structureFinalization.reason,
|
|
1084
|
+
stale_reasons: structureFinalization.stale_reasons,
|
|
1085
|
+
next_command: structureFinalization.required && structureFinalization.status !== 'fresh'
|
|
1086
|
+
? `lattice todo structure finalize --plan ${member.plan.plan_key} --json` : null,
|
|
1087
|
+
},
|
|
1088
|
+
result_digest: '',
|
|
1037
1089
|
};
|
|
1038
1090
|
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
1039
1091
|
return result;
|
|
@@ -1544,6 +1596,7 @@ async function status({ repoRoot }) {
|
|
|
1544
1596
|
parallelCandidates: await readTodoParallelCandidatesForStatus({
|
|
1545
1597
|
repoRoot, store, gitHead: currentHeadSha, changedPathsSince,
|
|
1546
1598
|
}),
|
|
1599
|
+
structureFinalizations: await readTodoStructureFinalizationsForStatus({ repoRoot, store }),
|
|
1547
1600
|
});
|
|
1548
1601
|
}
|
|
1549
1602
|
|
|
@@ -1737,6 +1790,498 @@ function requireCleanWorktree(repoRoot) {
|
|
|
1737
1790
|
}
|
|
1738
1791
|
}
|
|
1739
1792
|
|
|
1793
|
+
function structureGitIdentity(repoRoot, baselineSha, violations) {
|
|
1794
|
+
let currentHeadSha = null;
|
|
1795
|
+
try {
|
|
1796
|
+
currentHeadSha = gitSync(['rev-parse', 'HEAD'], {
|
|
1797
|
+
cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
1798
|
+
}).replace(/\r?\n$/u, '');
|
|
1799
|
+
} catch {
|
|
1800
|
+
violations.push({
|
|
1801
|
+
code: 'git_head_unresolved', path: '/baseline_sha',
|
|
1802
|
+
expected: 'current repository HEAD commit', actual: null,
|
|
1803
|
+
next_action: 'repair_git_head_then_rerun_structure_input_dry_run',
|
|
1804
|
+
});
|
|
1805
|
+
return { currentHeadSha: null };
|
|
1806
|
+
}
|
|
1807
|
+
if (!/^[0-9a-f]{40}$/u.test(currentHeadSha)) {
|
|
1808
|
+
violations.push({
|
|
1809
|
+
code: 'git_head_invalid', path: '/baseline_sha',
|
|
1810
|
+
expected: '40 lowercase hex commit OID', actual: currentHeadSha,
|
|
1811
|
+
next_action: 'repair_git_head_then_rerun_structure_input_dry_run',
|
|
1812
|
+
});
|
|
1813
|
+
return { currentHeadSha: null };
|
|
1814
|
+
}
|
|
1815
|
+
try {
|
|
1816
|
+
gitSync(['cat-file', '-e', `${baselineSha}^{commit}`], {
|
|
1817
|
+
cwd: repoRoot, stdio: ['ignore', 'ignore', 'ignore'],
|
|
1818
|
+
});
|
|
1819
|
+
} catch {
|
|
1820
|
+
violations.push({
|
|
1821
|
+
code: 'baseline_commit_unresolved', path: '/baseline_sha',
|
|
1822
|
+
expected: 'commit object reachable in this repository', actual: baselineSha,
|
|
1823
|
+
next_action: 'fetch_the_baseline_or_correct_baseline_sha',
|
|
1824
|
+
});
|
|
1825
|
+
return { currentHeadSha };
|
|
1826
|
+
}
|
|
1827
|
+
try {
|
|
1828
|
+
gitSync(['merge-base', '--is-ancestor', baselineSha, currentHeadSha], {
|
|
1829
|
+
cwd: repoRoot, stdio: ['ignore', 'ignore', 'ignore'],
|
|
1830
|
+
});
|
|
1831
|
+
} catch {
|
|
1832
|
+
violations.push({
|
|
1833
|
+
code: 'baseline_not_ancestor', path: '/baseline_sha',
|
|
1834
|
+
expected: { ancestor_of: currentHeadSha }, actual: baselineSha,
|
|
1835
|
+
next_action: 'select_an_ancestor_baseline_or_rebase_the_structure_input',
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
return { currentHeadSha };
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
async function analyzeStructureInput({ repoRoot, planKey, inputRef }) {
|
|
1842
|
+
const structureSet = await readStructureSetDraft(repoRoot, inputRef);
|
|
1843
|
+
const violations = [];
|
|
1844
|
+
const explained = explainTodoStructureSet(structureSet);
|
|
1845
|
+
if (!explained.valid) {
|
|
1846
|
+
violations.push({
|
|
1847
|
+
code: explained.reason,
|
|
1848
|
+
path: explained.path,
|
|
1849
|
+
...(explained.detail === undefined ? {} : { actual: explained.detail }),
|
|
1850
|
+
next_action: 'correct_the_reported_pointer_then_rerun_structure_input_dry_run',
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
const store = await readTodoStore({ repoRoot });
|
|
1855
|
+
const member = store.members.find(({ descriptor }) => descriptor.plan_key === planKey) ?? null;
|
|
1856
|
+
if (member === null) {
|
|
1857
|
+
violations.push({
|
|
1858
|
+
code: 'plan_not_active', path: '/plan_key', expected: planKey,
|
|
1859
|
+
actual: typeof structureSet?.plan_key === 'string' ? structureSet.plan_key : null,
|
|
1860
|
+
next_action: 'define_the_plan_before_adding_structure_input',
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
let currentHeadSha = null;
|
|
1865
|
+
if (explained.valid && member !== null) {
|
|
1866
|
+
const identities = [
|
|
1867
|
+
['project_id', store.project_id, structureSet.project_id],
|
|
1868
|
+
['plan_key', planKey, structureSet.plan_key],
|
|
1869
|
+
['plan_version', member.plan.plan_version, structureSet.plan_version],
|
|
1870
|
+
['topology_digest', member.plan.topology_digest, structureSet.topology_digest],
|
|
1871
|
+
];
|
|
1872
|
+
for (const [field, expected, actual] of identities) {
|
|
1873
|
+
if (actual !== expected) {
|
|
1874
|
+
violations.push({
|
|
1875
|
+
code: `${field}_mismatch`, path: `/${field}`, expected, actual,
|
|
1876
|
+
next_action: 'regenerate_structure_input_for_the_active_plan_identity',
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
const expectedTaskIds = member.tasks.filter(({ status }) => status !== 'done')
|
|
1881
|
+
.map(({ task_id: taskId }) => taskId);
|
|
1882
|
+
const coverage = explainTodoStructureSet(structureSet, { expectedTaskIds });
|
|
1883
|
+
if (!coverage.valid && ['coverage_missing', 'coverage_extra'].includes(coverage.reason)) {
|
|
1884
|
+
violations.push({
|
|
1885
|
+
code: coverage.reason, path: coverage.path,
|
|
1886
|
+
expected: { task_ids: [...expectedTaskIds].sort() },
|
|
1887
|
+
actual: coverage.detail,
|
|
1888
|
+
next_action: 'list_every_unfinished_task_as_graph_or_excluded',
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
({ currentHeadSha } = structureGitIdentity(repoRoot, structureSet.baseline_sha, violations));
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
const bounded = violations.slice(0, 64);
|
|
1895
|
+
const result = {
|
|
1896
|
+
schema: 'lattice.todo_structure_input_dry_run_result.v1',
|
|
1897
|
+
valid: bounded.length === 0,
|
|
1898
|
+
project_id: member?.plan.project_id ?? null,
|
|
1899
|
+
plan_key: member?.plan.plan_key ?? planKey,
|
|
1900
|
+
plan_version: member?.plan.plan_version ?? null,
|
|
1901
|
+
input_ref: inputRef,
|
|
1902
|
+
source_ref: todoStructureSourceRef(planKey),
|
|
1903
|
+
current_head_sha: currentHeadSha,
|
|
1904
|
+
violations: bounded,
|
|
1905
|
+
overflow_count: Math.max(0, violations.length - bounded.length),
|
|
1906
|
+
next_action: bounded.length === 0
|
|
1907
|
+
? `lattice todo structure input --plan ${planKey} --input ${inputRef}`
|
|
1908
|
+
: 'correct_the_reported_violations_then_rerun_structure_input_dry_run',
|
|
1909
|
+
result_digest: '',
|
|
1910
|
+
};
|
|
1911
|
+
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
1912
|
+
return { result, structureSet };
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
async function structureInputDryRun({ repoRoot, planKey, inputRef }) {
|
|
1916
|
+
return (await analyzeStructureInput({ repoRoot, planKey, inputRef })).result;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
async function structureInput({ repoRoot, planKey, inputRef }) {
|
|
1920
|
+
const analyzed = await analyzeStructureInput({ repoRoot, planKey, inputRef });
|
|
1921
|
+
if (!analyzed.result.valid) {
|
|
1922
|
+
throw new TodoStoreError('INVALID_TODO_STRUCTURE_SET', 'structure_input_dry_run_failed', undefined, {
|
|
1923
|
+
violations: analyzed.result.violations,
|
|
1924
|
+
overflow_count: analyzed.result.overflow_count,
|
|
1925
|
+
next_action: `lattice todo structure input --plan ${planKey} --input ${inputRef} --dry-run --json`,
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
const written = await writeTodoStructureSource({ repoRoot, structureSet: analyzed.structureSet });
|
|
1929
|
+
const result = {
|
|
1930
|
+
schema: 'lattice.todo_structure_input_result.v1',
|
|
1931
|
+
project_id: analyzed.structureSet.project_id,
|
|
1932
|
+
plan_key: analyzed.structureSet.plan_key,
|
|
1933
|
+
plan_version: analyzed.structureSet.plan_version,
|
|
1934
|
+
topology_digest: analyzed.structureSet.topology_digest,
|
|
1935
|
+
baseline_sha: analyzed.structureSet.baseline_sha,
|
|
1936
|
+
structure_set_digest: analyzed.structureSet.structure_set_digest,
|
|
1937
|
+
source_ref: written.ref,
|
|
1938
|
+
enabled: false,
|
|
1939
|
+
next_action: `commit ${written.ref}, then run lattice todo structure compile --plan ${planKey} --input ${written.ref}`,
|
|
1940
|
+
result_digest: '',
|
|
1941
|
+
};
|
|
1942
|
+
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
1943
|
+
return result;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
function structurePlanMember(store, requestedPlanKey) {
|
|
1947
|
+
if (requestedPlanKey !== null) {
|
|
1948
|
+
const member = store.members.find(({ plan }) => plan.plan_key === requestedPlanKey);
|
|
1949
|
+
if (member === undefined) {
|
|
1950
|
+
throw new TodoStoreError('STRUCTURE_PLAN_NOT_FOUND', 'plan_not_active', undefined, {
|
|
1951
|
+
plan_key: requestedPlanKey,
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
return member;
|
|
1955
|
+
}
|
|
1956
|
+
if (store.members.length !== 1) {
|
|
1957
|
+
throw new TodoStoreError('STRUCTURE_PLAN_AMBIGUOUS', 'plan_selection_ambiguous', undefined, {
|
|
1958
|
+
plan_keys: store.members.map(({ plan }) => plan.plan_key).sort(),
|
|
1959
|
+
next_action: 'rerun_with_plan_flag',
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
return store.members[0];
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
function structureNextActions({ coverage, planKey, findings = [], staleReasons = [] }) {
|
|
1966
|
+
const actions = [];
|
|
1967
|
+
const add = (value) => { if (!actions.includes(value)) actions.push(value); };
|
|
1968
|
+
if (coverage === 'missing') {
|
|
1969
|
+
add(`lattice todo structure input --plan ${planKey} --input <structure-set.json> --dry-run --json`);
|
|
1970
|
+
add(`lattice todo structure input --plan ${planKey} --input <structure-set.json>`);
|
|
1971
|
+
add(`lattice todo structure compile --plan ${planKey} --input .lattice/todo/structure/${planKey}.json`);
|
|
1972
|
+
} else if (['inconsistent', 'unknown'].includes(coverage)) {
|
|
1973
|
+
findings.forEach(({ next_action: nextAction }) => add(nextAction));
|
|
1974
|
+
add(`lattice todo structure input --plan ${planKey} --input <corrected-structure-set.json> --dry-run --json`);
|
|
1975
|
+
add(`lattice todo structure compile --plan ${planKey} --input .lattice/todo/structure/${planKey}.json`);
|
|
1976
|
+
} else if (coverage === 'stale') {
|
|
1977
|
+
if (staleReasons.includes('realization_head_digest')) {
|
|
1978
|
+
add(`lattice todo structure --plan ${planKey} --json`);
|
|
1979
|
+
} else {
|
|
1980
|
+
add(`lattice todo structure realize --plan ${planKey} --task <task-id> --input <realization.json>`);
|
|
1981
|
+
}
|
|
1982
|
+
add(`lattice todo structure finalize --plan ${planKey} --json`);
|
|
1983
|
+
} else if (coverage === 'superseded') {
|
|
1984
|
+
add(`lattice todo structure input --plan ${planKey} --input <migrated-structure-set.json> --dry-run --json`);
|
|
1985
|
+
} else if (coverage === 'consistent') {
|
|
1986
|
+
add(`lattice todo structure --plan ${planKey} --json`);
|
|
1987
|
+
add(`lattice todo structure realize --plan ${planKey} --task <task-id> --input <realization.json>`);
|
|
1988
|
+
add(`lattice todo structure finalize --plan ${planKey} --json`);
|
|
1989
|
+
}
|
|
1990
|
+
return actions;
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
async function structureCompile({ repoRoot, env, planKey, inputRef }) {
|
|
1994
|
+
const structureSet = await readStructureSetDraft(repoRoot, inputRef);
|
|
1995
|
+
const explained = explainTodoStructureSet(structureSet);
|
|
1996
|
+
if (!explained.valid) {
|
|
1997
|
+
throw new TodoStoreError('INVALID_TODO_STRUCTURE_SET', explained.reason, undefined, {
|
|
1998
|
+
input_ref: inputRef, path: explained.path,
|
|
1999
|
+
next_action: `lattice todo structure input --plan ${planKey} --input ${inputRef} --dry-run --json`,
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
const store = await readTodoStore({ repoRoot });
|
|
2003
|
+
const member = structurePlanMember(store, planKey);
|
|
2004
|
+
const source = await readTodoStructureSource({ repoRoot, planKey });
|
|
2005
|
+
if (source === null) {
|
|
2006
|
+
throw new TodoStoreError('STRUCTURE_SOURCE_MISSING', 'planned_source_missing', undefined, {
|
|
2007
|
+
plan_key: planKey,
|
|
2008
|
+
next_action: `lattice todo structure input --plan ${planKey} --input ${inputRef}`,
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
if (canonicalizeTodoArtifact(source) !== canonicalizeTodoArtifact(structureSet)) {
|
|
2012
|
+
throw new TodoStoreError('STRUCTURE_SOURCE_MISMATCH', 'compile_input_is_not_saved_source', undefined, {
|
|
2013
|
+
input_ref: inputRef, source_ref: todoStructureSourceRef(planKey),
|
|
2014
|
+
next_action: `lattice todo structure input --plan ${planKey} --input ${inputRef}`,
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
const existingBinding = await readTodoStructureBinding({
|
|
2018
|
+
repoRoot, planKey, planVersion: member.plan.plan_version,
|
|
2019
|
+
});
|
|
2020
|
+
if (existingBinding !== null) {
|
|
2021
|
+
throw new TodoStoreError('STRUCTURE_ALREADY_ENABLED', 'immutable_binding_exists', undefined, {
|
|
2022
|
+
plan_key: planKey, plan_version: member.plan.plan_version,
|
|
2023
|
+
next_action: `lattice todo structure --plan ${planKey} --json`,
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
const actor = mutationActor(env);
|
|
2027
|
+
const sourceEvidence = await collectTodoStructureSourceEvidence({ cwd: repoRoot, structureSet });
|
|
2028
|
+
const gitProvenance = collectTodoStructureGitProvenance({ repoRoot, structureSet });
|
|
2029
|
+
const realizations = [];
|
|
2030
|
+
for (const task of structureSet.tasks.filter(({ applicability }) => applicability === 'graph')) {
|
|
2031
|
+
realizations.push(...await readTodoStructureRealizationChain({
|
|
2032
|
+
repoRoot, structureSet, taskId: task.task_id,
|
|
2033
|
+
}));
|
|
2034
|
+
}
|
|
2035
|
+
const overlay = compileTodoStructureOverlay({
|
|
2036
|
+
structureSet,
|
|
2037
|
+
topology: mergedTopology(store),
|
|
2038
|
+
taskStates: structureSet.tasks.map(({ task_id: taskId }) => ({
|
|
2039
|
+
task_id: taskId,
|
|
2040
|
+
status: member.tasks.find(({ task_id: id }) => id === taskId)?.status ?? 'pending',
|
|
2041
|
+
})),
|
|
2042
|
+
sourceProjection: sourceEvidence.projection,
|
|
2043
|
+
gitProvenance,
|
|
2044
|
+
realizations,
|
|
2045
|
+
});
|
|
2046
|
+
const compiledAt = new Date().toISOString();
|
|
2047
|
+
const artifact = buildTodoStructureCompileArtifact({
|
|
2048
|
+
structureSet, sourceProjection: sourceEvidence.projection, gitProvenance, overlay,
|
|
2049
|
+
realizations, compiledAt, actor,
|
|
2050
|
+
});
|
|
2051
|
+
const written = await writeTodoStructureCompileArtifact({ repoRoot, artifact });
|
|
2052
|
+
let bindingRef = null;
|
|
2053
|
+
if (overlay.verdict === 'consistent') {
|
|
2054
|
+
const binding = {
|
|
2055
|
+
schema: TODO_STRUCTURE_BINDING_SCHEMA,
|
|
2056
|
+
project_id: structureSet.project_id, plan_key: structureSet.plan_key,
|
|
2057
|
+
plan_version: structureSet.plan_version, topology_digest: structureSet.topology_digest,
|
|
2058
|
+
profile: structureSet.profile, baseline_sha: structureSet.baseline_sha,
|
|
2059
|
+
structure_set_digest: structureSet.structure_set_digest,
|
|
2060
|
+
compiled_head_sha: artifact.current_head_sha,
|
|
2061
|
+
compile_artifact_digest: artifact.artifact_digest,
|
|
2062
|
+
activated_at: compiledAt, actor, binding_digest: '',
|
|
2063
|
+
};
|
|
2064
|
+
binding.binding_digest = todoSelfDigest(binding, 'binding_digest');
|
|
2065
|
+
bindingRef = (await writeTodoStructureBinding({ repoRoot, binding })).ref;
|
|
2066
|
+
}
|
|
2067
|
+
const nextActions = structureNextActions({
|
|
2068
|
+
coverage: overlay.verdict, planKey, findings: overlay.findings,
|
|
2069
|
+
});
|
|
2070
|
+
const result = {
|
|
2071
|
+
schema: 'lattice.todo_structure_compile_result.v1',
|
|
2072
|
+
project_id: structureSet.project_id, plan_key: planKey,
|
|
2073
|
+
plan_version: structureSet.plan_version, topology_digest: structureSet.topology_digest,
|
|
2074
|
+
structure_set_digest: structureSet.structure_set_digest,
|
|
2075
|
+
current_head_sha: artifact.current_head_sha,
|
|
2076
|
+
verdict: overlay.verdict, enabled: bindingRef !== null,
|
|
2077
|
+
artifact_ref: written.ref, binding_ref: bindingRef,
|
|
2078
|
+
finding_summary: overlay.finding_summary, findings: overlay.findings,
|
|
2079
|
+
next_actions: nextActions, result_digest: '',
|
|
2080
|
+
};
|
|
2081
|
+
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
2082
|
+
return result;
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
async function readStructureRealizationInput(repoRoot, inputRef) {
|
|
2086
|
+
let explained = null;
|
|
2087
|
+
return readJsonInput(repoRoot, inputRef, {
|
|
2088
|
+
validate: (value) => {
|
|
2089
|
+
explained = explainTodoStructureRealization(value);
|
|
2090
|
+
return explained.valid;
|
|
2091
|
+
},
|
|
2092
|
+
invalidCode: 'INVALID_TODO_STRUCTURE_REALIZATION',
|
|
2093
|
+
}).catch((error) => {
|
|
2094
|
+
if (error?.code === 'INVALID_TODO_STRUCTURE_REALIZATION' && explained !== null) {
|
|
2095
|
+
throw new TodoStoreError(error.code, explained.reason, undefined, {
|
|
2096
|
+
input_ref: inputRef, path: explained.path,
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
throw error;
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
async function readCurrentStructureEffective(repoRoot, structureSet) {
|
|
2104
|
+
const realizations = [];
|
|
2105
|
+
for (const task of structureSet.tasks.filter(({ applicability }) => applicability === 'graph')) {
|
|
2106
|
+
realizations.push(...await readTodoStructureRealizationChain({
|
|
2107
|
+
repoRoot, structureSet, taskId: task.task_id,
|
|
2108
|
+
}));
|
|
2109
|
+
}
|
|
2110
|
+
return projectTodoStructureEffective({ structureSet, realizations });
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
async function structureRealize({ repoRoot, env, planKey, taskId, inputRef }) {
|
|
2114
|
+
const realization = await readStructureRealizationInput(repoRoot, inputRef);
|
|
2115
|
+
const actor = mutationActor(env);
|
|
2116
|
+
if (realization.plan_key !== planKey || realization.task_id !== taskId) {
|
|
2117
|
+
throw new TodoStoreError('STRUCTURE_REALIZATION_BINDING_MISMATCH',
|
|
2118
|
+
'cli_target_mismatch', undefined, {
|
|
2119
|
+
expected: { plan_key: planKey, task_id: taskId },
|
|
2120
|
+
actual: { plan_key: realization.plan_key, task_id: realization.task_id },
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
if (canonicalizeTodoArtifact(realization.actor) !== canonicalizeTodoArtifact(actor)) {
|
|
2124
|
+
throw new TodoStoreError('STRUCTURE_REALIZATION_BINDING_MISMATCH',
|
|
2125
|
+
'actor_environment_mismatch', undefined, {
|
|
2126
|
+
expected_actor: actor, actual_actor: realization.actor,
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
const appended = await appendTodoStructureRealization({ repoRoot, realization });
|
|
2130
|
+
const structureSet = await readTodoStructureSource({ repoRoot, planKey });
|
|
2131
|
+
const effective = await readCurrentStructureEffective(repoRoot, structureSet);
|
|
2132
|
+
const taskProjection = effective.tasks.find(({ task_id: id }) => id === taskId);
|
|
2133
|
+
const result = {
|
|
2134
|
+
schema: 'lattice.todo_structure_realize_result.v1',
|
|
2135
|
+
project_id: realization.project_id, plan_key: planKey,
|
|
2136
|
+
plan_version: realization.plan_version, task_id: taskId,
|
|
2137
|
+
realization_ref: appended.ref,
|
|
2138
|
+
realization_digest: realization.realization_digest,
|
|
2139
|
+
previous_realization_digest: appended.previous_realization_digest,
|
|
2140
|
+
history_length: appended.history_length,
|
|
2141
|
+
effective: taskProjection,
|
|
2142
|
+
next_action: `lattice todo done --plan ${planKey} --task ${taskId} --evidence <file>`,
|
|
2143
|
+
result_digest: '',
|
|
2144
|
+
};
|
|
2145
|
+
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
2146
|
+
return result;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
async function structureFinalize({ repoRoot, env, planKey }) {
|
|
2150
|
+
const store = await readTodoStore({ repoRoot });
|
|
2151
|
+
const member = structurePlanMember(store, planKey);
|
|
2152
|
+
const source = await readTodoStructureSource({ repoRoot, planKey });
|
|
2153
|
+
const binding = await readTodoStructureBinding({
|
|
2154
|
+
repoRoot, planKey, planVersion: member.plan.plan_version,
|
|
2155
|
+
});
|
|
2156
|
+
if (source === null || binding === null
|
|
2157
|
+
|| source.structure_set_digest !== binding.structure_set_digest) {
|
|
2158
|
+
throw new TodoStoreError('STRUCTURE_FINALIZATION_UNAVAILABLE',
|
|
2159
|
+
'structure_not_enabled_or_stale', undefined, {
|
|
2160
|
+
plan_key: planKey,
|
|
2161
|
+
next_action: `lattice todo structure compile --plan ${planKey} --input .lattice/todo/structure/${planKey}.json`,
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
const incompleteTaskIds = member.tasks
|
|
2165
|
+
.filter(({ status }) => status !== 'done')
|
|
2166
|
+
.map(({ task_id: taskId }) => taskId).sort();
|
|
2167
|
+
if (incompleteTaskIds.length > 0) {
|
|
2168
|
+
throw new TodoStoreError('STRUCTURE_FINALIZATION_UNAVAILABLE',
|
|
2169
|
+
'plan_not_fully_done', undefined, {
|
|
2170
|
+
plan_key: planKey, incomplete_task_ids: incompleteTaskIds,
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
const realizations = [];
|
|
2174
|
+
const effectiveTransforms = new Map();
|
|
2175
|
+
for (const task of source.tasks.filter(({ applicability }) => applicability === 'graph')) {
|
|
2176
|
+
const chain = await readTodoStructureRealizationChain({
|
|
2177
|
+
repoRoot, structureSet: source, taskId: task.task_id,
|
|
2178
|
+
});
|
|
2179
|
+
const latest = chain.at(-1);
|
|
2180
|
+
if (latest === undefined) {
|
|
2181
|
+
throw new TodoStoreError('STRUCTURE_FINALIZATION_UNAVAILABLE',
|
|
2182
|
+
'realization_missing', undefined, {
|
|
2183
|
+
plan_key: planKey, task_id: task.task_id,
|
|
2184
|
+
next_action: `lattice todo structure realize --plan ${planKey} --task ${task.task_id} --input <realization.json>`,
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
realizations.push(...chain);
|
|
2188
|
+
effectiveTransforms.set(task.task_id, latest.realized);
|
|
2189
|
+
}
|
|
2190
|
+
const actor = mutationActor(env);
|
|
2191
|
+
const sourceEvidence = await collectTodoStructureSourceEvidence({
|
|
2192
|
+
cwd: repoRoot, structureSet: source, effectiveTransforms,
|
|
2193
|
+
});
|
|
2194
|
+
const gitProvenance = collectTodoStructureGitProvenance({ repoRoot, structureSet: source });
|
|
2195
|
+
const overlay = compileTodoStructureOverlay({
|
|
2196
|
+
structureSet: source,
|
|
2197
|
+
topology: mergedTopology(store),
|
|
2198
|
+
taskStates: source.tasks.map(({ task_id: taskId }) => ({ task_id: taskId, status: 'done' })),
|
|
2199
|
+
sourceProjection: sourceEvidence.projection,
|
|
2200
|
+
gitProvenance,
|
|
2201
|
+
realizations,
|
|
2202
|
+
});
|
|
2203
|
+
const artifact = buildTodoStructureCompileArtifact({
|
|
2204
|
+
structureSet: source, sourceProjection: sourceEvidence.projection,
|
|
2205
|
+
gitProvenance, overlay, realizations,
|
|
2206
|
+
compiledAt: new Date().toISOString(), actor,
|
|
2207
|
+
});
|
|
2208
|
+
let finalizationRef = null;
|
|
2209
|
+
if (overlay.verdict === 'consistent') {
|
|
2210
|
+
finalizationRef = (await writeTodoStructureFinalization({ repoRoot, artifact })).ref;
|
|
2211
|
+
}
|
|
2212
|
+
const finalizationNextActions = [...new Set([
|
|
2213
|
+
...overlay.findings.map(({ next_action: nextAction }) => nextAction),
|
|
2214
|
+
`lattice todo structure finalize --plan ${planKey} --json`,
|
|
2215
|
+
])];
|
|
2216
|
+
const result = {
|
|
2217
|
+
schema: 'lattice.todo_structure_finalize_result.v1',
|
|
2218
|
+
project_id: source.project_id, plan_key: source.plan_key,
|
|
2219
|
+
plan_version: source.plan_version, topology_digest: source.topology_digest,
|
|
2220
|
+
structure_set_digest: source.structure_set_digest,
|
|
2221
|
+
current_head_sha: artifact.current_head_sha,
|
|
2222
|
+
realization_head_digest: artifact.realization_head_digest,
|
|
2223
|
+
verdict: overlay.verdict, finalized: finalizationRef !== null,
|
|
2224
|
+
finalization_ref: finalizationRef,
|
|
2225
|
+
finalization_digest: finalizationRef === null ? null : artifact.artifact_digest,
|
|
2226
|
+
finding_summary: overlay.finding_summary, findings: overlay.findings,
|
|
2227
|
+
next_actions: finalizationRef === null
|
|
2228
|
+
? finalizationNextActions
|
|
2229
|
+
: [`lattice todo phase status --plan ${planKey}`],
|
|
2230
|
+
result_digest: '',
|
|
2231
|
+
};
|
|
2232
|
+
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
2233
|
+
return result;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
async function structure({ repoRoot, requestedPlanKey }) {
|
|
2237
|
+
const store = await readTodoStore({ repoRoot });
|
|
2238
|
+
const member = structurePlanMember(store, requestedPlanKey);
|
|
2239
|
+
const state = await readTodoStructureState({
|
|
2240
|
+
repoRoot, store, planKey: member.plan.plan_key,
|
|
2241
|
+
});
|
|
2242
|
+
const unboundVerdict = state.status === 'missing'
|
|
2243
|
+
&& state.reason === 'activation_binding_missing'
|
|
2244
|
+
&& ['inconsistent', 'unknown'].includes(state.compiled_verdict)
|
|
2245
|
+
? state.compiled_verdict : null;
|
|
2246
|
+
const coverage = unboundVerdict
|
|
2247
|
+
?? (state.status === 'fresh' ? state.effective_verdict : state.status);
|
|
2248
|
+
const findings = state.artifact?.overlay?.findings ?? [];
|
|
2249
|
+
const source = await readTodoStructureSource({ repoRoot, planKey: member.plan.plan_key });
|
|
2250
|
+
const effective = source !== null && source.plan_version === member.plan.plan_version
|
|
2251
|
+
? await readCurrentStructureEffective(repoRoot, source) : null;
|
|
2252
|
+
const nextActions = structureNextActions({
|
|
2253
|
+
coverage, planKey: member.plan.plan_key, findings, staleReasons: state.stale_reasons,
|
|
2254
|
+
});
|
|
2255
|
+
const finalization = await readTodoStructureFinalizationState({
|
|
2256
|
+
repoRoot, store, planKey: member.plan.plan_key,
|
|
2257
|
+
});
|
|
2258
|
+
const result = {
|
|
2259
|
+
schema: 'lattice.todo_structure_projection.v1',
|
|
2260
|
+
project_id: store.project_id, plan_key: member.plan.plan_key,
|
|
2261
|
+
plan_version: member.plan.plan_version, topology_digest: member.plan.topology_digest,
|
|
2262
|
+
coverage, freshness: state.status,
|
|
2263
|
+
enabled: state.binding_digest !== null,
|
|
2264
|
+
structure_set_digest: state.structure_set_digest,
|
|
2265
|
+
artifact_digest: state.artifact_digest,
|
|
2266
|
+
compiled_verdict: state.compiled_verdict,
|
|
2267
|
+
verdict: unboundVerdict ?? state.effective_verdict,
|
|
2268
|
+
stale_reasons: state.stale_reasons,
|
|
2269
|
+
graph: state.artifact?.overlay?.graph ?? null,
|
|
2270
|
+
finding_summary: state.artifact?.overlay?.finding_summary ?? null,
|
|
2271
|
+
findings,
|
|
2272
|
+
effective,
|
|
2273
|
+
finalization: {
|
|
2274
|
+
required: finalization.required, status: finalization.status,
|
|
2275
|
+
reason: finalization.reason, finalization_digest: finalization.finalization_digest,
|
|
2276
|
+
stale_reasons: finalization.stale_reasons,
|
|
2277
|
+
},
|
|
2278
|
+
next_actions: nextActions,
|
|
2279
|
+
result_digest: '',
|
|
2280
|
+
};
|
|
2281
|
+
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
2282
|
+
return result;
|
|
2283
|
+
}
|
|
2284
|
+
|
|
1740
2285
|
async function independenceCompile({ repoRoot, planKey, inputRef }) {
|
|
1741
2286
|
const witnessSet = await readWitnessSetInput(repoRoot, inputRef);
|
|
1742
2287
|
requireCleanWorktree(repoRoot);
|
|
@@ -1759,6 +2304,9 @@ async function independenceCompile({ repoRoot, planKey, inputRef }) {
|
|
|
1759
2304
|
previousArtifact,
|
|
1760
2305
|
});
|
|
1761
2306
|
const { ref } = await writeTodoIndependenceArtifact({ repoRoot, artifact });
|
|
2307
|
+
// compile成功後にだけ、受理した宣言をruntime readerが要求するcanonical bytesで固定する。
|
|
2308
|
+
// inputがstore自身でも外部draftでも、唯一の正規writerを通して同じ契約へ収束させる。
|
|
2309
|
+
await writeTodoWitnessSet({ repoRoot, witnessSet });
|
|
1762
2310
|
|
|
1763
2311
|
const result = {
|
|
1764
2312
|
schema: 'lattice.todo_independence_compile_result.v2',
|
|
@@ -2375,13 +2923,14 @@ async function loadNarratives(store, repoRoot) {
|
|
|
2375
2923
|
* 一つの関数だけが組む。
|
|
2376
2924
|
*/
|
|
2377
2925
|
export async function ganttLiveHeadDigest({ repoRoot, store }) {
|
|
2378
|
-
const [independence, seamProposals, noteHeads] = await Promise.all([
|
|
2926
|
+
const [independence, seamProposals, noteHeads, structures] = await Promise.all([
|
|
2379
2927
|
independenceForGantt({ repoRoot, store }),
|
|
2380
2928
|
seamProposalsForGantt({ repoRoot, store }),
|
|
2381
2929
|
noteHeadsForGantt({ repoRoot, store }),
|
|
2930
|
+
loadTodoStructurePresentation({ repoRoot, readModel: store }),
|
|
2382
2931
|
]);
|
|
2383
2932
|
return digestTodoArtifact({
|
|
2384
|
-
schema: 'lattice.todo_gantt_live_head.
|
|
2933
|
+
schema: 'lattice.todo_gantt_live_head.v3',
|
|
2385
2934
|
manifest_digest: store.manifest.manifest_digest,
|
|
2386
2935
|
independence: independence === null ? null : independence.map((entry) => ({
|
|
2387
2936
|
plan_key: entry.plan_key,
|
|
@@ -2394,6 +2943,7 @@ export async function ganttLiveHeadDigest({ repoRoot, store }) {
|
|
|
2394
2943
|
projection_digest: digestTodoArtifact(entry),
|
|
2395
2944
|
})),
|
|
2396
2945
|
note_heads: noteHeads,
|
|
2946
|
+
structure_projection_digest: structures.projection_digest,
|
|
2397
2947
|
});
|
|
2398
2948
|
}
|
|
2399
2949
|
|
|
@@ -2585,10 +3135,11 @@ export async function renderTodoGanttForProject({
|
|
|
2585
3135
|
const presentation = await loadTodoGanttPresentation({ repoRoot, readModel: store });
|
|
2586
3136
|
const topology = mergedTopology(store);
|
|
2587
3137
|
const chain = projectTodoChainV1(topology);
|
|
2588
|
-
const [independence, seamProposals, notes] = await Promise.all([
|
|
3138
|
+
const [independence, seamProposals, notes, structures] = await Promise.all([
|
|
2589
3139
|
independenceForGantt({ repoRoot, store }),
|
|
2590
3140
|
seamProposalsForGantt({ repoRoot, store }),
|
|
2591
3141
|
notesForGantt({ repoRoot, store }),
|
|
3142
|
+
loadTodoStructurePresentation({ repoRoot, readModel: store }),
|
|
2592
3143
|
]);
|
|
2593
3144
|
const layout = layoutTodoGantt(store, chain, { scope, independence, seamProposals });
|
|
2594
3145
|
// When the diagram hides history, the page also carries the full diagram so
|
|
@@ -2623,6 +3174,7 @@ export async function renderTodoGanttForProject({
|
|
|
2623
3174
|
chain_digest: digestTodoArtifact(chain),
|
|
2624
3175
|
layout_digest: digestTodoArtifact(layout),
|
|
2625
3176
|
note_bindings_digest: digestTodoArtifact(notes.headBindings),
|
|
3177
|
+
structure_projection_digest: structures.projection_digest,
|
|
2626
3178
|
renderer_version: TODO_GANTT_RENDERER_VERSION,
|
|
2627
3179
|
project_display_name: identity.displayName,
|
|
2628
3180
|
folded_task_count: layout.scope.folded_task_count,
|
|
@@ -2637,6 +3189,7 @@ export async function renderTodoGanttForProject({
|
|
|
2637
3189
|
metadata,
|
|
2638
3190
|
noteContexts: notes.contexts,
|
|
2639
3191
|
noteWarnings: notes.warnings,
|
|
3192
|
+
structurePresentation: structures,
|
|
2640
3193
|
});
|
|
2641
3194
|
return { store, metadata, memberBindings, rendered };
|
|
2642
3195
|
}
|
|
@@ -2709,6 +3262,8 @@ function writesTodoStore(argv) {
|
|
|
2709
3262
|
case 'snapshot': return argv.includes('--rebuild');
|
|
2710
3263
|
case 'independence': return second === 'compile'
|
|
2711
3264
|
|| (second === 'witness' && ['migrate', 'scaffold'].includes(third));
|
|
3265
|
+
case 'structure': return ['compile', 'realize', 'finalize'].includes(second)
|
|
3266
|
+
|| (second === 'input' && !argv.includes('--dry-run'));
|
|
2712
3267
|
case 'seam-proposal': return ['compile', 'apply', 'land'].includes(second);
|
|
2713
3268
|
case 'evidence': return second === 'promote';
|
|
2714
3269
|
case 'dependency': return second === 'connect';
|
|
@@ -2873,6 +3428,22 @@ export async function runTodoCli({ argv, cwd, stdout, stderr, env = process.env
|
|
|
2873
3428
|
});
|
|
2874
3429
|
}
|
|
2875
3430
|
|
|
3431
|
+
if (argv[0] === 'structure' && ['input', 'compile'].includes(argv[1])
|
|
3432
|
+
&& argv[4] === '--input' && typeof argv[5] === 'string' && path.isAbsolute(argv[5])) {
|
|
3433
|
+
return typedArgumentFailure(stderr, 'INPUT_OUTSIDE_REPOSITORY', 'absolute_input_path_rejected', {
|
|
3434
|
+
argument: '--input', expected: 'repo-relative path', actual: 'absolute path',
|
|
3435
|
+
next_action: 'place_the_input_inside_the_repository_and_pass_a_repo_relative_path',
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
if (argv[0] === 'structure' && argv[1] === 'realize'
|
|
3440
|
+
&& argv[6] === '--input' && typeof argv[7] === 'string' && path.isAbsolute(argv[7])) {
|
|
3441
|
+
return typedArgumentFailure(stderr, 'INPUT_OUTSIDE_REPOSITORY', 'absolute_input_path_rejected', {
|
|
3442
|
+
argument: '--input', expected: 'repo-relative path', actual: 'absolute path',
|
|
3443
|
+
next_action: 'place_the_input_inside_the_repository_and_pass_a_repo_relative_path',
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
|
|
2876
3447
|
// `--schema --json`はstoreを読まない決定的な出力(`plan create --schema`と同じ規律)。
|
|
2877
3448
|
// 通常dispatchより前に処理し、repoRoot解決やdashboard daemon起動を経由させない。
|
|
2878
3449
|
if (argv.length === 3 && argv[1] === '--schema' && argv[2] === '--json'
|
|
@@ -2983,6 +3554,40 @@ export async function runTodoCli({ argv, cwd, stdout, stderr, env = process.env
|
|
|
2983
3554
|
action = (repoRoot) => independenceCompile({
|
|
2984
3555
|
repoRoot, planKey: argv[3], inputRef: argv[5],
|
|
2985
3556
|
});
|
|
3557
|
+
} else if (argv.length === 8 && argv[0] === 'structure' && argv[1] === 'input'
|
|
3558
|
+
&& argv[2] === '--plan' && isTodoIdentifier(argv[3])
|
|
3559
|
+
&& argv[4] === '--input' && isTodoRef(argv[5])
|
|
3560
|
+
&& argv[6] === '--dry-run' && argv[7] === '--json') {
|
|
3561
|
+
action = (repoRoot) => structureInputDryRun({
|
|
3562
|
+
repoRoot, planKey: argv[3], inputRef: argv[5],
|
|
3563
|
+
});
|
|
3564
|
+
} else if (argv.length === 6 && argv[0] === 'structure' && argv[1] === 'input'
|
|
3565
|
+
&& argv[2] === '--plan' && isTodoIdentifier(argv[3])
|
|
3566
|
+
&& argv[4] === '--input' && isTodoRef(argv[5])) {
|
|
3567
|
+
action = (repoRoot) => structureInput({
|
|
3568
|
+
repoRoot, planKey: argv[3], inputRef: argv[5],
|
|
3569
|
+
});
|
|
3570
|
+
} else if (argv.length === 6 && argv[0] === 'structure' && argv[1] === 'compile'
|
|
3571
|
+
&& argv[2] === '--plan' && isTodoIdentifier(argv[3])
|
|
3572
|
+
&& argv[4] === '--input' && isTodoRef(argv[5])) {
|
|
3573
|
+
action = (repoRoot) => structureCompile({
|
|
3574
|
+
repoRoot, env, planKey: argv[3], inputRef: argv[5],
|
|
3575
|
+
});
|
|
3576
|
+
} else if (argv.length === 8 && argv[0] === 'structure' && argv[1] === 'realize'
|
|
3577
|
+
&& argv[2] === '--plan' && isTodoIdentifier(argv[3])
|
|
3578
|
+
&& argv[4] === '--task' && isTodoIdentifier(argv[5])
|
|
3579
|
+
&& argv[6] === '--input' && isTodoRef(argv[7])) {
|
|
3580
|
+
action = (repoRoot) => structureRealize({
|
|
3581
|
+
repoRoot, env, planKey: argv[3], taskId: argv[5], inputRef: argv[7],
|
|
3582
|
+
});
|
|
3583
|
+
} else if (argv.length === 5 && argv[0] === 'structure' && argv[1] === 'finalize'
|
|
3584
|
+
&& argv[2] === '--plan' && isTodoIdentifier(argv[3]) && argv[4] === '--json') {
|
|
3585
|
+
action = (repoRoot) => structureFinalize({ repoRoot, env, planKey: argv[3] });
|
|
3586
|
+
} else if (argv.length === 2 && argv[0] === 'structure' && argv[1] === '--json') {
|
|
3587
|
+
action = (repoRoot) => structure({ repoRoot, requestedPlanKey: null });
|
|
3588
|
+
} else if (argv.length === 4 && argv[0] === 'structure'
|
|
3589
|
+
&& argv[1] === '--plan' && isTodoIdentifier(argv[2]) && argv[3] === '--json') {
|
|
3590
|
+
action = (repoRoot) => structure({ repoRoot, requestedPlanKey: argv[2] });
|
|
2986
3591
|
} else if ((argv.length === 1 && argv[0] === 'independence')
|
|
2987
3592
|
|| (argv.length === 2 && argv[0] === 'independence' && argv[1] === '--json')) {
|
|
2988
3593
|
action = (repoRoot) => independence({ repoRoot, requestedPlanKey: null });
|