@quolu/lattice 0.57.3 → 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/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/sensor-adapter.mjs +8 -2
- package/src/todo-chain.mjs +19 -5
- package/src/todo-cli.mjs +610 -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 +608 -14
- package/src/todo-structure-authoritative-observation.mjs +162 -0
- package/src/todo-structure-contracts.mjs +706 -0
- package/src/todo-structure-git-adapter.mjs +452 -0
- package/src/todo-structure-overlay.mjs +593 -0
- package/src/todo-structure-presentation.mjs +163 -0
- package/src/todo-structure-source-adapter.mjs +452 -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,27 @@ 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 { collectTodoStructureAuthoritativeObservation } from './todo-structure-authoritative-observation.mjs';
|
|
81
|
+
import { compileTodoStructureOverlay } from './todo-structure-overlay.mjs';
|
|
82
|
+
import {
|
|
83
|
+
buildTodoStructureCompileArtifact,
|
|
84
|
+
projectTodoStructureEffective,
|
|
85
|
+
readTodoStructureFinalizationState,
|
|
86
|
+
readTodoStructureFinalizationsForStatus,
|
|
87
|
+
readTodoStructureState,
|
|
88
|
+
} from './todo-structure-store.mjs';
|
|
65
89
|
import { withStartRetractionGuard } from './runtime-pull-intake.mjs';
|
|
66
90
|
import {
|
|
67
91
|
appendTodoExtraction,
|
|
@@ -159,6 +183,9 @@ const TODO_SCHEMA_COMMANDS = Object.freeze({
|
|
|
159
183
|
title: 'lattice.phase_todo_revision.v3', file: 'lattice.phase_todo_revision.v3.schema.json',
|
|
160
184
|
},
|
|
161
185
|
migrate: { title: 'lattice.todo_extraction.v3', file: 'lattice.todo_extraction.v3.schema.json' },
|
|
186
|
+
structure: {
|
|
187
|
+
title: 'lattice.todo_structure_set.v1', file: 'lattice.todo_structure_set.v1.schema.json',
|
|
188
|
+
},
|
|
162
189
|
});
|
|
163
190
|
|
|
164
191
|
async function runTodoSchemaCommand(command, stdout) {
|
|
@@ -207,7 +234,7 @@ function internalFailure(stderr, error) {
|
|
|
207
234
|
}
|
|
208
235
|
|
|
209
236
|
const TODO_COMMAND_NAMES = Object.freeze([
|
|
210
|
-
'status', 'show', 'note', 'bindings', 'independence', 'seam-profile', 'seam-proposal',
|
|
237
|
+
'status', 'show', 'note', 'bindings', 'independence', 'structure', 'seam-profile', 'seam-proposal',
|
|
211
238
|
'verify', 'snapshot', 'gantt', 'dashboard', 'phase', 'migrate', 'start', 'block',
|
|
212
239
|
'unblock', 'done', 'reopen', 'evidence', 'split', 'revise', 'revise-phase', 'revise-set',
|
|
213
240
|
]);
|
|
@@ -553,6 +580,14 @@ async function readWitnessSetInput(repoRoot, inputRef) {
|
|
|
553
580
|
return witnessSet;
|
|
554
581
|
}
|
|
555
582
|
|
|
583
|
+
/** structure draftはpointer診断のためcontract違反でもparseし、判定はdry-runへ返す。 */
|
|
584
|
+
async function readStructureSetDraft(repoRoot, inputRef) {
|
|
585
|
+
return readJsonInput(repoRoot, inputRef, {
|
|
586
|
+
validate: () => true,
|
|
587
|
+
invalidCode: 'INVALID_TODO_STRUCTURE_SET',
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
556
591
|
function mutationActor(env) {
|
|
557
592
|
const entries = ACTOR_ENV_KEYS.map((key) => ({ key, value: env[key] }));
|
|
558
593
|
const missingEnvironment = entries
|
|
@@ -1016,6 +1051,9 @@ async function dependencyConnect({
|
|
|
1016
1051
|
async function phaseStatus({ repoRoot, planKey }) {
|
|
1017
1052
|
const store = await readTodoStore({ repoRoot });
|
|
1018
1053
|
const [member] = selectMembers(store, planKey);
|
|
1054
|
+
const structureFinalization = await readTodoStructureFinalizationState({
|
|
1055
|
+
repoRoot, store, planKey: member.plan.plan_key,
|
|
1056
|
+
});
|
|
1019
1057
|
// ADR 0147以降、phase無しplan(v1/v2/v3)もreadTodoStoreが導出済みの暗黙terminal-audit
|
|
1020
1058
|
// Phaseをmember.phasesへ積んでいる(snapshot artifactの形式は変えない・v1にはphasesキーが
|
|
1021
1059
|
// 無いのでsnapshot.phasesは直接読まない)。ここでPHASE_UNAVAILABLEへ拒否せず、その暗黙Phase
|
|
@@ -1027,13 +1065,26 @@ async function phaseStatus({ repoRoot, planKey }) {
|
|
|
1027
1065
|
const phases = member.phases.map((phase) => ({
|
|
1028
1066
|
...phase,
|
|
1029
1067
|
guidance: phase.status === 'gate_ready'
|
|
1030
|
-
?
|
|
1068
|
+
? [
|
|
1069
|
+
auditGateGuidance(member.plan.plan_key, phase.phase_id),
|
|
1070
|
+
...(structureFinalization.required && structureFinalization.status !== 'fresh'
|
|
1071
|
+
? [`構造finalizationが${structureFinalization.status}です。先に lattice todo structure finalize --plan ${member.plan.plan_key} --json を実行してください。`]
|
|
1072
|
+
: []),
|
|
1073
|
+
].join('\n') : null,
|
|
1031
1074
|
}));
|
|
1032
1075
|
const result = {
|
|
1033
|
-
schema: 'lattice.phase_status_result.
|
|
1076
|
+
schema: 'lattice.phase_status_result.v2', project_id: store.project_id,
|
|
1034
1077
|
plan_key: member.plan.plan_key, plan_version: member.plan.plan_version,
|
|
1035
1078
|
journal_head_digest: member.journal.events.at(-1).event_digest,
|
|
1036
|
-
implicit, phases,
|
|
1079
|
+
implicit, phases,
|
|
1080
|
+
structure_finalization: {
|
|
1081
|
+
enabled: structureFinalization.enabled, required: structureFinalization.required,
|
|
1082
|
+
status: structureFinalization.status, reason: structureFinalization.reason,
|
|
1083
|
+
stale_reasons: structureFinalization.stale_reasons,
|
|
1084
|
+
next_command: structureFinalization.required && structureFinalization.status !== 'fresh'
|
|
1085
|
+
? `lattice todo structure finalize --plan ${member.plan.plan_key} --json` : null,
|
|
1086
|
+
},
|
|
1087
|
+
result_digest: '',
|
|
1037
1088
|
};
|
|
1038
1089
|
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
1039
1090
|
return result;
|
|
@@ -1544,6 +1595,7 @@ async function status({ repoRoot }) {
|
|
|
1544
1595
|
parallelCandidates: await readTodoParallelCandidatesForStatus({
|
|
1545
1596
|
repoRoot, store, gitHead: currentHeadSha, changedPathsSince,
|
|
1546
1597
|
}),
|
|
1598
|
+
structureFinalizations: await readTodoStructureFinalizationsForStatus({ repoRoot, store }),
|
|
1547
1599
|
});
|
|
1548
1600
|
}
|
|
1549
1601
|
|
|
@@ -1737,6 +1789,500 @@ function requireCleanWorktree(repoRoot) {
|
|
|
1737
1789
|
}
|
|
1738
1790
|
}
|
|
1739
1791
|
|
|
1792
|
+
function structureGitIdentity(repoRoot, baselineSha, violations) {
|
|
1793
|
+
let currentHeadSha = null;
|
|
1794
|
+
try {
|
|
1795
|
+
currentHeadSha = gitSync(['rev-parse', 'HEAD'], {
|
|
1796
|
+
cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
1797
|
+
}).replace(/\r?\n$/u, '');
|
|
1798
|
+
} catch {
|
|
1799
|
+
violations.push({
|
|
1800
|
+
code: 'git_head_unresolved', path: '/baseline_sha',
|
|
1801
|
+
expected: 'current repository HEAD commit', actual: null,
|
|
1802
|
+
next_action: 'repair_git_head_then_rerun_structure_input_dry_run',
|
|
1803
|
+
});
|
|
1804
|
+
return { currentHeadSha: null };
|
|
1805
|
+
}
|
|
1806
|
+
if (!/^[0-9a-f]{40}$/u.test(currentHeadSha)) {
|
|
1807
|
+
violations.push({
|
|
1808
|
+
code: 'git_head_invalid', path: '/baseline_sha',
|
|
1809
|
+
expected: '40 lowercase hex commit OID', actual: currentHeadSha,
|
|
1810
|
+
next_action: 'repair_git_head_then_rerun_structure_input_dry_run',
|
|
1811
|
+
});
|
|
1812
|
+
return { currentHeadSha: null };
|
|
1813
|
+
}
|
|
1814
|
+
try {
|
|
1815
|
+
gitSync(['cat-file', '-e', `${baselineSha}^{commit}`], {
|
|
1816
|
+
cwd: repoRoot, stdio: ['ignore', 'ignore', 'ignore'],
|
|
1817
|
+
});
|
|
1818
|
+
} catch {
|
|
1819
|
+
violations.push({
|
|
1820
|
+
code: 'baseline_commit_unresolved', path: '/baseline_sha',
|
|
1821
|
+
expected: 'commit object reachable in this repository', actual: baselineSha,
|
|
1822
|
+
next_action: 'fetch_the_baseline_or_correct_baseline_sha',
|
|
1823
|
+
});
|
|
1824
|
+
return { currentHeadSha };
|
|
1825
|
+
}
|
|
1826
|
+
try {
|
|
1827
|
+
gitSync(['merge-base', '--is-ancestor', baselineSha, currentHeadSha], {
|
|
1828
|
+
cwd: repoRoot, stdio: ['ignore', 'ignore', 'ignore'],
|
|
1829
|
+
});
|
|
1830
|
+
} catch {
|
|
1831
|
+
violations.push({
|
|
1832
|
+
code: 'baseline_not_ancestor', path: '/baseline_sha',
|
|
1833
|
+
expected: { ancestor_of: currentHeadSha }, actual: baselineSha,
|
|
1834
|
+
next_action: 'select_an_ancestor_baseline_or_rebase_the_structure_input',
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
return { currentHeadSha };
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
async function analyzeStructureInput({ repoRoot, planKey, inputRef }) {
|
|
1841
|
+
const structureSet = await readStructureSetDraft(repoRoot, inputRef);
|
|
1842
|
+
const violations = [];
|
|
1843
|
+
const explained = explainTodoStructureSet(structureSet);
|
|
1844
|
+
if (!explained.valid) {
|
|
1845
|
+
violations.push({
|
|
1846
|
+
code: explained.reason,
|
|
1847
|
+
path: explained.path,
|
|
1848
|
+
...(explained.detail === undefined ? {} : { actual: explained.detail }),
|
|
1849
|
+
next_action: 'correct_the_reported_pointer_then_rerun_structure_input_dry_run',
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
const store = await readTodoStore({ repoRoot });
|
|
1854
|
+
const member = store.members.find(({ descriptor }) => descriptor.plan_key === planKey) ?? null;
|
|
1855
|
+
if (member === null) {
|
|
1856
|
+
violations.push({
|
|
1857
|
+
code: 'plan_not_active', path: '/plan_key', expected: planKey,
|
|
1858
|
+
actual: typeof structureSet?.plan_key === 'string' ? structureSet.plan_key : null,
|
|
1859
|
+
next_action: 'define_the_plan_before_adding_structure_input',
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
let currentHeadSha = null;
|
|
1864
|
+
if (explained.valid && member !== null) {
|
|
1865
|
+
const identities = [
|
|
1866
|
+
['project_id', store.project_id, structureSet.project_id],
|
|
1867
|
+
['plan_key', planKey, structureSet.plan_key],
|
|
1868
|
+
['plan_version', member.plan.plan_version, structureSet.plan_version],
|
|
1869
|
+
['topology_digest', member.plan.topology_digest, structureSet.topology_digest],
|
|
1870
|
+
];
|
|
1871
|
+
for (const [field, expected, actual] of identities) {
|
|
1872
|
+
if (actual !== expected) {
|
|
1873
|
+
violations.push({
|
|
1874
|
+
code: `${field}_mismatch`, path: `/${field}`, expected, actual,
|
|
1875
|
+
next_action: 'regenerate_structure_input_for_the_active_plan_identity',
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
const expectedTaskIds = member.tasks.filter(({ status }) => status !== 'done')
|
|
1880
|
+
.map(({ task_id: taskId }) => taskId);
|
|
1881
|
+
const coverage = explainTodoStructureSet(structureSet, { expectedTaskIds });
|
|
1882
|
+
if (!coverage.valid && ['coverage_missing', 'coverage_extra'].includes(coverage.reason)) {
|
|
1883
|
+
violations.push({
|
|
1884
|
+
code: coverage.reason, path: coverage.path,
|
|
1885
|
+
expected: { task_ids: [...expectedTaskIds].sort() },
|
|
1886
|
+
actual: coverage.detail,
|
|
1887
|
+
next_action: 'list_every_unfinished_task_as_graph_or_excluded',
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
({ currentHeadSha } = structureGitIdentity(repoRoot, structureSet.baseline_sha, violations));
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
const bounded = violations.slice(0, 64);
|
|
1894
|
+
const result = {
|
|
1895
|
+
schema: 'lattice.todo_structure_input_dry_run_result.v1',
|
|
1896
|
+
valid: bounded.length === 0,
|
|
1897
|
+
project_id: member?.plan.project_id ?? null,
|
|
1898
|
+
plan_key: member?.plan.plan_key ?? planKey,
|
|
1899
|
+
plan_version: member?.plan.plan_version ?? null,
|
|
1900
|
+
input_ref: inputRef,
|
|
1901
|
+
source_ref: todoStructureSourceRef(planKey),
|
|
1902
|
+
current_head_sha: currentHeadSha,
|
|
1903
|
+
violations: bounded,
|
|
1904
|
+
overflow_count: Math.max(0, violations.length - bounded.length),
|
|
1905
|
+
next_action: bounded.length === 0
|
|
1906
|
+
? `lattice todo structure input --plan ${planKey} --input ${inputRef}`
|
|
1907
|
+
: 'correct_the_reported_violations_then_rerun_structure_input_dry_run',
|
|
1908
|
+
result_digest: '',
|
|
1909
|
+
};
|
|
1910
|
+
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
1911
|
+
return { result, structureSet };
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
async function structureInputDryRun({ repoRoot, planKey, inputRef }) {
|
|
1915
|
+
return (await analyzeStructureInput({ repoRoot, planKey, inputRef })).result;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
async function structureInput({ repoRoot, planKey, inputRef }) {
|
|
1919
|
+
const analyzed = await analyzeStructureInput({ repoRoot, planKey, inputRef });
|
|
1920
|
+
if (!analyzed.result.valid) {
|
|
1921
|
+
throw new TodoStoreError('INVALID_TODO_STRUCTURE_SET', 'structure_input_dry_run_failed', undefined, {
|
|
1922
|
+
violations: analyzed.result.violations,
|
|
1923
|
+
overflow_count: analyzed.result.overflow_count,
|
|
1924
|
+
next_action: `lattice todo structure input --plan ${planKey} --input ${inputRef} --dry-run --json`,
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
const written = await writeTodoStructureSource({ repoRoot, structureSet: analyzed.structureSet });
|
|
1928
|
+
const result = {
|
|
1929
|
+
schema: 'lattice.todo_structure_input_result.v1',
|
|
1930
|
+
project_id: analyzed.structureSet.project_id,
|
|
1931
|
+
plan_key: analyzed.structureSet.plan_key,
|
|
1932
|
+
plan_version: analyzed.structureSet.plan_version,
|
|
1933
|
+
topology_digest: analyzed.structureSet.topology_digest,
|
|
1934
|
+
baseline_sha: analyzed.structureSet.baseline_sha,
|
|
1935
|
+
structure_set_digest: analyzed.structureSet.structure_set_digest,
|
|
1936
|
+
source_ref: written.ref,
|
|
1937
|
+
enabled: false,
|
|
1938
|
+
next_action: `commit ${written.ref}, then run lattice todo structure compile --plan ${planKey} --input ${written.ref}`,
|
|
1939
|
+
result_digest: '',
|
|
1940
|
+
};
|
|
1941
|
+
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
1942
|
+
return result;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
function structurePlanMember(store, requestedPlanKey) {
|
|
1946
|
+
if (requestedPlanKey !== null) {
|
|
1947
|
+
const member = store.members.find(({ plan }) => plan.plan_key === requestedPlanKey);
|
|
1948
|
+
if (member === undefined) {
|
|
1949
|
+
throw new TodoStoreError('STRUCTURE_PLAN_NOT_FOUND', 'plan_not_active', undefined, {
|
|
1950
|
+
plan_key: requestedPlanKey,
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
return member;
|
|
1954
|
+
}
|
|
1955
|
+
if (store.members.length !== 1) {
|
|
1956
|
+
throw new TodoStoreError('STRUCTURE_PLAN_AMBIGUOUS', 'plan_selection_ambiguous', undefined, {
|
|
1957
|
+
plan_keys: store.members.map(({ plan }) => plan.plan_key).sort(),
|
|
1958
|
+
next_action: 'rerun_with_plan_flag',
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
return store.members[0];
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
function structureNextActions({ coverage, planKey, findings = [], staleReasons = [] }) {
|
|
1965
|
+
const actions = [];
|
|
1966
|
+
const add = (value) => { if (!actions.includes(value)) actions.push(value); };
|
|
1967
|
+
if (coverage === 'missing') {
|
|
1968
|
+
add(`lattice todo structure input --plan ${planKey} --input <structure-set.json> --dry-run --json`);
|
|
1969
|
+
add(`lattice todo structure input --plan ${planKey} --input <structure-set.json>`);
|
|
1970
|
+
add(`lattice todo structure compile --plan ${planKey} --input .lattice/todo/structure/${planKey}.json`);
|
|
1971
|
+
} else if (['inconsistent', 'unknown'].includes(coverage)) {
|
|
1972
|
+
findings.forEach(({ next_action: nextAction }) => add(nextAction));
|
|
1973
|
+
add(`lattice todo structure input --plan ${planKey} --input <corrected-structure-set.json> --dry-run --json`);
|
|
1974
|
+
add(`lattice todo structure compile --plan ${planKey} --input .lattice/todo/structure/${planKey}.json`);
|
|
1975
|
+
} else if (coverage === 'stale') {
|
|
1976
|
+
if (staleReasons.includes('realization_head_digest')) {
|
|
1977
|
+
add(`lattice todo structure --plan ${planKey} --json`);
|
|
1978
|
+
} else {
|
|
1979
|
+
add(`lattice todo structure realize --plan ${planKey} --task <task-id> --input <realization.json>`);
|
|
1980
|
+
}
|
|
1981
|
+
add(`lattice todo structure finalize --plan ${planKey} --json`);
|
|
1982
|
+
} else if (coverage === 'superseded') {
|
|
1983
|
+
add(`lattice todo structure input --plan ${planKey} --input <migrated-structure-set.json> --dry-run --json`);
|
|
1984
|
+
} else if (coverage === 'consistent') {
|
|
1985
|
+
add(`lattice todo structure --plan ${planKey} --json`);
|
|
1986
|
+
add(`lattice todo structure realize --plan ${planKey} --task <task-id> --input <realization.json>`);
|
|
1987
|
+
add(`lattice todo structure finalize --plan ${planKey} --json`);
|
|
1988
|
+
}
|
|
1989
|
+
return actions;
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
async function structureCompile({ repoRoot, env, planKey, inputRef }) {
|
|
1993
|
+
const structureSet = await readStructureSetDraft(repoRoot, inputRef);
|
|
1994
|
+
const explained = explainTodoStructureSet(structureSet);
|
|
1995
|
+
if (!explained.valid) {
|
|
1996
|
+
throw new TodoStoreError('INVALID_TODO_STRUCTURE_SET', explained.reason, undefined, {
|
|
1997
|
+
input_ref: inputRef, path: explained.path,
|
|
1998
|
+
next_action: `lattice todo structure input --plan ${planKey} --input ${inputRef} --dry-run --json`,
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
const store = await readTodoStore({ repoRoot });
|
|
2002
|
+
const member = structurePlanMember(store, planKey);
|
|
2003
|
+
const source = await readTodoStructureSource({ repoRoot, planKey });
|
|
2004
|
+
if (source === null) {
|
|
2005
|
+
throw new TodoStoreError('STRUCTURE_SOURCE_MISSING', 'planned_source_missing', undefined, {
|
|
2006
|
+
plan_key: planKey,
|
|
2007
|
+
next_action: `lattice todo structure input --plan ${planKey} --input ${inputRef}`,
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
if (canonicalizeTodoArtifact(source) !== canonicalizeTodoArtifact(structureSet)) {
|
|
2011
|
+
throw new TodoStoreError('STRUCTURE_SOURCE_MISMATCH', 'compile_input_is_not_saved_source', undefined, {
|
|
2012
|
+
input_ref: inputRef, source_ref: todoStructureSourceRef(planKey),
|
|
2013
|
+
next_action: `lattice todo structure input --plan ${planKey} --input ${inputRef}`,
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
const existingBinding = await readTodoStructureBinding({
|
|
2017
|
+
repoRoot, planKey, planVersion: member.plan.plan_version,
|
|
2018
|
+
});
|
|
2019
|
+
if (existingBinding !== null) {
|
|
2020
|
+
throw new TodoStoreError('STRUCTURE_ALREADY_ENABLED', 'immutable_binding_exists', undefined, {
|
|
2021
|
+
plan_key: planKey, plan_version: member.plan.plan_version,
|
|
2022
|
+
next_action: `lattice todo structure --plan ${planKey} --json`,
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
const actor = mutationActor(env);
|
|
2026
|
+
const observation = await collectTodoStructureAuthoritativeObservation({ repoRoot, structureSet });
|
|
2027
|
+
const sourceEvidence = observation.source_evidence;
|
|
2028
|
+
const gitProvenance = observation.git_provenance;
|
|
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 observation = await collectTodoStructureAuthoritativeObservation({
|
|
2192
|
+
repoRoot, structureSet: source, effectiveTransforms,
|
|
2193
|
+
});
|
|
2194
|
+
const sourceEvidence = observation.source_evidence;
|
|
2195
|
+
const gitProvenance = observation.git_provenance;
|
|
2196
|
+
const overlay = compileTodoStructureOverlay({
|
|
2197
|
+
structureSet: source,
|
|
2198
|
+
topology: mergedTopology(store),
|
|
2199
|
+
taskStates: source.tasks.map(({ task_id: taskId }) => ({ task_id: taskId, status: 'done' })),
|
|
2200
|
+
sourceProjection: sourceEvidence.projection,
|
|
2201
|
+
gitProvenance,
|
|
2202
|
+
realizations,
|
|
2203
|
+
});
|
|
2204
|
+
const artifact = buildTodoStructureCompileArtifact({
|
|
2205
|
+
structureSet: source, sourceProjection: sourceEvidence.projection,
|
|
2206
|
+
gitProvenance, overlay, realizations,
|
|
2207
|
+
compiledAt: new Date().toISOString(), actor,
|
|
2208
|
+
});
|
|
2209
|
+
let finalizationRef = null;
|
|
2210
|
+
if (overlay.verdict === 'consistent') {
|
|
2211
|
+
finalizationRef = (await writeTodoStructureFinalization({ repoRoot, artifact })).ref;
|
|
2212
|
+
}
|
|
2213
|
+
const finalizationNextActions = [...new Set([
|
|
2214
|
+
...overlay.findings.map(({ next_action: nextAction }) => nextAction),
|
|
2215
|
+
`lattice todo structure finalize --plan ${planKey} --json`,
|
|
2216
|
+
])];
|
|
2217
|
+
const result = {
|
|
2218
|
+
schema: 'lattice.todo_structure_finalize_result.v1',
|
|
2219
|
+
project_id: source.project_id, plan_key: source.plan_key,
|
|
2220
|
+
plan_version: source.plan_version, topology_digest: source.topology_digest,
|
|
2221
|
+
structure_set_digest: source.structure_set_digest,
|
|
2222
|
+
current_head_sha: artifact.current_head_sha,
|
|
2223
|
+
realization_head_digest: artifact.realization_head_digest,
|
|
2224
|
+
verdict: overlay.verdict, finalized: finalizationRef !== null,
|
|
2225
|
+
finalization_ref: finalizationRef,
|
|
2226
|
+
finalization_digest: finalizationRef === null ? null : artifact.artifact_digest,
|
|
2227
|
+
finding_summary: overlay.finding_summary, findings: overlay.findings,
|
|
2228
|
+
next_actions: finalizationRef === null
|
|
2229
|
+
? finalizationNextActions
|
|
2230
|
+
: [`lattice todo phase status --plan ${planKey}`],
|
|
2231
|
+
result_digest: '',
|
|
2232
|
+
};
|
|
2233
|
+
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
2234
|
+
return result;
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
async function structure({ repoRoot, requestedPlanKey }) {
|
|
2238
|
+
const store = await readTodoStore({ repoRoot });
|
|
2239
|
+
const member = structurePlanMember(store, requestedPlanKey);
|
|
2240
|
+
const state = await readTodoStructureState({
|
|
2241
|
+
repoRoot, store, planKey: member.plan.plan_key,
|
|
2242
|
+
});
|
|
2243
|
+
const unboundVerdict = state.status === 'missing'
|
|
2244
|
+
&& state.reason === 'activation_binding_missing'
|
|
2245
|
+
&& ['inconsistent', 'unknown'].includes(state.compiled_verdict)
|
|
2246
|
+
? state.compiled_verdict : null;
|
|
2247
|
+
const coverage = unboundVerdict
|
|
2248
|
+
?? (state.status === 'fresh' ? state.effective_verdict : state.status);
|
|
2249
|
+
const findings = state.artifact?.overlay?.findings ?? [];
|
|
2250
|
+
const source = await readTodoStructureSource({ repoRoot, planKey: member.plan.plan_key });
|
|
2251
|
+
const effective = source !== null && source.plan_version === member.plan.plan_version
|
|
2252
|
+
? await readCurrentStructureEffective(repoRoot, source) : null;
|
|
2253
|
+
const nextActions = structureNextActions({
|
|
2254
|
+
coverage, planKey: member.plan.plan_key, findings, staleReasons: state.stale_reasons,
|
|
2255
|
+
});
|
|
2256
|
+
const finalization = await readTodoStructureFinalizationState({
|
|
2257
|
+
repoRoot, store, planKey: member.plan.plan_key,
|
|
2258
|
+
});
|
|
2259
|
+
const result = {
|
|
2260
|
+
schema: 'lattice.todo_structure_projection.v1',
|
|
2261
|
+
project_id: store.project_id, plan_key: member.plan.plan_key,
|
|
2262
|
+
plan_version: member.plan.plan_version, topology_digest: member.plan.topology_digest,
|
|
2263
|
+
coverage, freshness: state.status,
|
|
2264
|
+
enabled: state.binding_digest !== null,
|
|
2265
|
+
structure_set_digest: state.structure_set_digest,
|
|
2266
|
+
artifact_digest: state.artifact_digest,
|
|
2267
|
+
compiled_verdict: state.compiled_verdict,
|
|
2268
|
+
verdict: unboundVerdict ?? state.effective_verdict,
|
|
2269
|
+
stale_reasons: state.stale_reasons,
|
|
2270
|
+
graph: state.artifact?.overlay?.graph ?? null,
|
|
2271
|
+
finding_summary: state.artifact?.overlay?.finding_summary ?? null,
|
|
2272
|
+
findings,
|
|
2273
|
+
effective,
|
|
2274
|
+
finalization: {
|
|
2275
|
+
required: finalization.required, status: finalization.status,
|
|
2276
|
+
reason: finalization.reason, finalization_digest: finalization.finalization_digest,
|
|
2277
|
+
stale_reasons: finalization.stale_reasons,
|
|
2278
|
+
},
|
|
2279
|
+
next_actions: nextActions,
|
|
2280
|
+
result_digest: '',
|
|
2281
|
+
};
|
|
2282
|
+
result.result_digest = todoSelfDigest(result, 'result_digest');
|
|
2283
|
+
return result;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
1740
2286
|
async function independenceCompile({ repoRoot, planKey, inputRef }) {
|
|
1741
2287
|
const witnessSet = await readWitnessSetInput(repoRoot, inputRef);
|
|
1742
2288
|
requireCleanWorktree(repoRoot);
|
|
@@ -2378,13 +2924,14 @@ async function loadNarratives(store, repoRoot) {
|
|
|
2378
2924
|
* 一つの関数だけが組む。
|
|
2379
2925
|
*/
|
|
2380
2926
|
export async function ganttLiveHeadDigest({ repoRoot, store }) {
|
|
2381
|
-
const [independence, seamProposals, noteHeads] = await Promise.all([
|
|
2927
|
+
const [independence, seamProposals, noteHeads, structures] = await Promise.all([
|
|
2382
2928
|
independenceForGantt({ repoRoot, store }),
|
|
2383
2929
|
seamProposalsForGantt({ repoRoot, store }),
|
|
2384
2930
|
noteHeadsForGantt({ repoRoot, store }),
|
|
2931
|
+
loadTodoStructurePresentation({ repoRoot, readModel: store }),
|
|
2385
2932
|
]);
|
|
2386
2933
|
return digestTodoArtifact({
|
|
2387
|
-
schema: 'lattice.todo_gantt_live_head.
|
|
2934
|
+
schema: 'lattice.todo_gantt_live_head.v3',
|
|
2388
2935
|
manifest_digest: store.manifest.manifest_digest,
|
|
2389
2936
|
independence: independence === null ? null : independence.map((entry) => ({
|
|
2390
2937
|
plan_key: entry.plan_key,
|
|
@@ -2397,6 +2944,7 @@ export async function ganttLiveHeadDigest({ repoRoot, store }) {
|
|
|
2397
2944
|
projection_digest: digestTodoArtifact(entry),
|
|
2398
2945
|
})),
|
|
2399
2946
|
note_heads: noteHeads,
|
|
2947
|
+
structure_projection_digest: structures.projection_digest,
|
|
2400
2948
|
});
|
|
2401
2949
|
}
|
|
2402
2950
|
|
|
@@ -2588,10 +3136,11 @@ export async function renderTodoGanttForProject({
|
|
|
2588
3136
|
const presentation = await loadTodoGanttPresentation({ repoRoot, readModel: store });
|
|
2589
3137
|
const topology = mergedTopology(store);
|
|
2590
3138
|
const chain = projectTodoChainV1(topology);
|
|
2591
|
-
const [independence, seamProposals, notes] = await Promise.all([
|
|
3139
|
+
const [independence, seamProposals, notes, structures] = await Promise.all([
|
|
2592
3140
|
independenceForGantt({ repoRoot, store }),
|
|
2593
3141
|
seamProposalsForGantt({ repoRoot, store }),
|
|
2594
3142
|
notesForGantt({ repoRoot, store }),
|
|
3143
|
+
loadTodoStructurePresentation({ repoRoot, readModel: store }),
|
|
2595
3144
|
]);
|
|
2596
3145
|
const layout = layoutTodoGantt(store, chain, { scope, independence, seamProposals });
|
|
2597
3146
|
// When the diagram hides history, the page also carries the full diagram so
|
|
@@ -2626,6 +3175,7 @@ export async function renderTodoGanttForProject({
|
|
|
2626
3175
|
chain_digest: digestTodoArtifact(chain),
|
|
2627
3176
|
layout_digest: digestTodoArtifact(layout),
|
|
2628
3177
|
note_bindings_digest: digestTodoArtifact(notes.headBindings),
|
|
3178
|
+
structure_projection_digest: structures.projection_digest,
|
|
2629
3179
|
renderer_version: TODO_GANTT_RENDERER_VERSION,
|
|
2630
3180
|
project_display_name: identity.displayName,
|
|
2631
3181
|
folded_task_count: layout.scope.folded_task_count,
|
|
@@ -2640,6 +3190,7 @@ export async function renderTodoGanttForProject({
|
|
|
2640
3190
|
metadata,
|
|
2641
3191
|
noteContexts: notes.contexts,
|
|
2642
3192
|
noteWarnings: notes.warnings,
|
|
3193
|
+
structurePresentation: structures,
|
|
2643
3194
|
});
|
|
2644
3195
|
return { store, metadata, memberBindings, rendered };
|
|
2645
3196
|
}
|
|
@@ -2712,6 +3263,8 @@ function writesTodoStore(argv) {
|
|
|
2712
3263
|
case 'snapshot': return argv.includes('--rebuild');
|
|
2713
3264
|
case 'independence': return second === 'compile'
|
|
2714
3265
|
|| (second === 'witness' && ['migrate', 'scaffold'].includes(third));
|
|
3266
|
+
case 'structure': return ['compile', 'realize', 'finalize'].includes(second)
|
|
3267
|
+
|| (second === 'input' && !argv.includes('--dry-run'));
|
|
2715
3268
|
case 'seam-proposal': return ['compile', 'apply', 'land'].includes(second);
|
|
2716
3269
|
case 'evidence': return second === 'promote';
|
|
2717
3270
|
case 'dependency': return second === 'connect';
|
|
@@ -2876,6 +3429,22 @@ export async function runTodoCli({ argv, cwd, stdout, stderr, env = process.env
|
|
|
2876
3429
|
});
|
|
2877
3430
|
}
|
|
2878
3431
|
|
|
3432
|
+
if (argv[0] === 'structure' && ['input', 'compile'].includes(argv[1])
|
|
3433
|
+
&& argv[4] === '--input' && typeof argv[5] === 'string' && path.isAbsolute(argv[5])) {
|
|
3434
|
+
return typedArgumentFailure(stderr, 'INPUT_OUTSIDE_REPOSITORY', 'absolute_input_path_rejected', {
|
|
3435
|
+
argument: '--input', expected: 'repo-relative path', actual: 'absolute path',
|
|
3436
|
+
next_action: 'place_the_input_inside_the_repository_and_pass_a_repo_relative_path',
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
if (argv[0] === 'structure' && argv[1] === 'realize'
|
|
3441
|
+
&& argv[6] === '--input' && typeof argv[7] === 'string' && path.isAbsolute(argv[7])) {
|
|
3442
|
+
return typedArgumentFailure(stderr, 'INPUT_OUTSIDE_REPOSITORY', 'absolute_input_path_rejected', {
|
|
3443
|
+
argument: '--input', expected: 'repo-relative path', actual: 'absolute path',
|
|
3444
|
+
next_action: 'place_the_input_inside_the_repository_and_pass_a_repo_relative_path',
|
|
3445
|
+
});
|
|
3446
|
+
}
|
|
3447
|
+
|
|
2879
3448
|
// `--schema --json`はstoreを読まない決定的な出力(`plan create --schema`と同じ規律)。
|
|
2880
3449
|
// 通常dispatchより前に処理し、repoRoot解決やdashboard daemon起動を経由させない。
|
|
2881
3450
|
if (argv.length === 3 && argv[1] === '--schema' && argv[2] === '--json'
|
|
@@ -2986,6 +3555,40 @@ export async function runTodoCli({ argv, cwd, stdout, stderr, env = process.env
|
|
|
2986
3555
|
action = (repoRoot) => independenceCompile({
|
|
2987
3556
|
repoRoot, planKey: argv[3], inputRef: argv[5],
|
|
2988
3557
|
});
|
|
3558
|
+
} else if (argv.length === 8 && argv[0] === 'structure' && argv[1] === 'input'
|
|
3559
|
+
&& argv[2] === '--plan' && isTodoIdentifier(argv[3])
|
|
3560
|
+
&& argv[4] === '--input' && isTodoRef(argv[5])
|
|
3561
|
+
&& argv[6] === '--dry-run' && argv[7] === '--json') {
|
|
3562
|
+
action = (repoRoot) => structureInputDryRun({
|
|
3563
|
+
repoRoot, planKey: argv[3], inputRef: argv[5],
|
|
3564
|
+
});
|
|
3565
|
+
} else if (argv.length === 6 && argv[0] === 'structure' && argv[1] === 'input'
|
|
3566
|
+
&& argv[2] === '--plan' && isTodoIdentifier(argv[3])
|
|
3567
|
+
&& argv[4] === '--input' && isTodoRef(argv[5])) {
|
|
3568
|
+
action = (repoRoot) => structureInput({
|
|
3569
|
+
repoRoot, planKey: argv[3], inputRef: argv[5],
|
|
3570
|
+
});
|
|
3571
|
+
} else if (argv.length === 6 && argv[0] === 'structure' && argv[1] === 'compile'
|
|
3572
|
+
&& argv[2] === '--plan' && isTodoIdentifier(argv[3])
|
|
3573
|
+
&& argv[4] === '--input' && isTodoRef(argv[5])) {
|
|
3574
|
+
action = (repoRoot) => structureCompile({
|
|
3575
|
+
repoRoot, env, planKey: argv[3], inputRef: argv[5],
|
|
3576
|
+
});
|
|
3577
|
+
} else if (argv.length === 8 && argv[0] === 'structure' && argv[1] === 'realize'
|
|
3578
|
+
&& argv[2] === '--plan' && isTodoIdentifier(argv[3])
|
|
3579
|
+
&& argv[4] === '--task' && isTodoIdentifier(argv[5])
|
|
3580
|
+
&& argv[6] === '--input' && isTodoRef(argv[7])) {
|
|
3581
|
+
action = (repoRoot) => structureRealize({
|
|
3582
|
+
repoRoot, env, planKey: argv[3], taskId: argv[5], inputRef: argv[7],
|
|
3583
|
+
});
|
|
3584
|
+
} else if (argv.length === 5 && argv[0] === 'structure' && argv[1] === 'finalize'
|
|
3585
|
+
&& argv[2] === '--plan' && isTodoIdentifier(argv[3]) && argv[4] === '--json') {
|
|
3586
|
+
action = (repoRoot) => structureFinalize({ repoRoot, env, planKey: argv[3] });
|
|
3587
|
+
} else if (argv.length === 2 && argv[0] === 'structure' && argv[1] === '--json') {
|
|
3588
|
+
action = (repoRoot) => structure({ repoRoot, requestedPlanKey: null });
|
|
3589
|
+
} else if (argv.length === 4 && argv[0] === 'structure'
|
|
3590
|
+
&& argv[1] === '--plan' && isTodoIdentifier(argv[2]) && argv[3] === '--json') {
|
|
3591
|
+
action = (repoRoot) => structure({ repoRoot, requestedPlanKey: argv[2] });
|
|
2989
3592
|
} else if ((argv.length === 1 && argv[0] === 'independence')
|
|
2990
3593
|
|| (argv.length === 2 && argv[0] === 'independence' && argv[1] === '--json')) {
|
|
2991
3594
|
action = (repoRoot) => independence({ repoRoot, requestedPlanKey: null });
|