@quolu/lattice 0.57.3 → 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/sensor-adapter.mjs +8 -2
- package/src/todo-chain.mjs +19 -5
- package/src/todo-cli.mjs +609 -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-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);
|
|
@@ -2378,13 +2923,14 @@ async function loadNarratives(store, repoRoot) {
|
|
|
2378
2923
|
* 一つの関数だけが組む。
|
|
2379
2924
|
*/
|
|
2380
2925
|
export async function ganttLiveHeadDigest({ repoRoot, store }) {
|
|
2381
|
-
const [independence, seamProposals, noteHeads] = await Promise.all([
|
|
2926
|
+
const [independence, seamProposals, noteHeads, structures] = await Promise.all([
|
|
2382
2927
|
independenceForGantt({ repoRoot, store }),
|
|
2383
2928
|
seamProposalsForGantt({ repoRoot, store }),
|
|
2384
2929
|
noteHeadsForGantt({ repoRoot, store }),
|
|
2930
|
+
loadTodoStructurePresentation({ repoRoot, readModel: store }),
|
|
2385
2931
|
]);
|
|
2386
2932
|
return digestTodoArtifact({
|
|
2387
|
-
schema: 'lattice.todo_gantt_live_head.
|
|
2933
|
+
schema: 'lattice.todo_gantt_live_head.v3',
|
|
2388
2934
|
manifest_digest: store.manifest.manifest_digest,
|
|
2389
2935
|
independence: independence === null ? null : independence.map((entry) => ({
|
|
2390
2936
|
plan_key: entry.plan_key,
|
|
@@ -2397,6 +2943,7 @@ export async function ganttLiveHeadDigest({ repoRoot, store }) {
|
|
|
2397
2943
|
projection_digest: digestTodoArtifact(entry),
|
|
2398
2944
|
})),
|
|
2399
2945
|
note_heads: noteHeads,
|
|
2946
|
+
structure_projection_digest: structures.projection_digest,
|
|
2400
2947
|
});
|
|
2401
2948
|
}
|
|
2402
2949
|
|
|
@@ -2588,10 +3135,11 @@ export async function renderTodoGanttForProject({
|
|
|
2588
3135
|
const presentation = await loadTodoGanttPresentation({ repoRoot, readModel: store });
|
|
2589
3136
|
const topology = mergedTopology(store);
|
|
2590
3137
|
const chain = projectTodoChainV1(topology);
|
|
2591
|
-
const [independence, seamProposals, notes] = await Promise.all([
|
|
3138
|
+
const [independence, seamProposals, notes, structures] = await Promise.all([
|
|
2592
3139
|
independenceForGantt({ repoRoot, store }),
|
|
2593
3140
|
seamProposalsForGantt({ repoRoot, store }),
|
|
2594
3141
|
notesForGantt({ repoRoot, store }),
|
|
3142
|
+
loadTodoStructurePresentation({ repoRoot, readModel: store }),
|
|
2595
3143
|
]);
|
|
2596
3144
|
const layout = layoutTodoGantt(store, chain, { scope, independence, seamProposals });
|
|
2597
3145
|
// When the diagram hides history, the page also carries the full diagram so
|
|
@@ -2626,6 +3174,7 @@ export async function renderTodoGanttForProject({
|
|
|
2626
3174
|
chain_digest: digestTodoArtifact(chain),
|
|
2627
3175
|
layout_digest: digestTodoArtifact(layout),
|
|
2628
3176
|
note_bindings_digest: digestTodoArtifact(notes.headBindings),
|
|
3177
|
+
structure_projection_digest: structures.projection_digest,
|
|
2629
3178
|
renderer_version: TODO_GANTT_RENDERER_VERSION,
|
|
2630
3179
|
project_display_name: identity.displayName,
|
|
2631
3180
|
folded_task_count: layout.scope.folded_task_count,
|
|
@@ -2640,6 +3189,7 @@ export async function renderTodoGanttForProject({
|
|
|
2640
3189
|
metadata,
|
|
2641
3190
|
noteContexts: notes.contexts,
|
|
2642
3191
|
noteWarnings: notes.warnings,
|
|
3192
|
+
structurePresentation: structures,
|
|
2643
3193
|
});
|
|
2644
3194
|
return { store, metadata, memberBindings, rendered };
|
|
2645
3195
|
}
|
|
@@ -2712,6 +3262,8 @@ function writesTodoStore(argv) {
|
|
|
2712
3262
|
case 'snapshot': return argv.includes('--rebuild');
|
|
2713
3263
|
case 'independence': return second === 'compile'
|
|
2714
3264
|
|| (second === 'witness' && ['migrate', 'scaffold'].includes(third));
|
|
3265
|
+
case 'structure': return ['compile', 'realize', 'finalize'].includes(second)
|
|
3266
|
+
|| (second === 'input' && !argv.includes('--dry-run'));
|
|
2715
3267
|
case 'seam-proposal': return ['compile', 'apply', 'land'].includes(second);
|
|
2716
3268
|
case 'evidence': return second === 'promote';
|
|
2717
3269
|
case 'dependency': return second === 'connect';
|
|
@@ -2876,6 +3428,22 @@ export async function runTodoCli({ argv, cwd, stdout, stderr, env = process.env
|
|
|
2876
3428
|
});
|
|
2877
3429
|
}
|
|
2878
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
|
+
|
|
2879
3447
|
// `--schema --json`はstoreを読まない決定的な出力(`plan create --schema`と同じ規律)。
|
|
2880
3448
|
// 通常dispatchより前に処理し、repoRoot解決やdashboard daemon起動を経由させない。
|
|
2881
3449
|
if (argv.length === 3 && argv[1] === '--schema' && argv[2] === '--json'
|
|
@@ -2986,6 +3554,40 @@ export async function runTodoCli({ argv, cwd, stdout, stderr, env = process.env
|
|
|
2986
3554
|
action = (repoRoot) => independenceCompile({
|
|
2987
3555
|
repoRoot, planKey: argv[3], inputRef: argv[5],
|
|
2988
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] });
|
|
2989
3591
|
} else if ((argv.length === 1 && argv[0] === 'independence')
|
|
2990
3592
|
|| (argv.length === 2 && argv[0] === 'independence' && argv[1] === '--json')) {
|
|
2991
3593
|
action = (repoRoot) => independence({ repoRoot, requestedPlanKey: null });
|