@quolu/lattice 0.41.0 → 0.42.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 +3 -2
- package/README.md +5 -3
- package/bin/lattice-dashboard.mjs +2 -2
- package/package.json +1 -1
- package/src/cli-help.mjs +1 -5
- package/src/runtime-cli.mjs +25 -175
- package/src/runtime-multi-epoch-store.mjs +1 -2
- package/src/todo-cli.mjs +7 -2
- package/src/todo-gantt-html-independence.mjs +52 -12
- package/src/todo-gantt-html-shared.mjs +4 -1
- package/src/todo-gantt-html-style.mjs +1 -0
package/README.ja.md
CHANGED
|
@@ -61,8 +61,9 @@ lattice todo note --plan <key> --task <id> --message "既存parserを使い、fa
|
|
|
61
61
|
lattice todo show --plan <key> --task <id> --json
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
動的工程表は、選択したToDoの右ペインへ初期設計メモと追記noteの本文を表示します。あわせて前提工程・
|
|
65
|
+
後続工程それぞれの状態(未着手/作業中/完了/ブロック中)と、その工程の並列可否を示します。
|
|
66
|
+
note本文を落とすのはrepo外へHTMLを出す公開配信面だけです。project別HTMLの生成や再生成は不要です。
|
|
66
67
|
|
|
67
68
|
### 変換の受入五条件
|
|
68
69
|
|
package/README.md
CHANGED
|
@@ -74,9 +74,11 @@ lattice todo note --plan <key> --task <id> --message "Use the existing parser; d
|
|
|
74
74
|
lattice todo show --plan <key> --task <id> --json
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
The
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
The selected ToDo's detail pane shows the initial design memo and the append-only note bodies, plus the
|
|
78
|
+
status of each prerequisite and dependent ToDo (pending, in progress, done, blocked) and whether the ToDo
|
|
79
|
+
can run in parallel. Only a public distribution surface — HTML served outside the repository — drops note
|
|
80
|
+
bodies. Static per-project HTML is not generated; `lattice todo gantt serve` and the shared dashboard
|
|
81
|
+
read the store dynamically.
|
|
80
82
|
|
|
81
83
|
### The five acceptance conditions
|
|
82
84
|
|
|
@@ -5,7 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
|
|
6
6
|
import { readTodoStoreStable } from '../src/todo-store.mjs';
|
|
7
7
|
import { projectTodoStatus } from '../src/todo-status.mjs';
|
|
8
|
-
import { ganttLiveHeadDigest,
|
|
8
|
+
import { ganttLiveHeadDigest, renderTodoGanttForProject } from '../src/todo-cli.mjs';
|
|
9
9
|
import {
|
|
10
10
|
readVisibleTodoDashboardProjects,
|
|
11
11
|
todoDashboardMemberNeedsVisibility,
|
|
@@ -90,7 +90,7 @@ async function synchronize() {
|
|
|
90
90
|
displayName: entry.display_name,
|
|
91
91
|
render: async ({ displayName }) => {
|
|
92
92
|
const store = await readCachedStore(entry.repo_root);
|
|
93
|
-
const result = await
|
|
93
|
+
const result = await renderTodoGanttForProject({ repoRoot: entry.repo_root,
|
|
94
94
|
stable: true, displayName, readModel: store });
|
|
95
95
|
return {
|
|
96
96
|
html: result.rendered.html,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Quo / クオ at kitepon.dev",
|
package/src/cli-help.mjs
CHANGED
|
@@ -30,18 +30,14 @@ Commands:
|
|
|
30
30
|
create --schema --json # 既定は最新版(v4)のJSON Schemaを返す
|
|
31
31
|
create --schema-version <1|2|3|4> --json
|
|
32
32
|
show <plan_key> --json # task・依存・phase・状態をplan本体から1コマンドで投影する
|
|
33
|
-
compile --request <request.json>
|
|
33
|
+
compile --request <request.json>
|
|
34
34
|
compile --schema --json # lattice.run_request.v1 の JSON Schema を出す
|
|
35
35
|
verify --request <request.json> --plan <plan.json>
|
|
36
36
|
`,
|
|
37
37
|
run: `Usage: lattice run <command> [options]
|
|
38
38
|
|
|
39
39
|
Commands:
|
|
40
|
-
start --request <request.json> --plan <compile-artifact.json> --executor <adapter>
|
|
41
|
-
# TODO storeと連携して同一repo writerを並列起動する場合のgate付き入口。
|
|
42
|
-
# artifactはplan compile --todo-plan <key>で現revisionへ束縛して発行する
|
|
43
40
|
start --request <request.json> --executor <adapter>
|
|
44
|
-
# artifactを消費しない既存runtime互換入口
|
|
45
41
|
start --schema --json # lattice.run_request.v1 の JSON Schema を出す
|
|
46
42
|
adapter register --input <descriptor.json>
|
|
47
43
|
adapter register --schema --json # 登録入力の JSON Schema を出す
|
package/src/runtime-cli.mjs
CHANGED
|
@@ -81,8 +81,6 @@ import { createRuntimeControlRequest, validateRuntimeControlResponse } from './r
|
|
|
81
81
|
import { createRuntimeControlStore } from './runtime-control-store.mjs';
|
|
82
82
|
import { createRuntimeGateStore } from './runtime-gate-store.mjs';
|
|
83
83
|
import { acquireRuntimeLifecycleLock } from './runtime-lifecycle-lock.mjs';
|
|
84
|
-
import { readTodoIndependenceArtifact, readTodoStore } from './todo-store.mjs';
|
|
85
|
-
import { projectIndependenceFrontier } from './todo-independence.mjs';
|
|
86
84
|
import {
|
|
87
85
|
AdapterRegistryError,
|
|
88
86
|
listRuntimeAdapters,
|
|
@@ -132,47 +130,6 @@ const CLI_ERROR_SCHEMA = 'lattice.cli_error.v2';
|
|
|
132
130
|
const RUN_STORE_ROOT = ['.lattice', 'runs'];
|
|
133
131
|
const RUN_REF = /^\.lattice\/runs\/([0-9A-Za-z](?:[0-9A-Za-z._-]{0,127}))$/u;
|
|
134
132
|
const KNOWN_ADAPTERS = Object.freeze(['scripted', 'isolated-worktree', 'actual-agent']);
|
|
135
|
-
|
|
136
|
-
function sameTextSet(left, right) {
|
|
137
|
-
return Array.isArray(left) && Array.isArray(right)
|
|
138
|
-
&& left.length === right.length
|
|
139
|
-
&& [...left].sort().every((value, index) => value === [...right].sort()[index]);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function compileArtifactBodyIsValid(artifact, request) {
|
|
143
|
-
const keys = ['schema', 'request_digest', 'plan', 'manifests', 'schedule', 'graph_digest',
|
|
144
|
-
'todo_plan_binding', 'result_digest'];
|
|
145
|
-
if (artifact === null || typeof artifact !== 'object' || Array.isArray(artifact)
|
|
146
|
-
|| Object.keys(artifact).sort().join('\0') !== keys.sort().join('\0')
|
|
147
|
-
|| artifact.schema !== COMPILE_RESULT_SCHEMA) return false;
|
|
148
|
-
const { result_digest: claimedDigest, ...body } = artifact;
|
|
149
|
-
if (claimedDigest !== digestArtifact(body) || artifact.request_digest !== request.request_digest
|
|
150
|
-
|| !validateRuntimePlan(artifact.plan)
|
|
151
|
-
|| !verifyRuntimePlanBinding({ plan: artifact.plan, request })) return false;
|
|
152
|
-
const nodeIds = artifact.plan.nodes.map((node) => node.todo_id);
|
|
153
|
-
if (artifact.manifests === null || typeof artifact.manifests !== 'object'
|
|
154
|
-
|| Array.isArray(artifact.manifests)
|
|
155
|
-
|| !sameTextSet(Object.keys(artifact.manifests), nodeIds)) return false;
|
|
156
|
-
return nodeIds.every((todoId) => validateRuntimeBoundaryManifest(artifact.manifests[todoId])
|
|
157
|
-
&& artifact.manifests[todoId].manifest_digest === artifact.plan.manifest_digests[todoId]);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function todoPlanBindingFor(member) {
|
|
161
|
-
return {
|
|
162
|
-
project_id: member.plan.project_id,
|
|
163
|
-
plan_key: member.plan.plan_key,
|
|
164
|
-
plan_version: member.plan.plan_version,
|
|
165
|
-
topology_digest: member.plan.topology_digest,
|
|
166
|
-
plan_digest: member.plan.plan_digest,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function sameTodoPlanBinding(left, right) {
|
|
171
|
-
return left !== null && typeof left === 'object' && !Array.isArray(left)
|
|
172
|
-
&& Object.keys(left).sort().join('\0') === ['project_id', 'plan_key', 'plan_version',
|
|
173
|
-
'topology_digest', 'plan_digest'].sort().join('\0')
|
|
174
|
-
&& Object.entries(right).every(([key, value]) => left[key] === value);
|
|
175
|
-
}
|
|
176
133
|
/**
|
|
177
134
|
* 自動escalationがlifecycle lockを待つ上限(ADR 0143)。
|
|
178
135
|
*
|
|
@@ -439,27 +396,9 @@ async function compileFromRepo({ request, cwd, planRef, planEpoch, predecessorRe
|
|
|
439
396
|
});
|
|
440
397
|
}
|
|
441
398
|
|
|
442
|
-
async function planCompile({ requestPath,
|
|
399
|
+
async function planCompile({ requestPath, cwd, stdout }) {
|
|
443
400
|
const request = await loadRequest(requestPath);
|
|
444
401
|
await resolveRepoBinding(cwd, request);
|
|
445
|
-
let todoPlanBinding = null;
|
|
446
|
-
if (todoPlanKey !== null) {
|
|
447
|
-
const store = await readTodoStore({ repoRoot: cwd });
|
|
448
|
-
const member = store.members.find(({ descriptor }) => descriptor.plan_key === todoPlanKey);
|
|
449
|
-
if (member === undefined) {
|
|
450
|
-
throw new CliContractError('TODO_PLAN_NOT_ACTIVE', '指定されたTODO planはactiveではない', {
|
|
451
|
-
plan_key: todoPlanKey, next_action: 'select_an_active_todo_plan',
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
if (!sameTextSet(member.plan.tasks.map(({ task_id: taskId }) => taskId),
|
|
455
|
-
request.todos.map(({ todo_id: todoId }) => todoId))) {
|
|
456
|
-
throw new CliContractError('TODO_PLAN_TASK_MISMATCH',
|
|
457
|
-
'run requestのTODO集合がactive TODO planと一致しない', {
|
|
458
|
-
plan_key: todoPlanKey, next_action: 'compile_a_request_for_the_current_plan_revision',
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
todoPlanBinding = todoPlanBindingFor(member);
|
|
462
|
-
}
|
|
463
402
|
const result = await compileFromRepo({
|
|
464
403
|
request,
|
|
465
404
|
cwd,
|
|
@@ -477,7 +416,6 @@ async function planCompile({ requestPath, todoPlanKey = null, cwd, stdout }) {
|
|
|
477
416
|
manifests: result.manifests,
|
|
478
417
|
schedule: result.schedule,
|
|
479
418
|
graph_digest: result.graph_digest,
|
|
480
|
-
todo_plan_binding: todoPlanBinding,
|
|
481
419
|
};
|
|
482
420
|
artifact.result_digest = digestArtifact(artifact);
|
|
483
421
|
stdout.write(`${JSON.stringify(artifact)}\n`);
|
|
@@ -588,8 +526,7 @@ async function readRunStore(runDir) {
|
|
|
588
526
|
path.join(runDir, 'plan-compile-result.json'), 'plan compile result',
|
|
589
527
|
);
|
|
590
528
|
const request = await readBoundedJson(path.join(runDir, 'request.json'), 'run request');
|
|
591
|
-
const compileKeys = ['schema', 'request_digest', 'plan', 'manifests', 'schedule', 'graph_digest',
|
|
592
|
-
'todo_plan_binding', 'result_digest'];
|
|
529
|
+
const compileKeys = ['schema', 'request_digest', 'plan', 'manifests', 'schedule', 'graph_digest', 'result_digest'];
|
|
593
530
|
const metaKeys = ['schema', 'run_id', 'executor_adapter', 'plan_digest'];
|
|
594
531
|
const { result_digest: claimedCompileDigest, ...compileBody } = compileArtifact ?? {};
|
|
595
532
|
const legacyMetaValid = meta !== null && typeof meta === 'object' && !Array.isArray(meta)
|
|
@@ -1153,76 +1090,7 @@ function isDistributedScriptedControllerActivation(activation) {
|
|
|
1153
1090
|
));
|
|
1154
1091
|
}
|
|
1155
1092
|
|
|
1156
|
-
async function
|
|
1157
|
-
if (!compileArtifactBodyIsValid(compileArtifact, request)) {
|
|
1158
|
-
throw new CliContractError('INVALID_PLAN_ARTIFACT',
|
|
1159
|
-
'compile artifactのdigest・request binding・plan/manifest bindingが不正', {
|
|
1160
|
-
next_action: 'rerun_lattice_plan_compile_then_pass_its_artifact',
|
|
1161
|
-
});
|
|
1162
|
-
}
|
|
1163
|
-
if (compileArtifact.todo_plan_binding === null) {
|
|
1164
|
-
throw new CliContractError('COMPILE_ARTIFACT_UNBOUND',
|
|
1165
|
-
'compile artifactがTODO plan revisionへ束縛されていない', {
|
|
1166
|
-
next_action: 'rerun_lattice_plan_compile_with_todo_plan',
|
|
1167
|
-
});
|
|
1168
|
-
}
|
|
1169
|
-
const store = await readTodoStore({ repoRoot });
|
|
1170
|
-
const binding = compileArtifact.todo_plan_binding;
|
|
1171
|
-
const member = store.members.find(({ descriptor }) => descriptor.plan_key === binding.plan_key);
|
|
1172
|
-
if (member === undefined || !sameTodoPlanBinding(binding, todoPlanBindingFor(member))) {
|
|
1173
|
-
throw new CliContractError('STALE_TODO_PLAN_BINDING',
|
|
1174
|
-
'compile artifactのTODO plan version/digestが現在のactive revisionと一致しない', {
|
|
1175
|
-
plan_key: binding.plan_key,
|
|
1176
|
-
next_action: 'recompile_for_the_current_todo_plan_revision',
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
const taskIds = compileArtifact.plan.nodes.map(({ todo_id: todoId }) => todoId);
|
|
1180
|
-
if (!sameTextSet(member.plan.tasks.map(({ task_id: taskId }) => taskId), taskIds)) {
|
|
1181
|
-
throw new CliContractError('TODO_PLAN_TASK_MISMATCH',
|
|
1182
|
-
'compile artifactのtask集合が現在のTODO planと一致しない', {
|
|
1183
|
-
plan_key: binding.plan_key,
|
|
1184
|
-
next_action: 'recompile_for_the_current_todo_plan_revision',
|
|
1185
|
-
});
|
|
1186
|
-
}
|
|
1187
|
-
const independenceArtifact = await readTodoIndependenceArtifact({
|
|
1188
|
-
repoRoot, store, planKey: binding.plan_key,
|
|
1189
|
-
});
|
|
1190
|
-
const currentHead = await runGit(['rev-parse', 'HEAD'], repoRoot);
|
|
1191
|
-
if (currentHead.code !== 0) throw new CliContractError('REPO_UNRESOLVED', 'cwdのgit HEADを解決できない');
|
|
1192
|
-
const currentBaseSha = currentHead.stdout.trim();
|
|
1193
|
-
const projected = projectIndependenceFrontier({
|
|
1194
|
-
artifact: independenceArtifact,
|
|
1195
|
-
readyTaskIds: taskIds,
|
|
1196
|
-
activeTaskIds: [],
|
|
1197
|
-
plan: member.plan,
|
|
1198
|
-
currentBaseSha,
|
|
1199
|
-
changedPaths: null,
|
|
1200
|
-
});
|
|
1201
|
-
if (projected.coverage !== 'verified') {
|
|
1202
|
-
throw new CliContractError('PARALLEL_GROUP_UNVERIFIED',
|
|
1203
|
-
'TODO independence recordがverifiedではないため並列writerを起動できない', {
|
|
1204
|
-
coverage: projected.coverage,
|
|
1205
|
-
next_action: 'lattice todo independence compile --plan ' + binding.plan_key + ' --input <witness.json>',
|
|
1206
|
-
});
|
|
1207
|
-
}
|
|
1208
|
-
for (const wave of compileArtifact.schedule.waves) {
|
|
1209
|
-
if (wave.todo_ids.length < 2) continue;
|
|
1210
|
-
const verified = projected.frontier.parallel_groups.some((group) => (
|
|
1211
|
-
sameTextSet(group.task_ids, wave.todo_ids)
|
|
1212
|
-
));
|
|
1213
|
-
if (!verified) {
|
|
1214
|
-
throw new CliContractError('PARALLEL_GROUP_UNVERIFIED',
|
|
1215
|
-
'compile artifactが同時dispatchするtask群はverified parallel groupではない', {
|
|
1216
|
-
task_ids: wave.todo_ids,
|
|
1217
|
-
coverage: projected.coverage,
|
|
1218
|
-
unknown: projected.frontier.unknown,
|
|
1219
|
-
next_action: 'compile_independence_for_the_current_plan_then_recompile_the_run_plan',
|
|
1220
|
-
});
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
async function runStart({ requestPath, planPath = null, executorAdapter, cwd, stdout }) {
|
|
1093
|
+
async function runStart({ requestPath, executorAdapter, cwd, stdout }) {
|
|
1226
1094
|
// --executor省略時の暗黙fallbackは持たない(Decision 8)。未知adapterはtyped reject。
|
|
1227
1095
|
if (!KNOWN_ADAPTERS.includes(executorAdapter)) {
|
|
1228
1096
|
throw new CliContractError('UNKNOWN_ADAPTER', `未知のexecutor adapter: ${executorAdapter}`);
|
|
@@ -1236,32 +1104,30 @@ async function runStart({ requestPath, planPath = null, executorAdapter, cwd, st
|
|
|
1236
1104
|
await requireSafeRunAncestors(repoRoot);
|
|
1237
1105
|
await requireIgnoredRunStore(repoRoot);
|
|
1238
1106
|
await resolveRepoBinding(repoRoot, request);
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
throw new CliContractError(result.code, 'dispatchable planを発行できない', result.detail);
|
|
1249
|
-
}
|
|
1250
|
-
compileArtifact = {
|
|
1251
|
-
schema: COMPILE_RESULT_SCHEMA, request_digest: request.request_digest, plan: result.plan,
|
|
1252
|
-
manifests: result.manifests, schedule: result.schedule, graph_digest: result.graph_digest,
|
|
1253
|
-
todo_plan_binding: null,
|
|
1254
|
-
};
|
|
1255
|
-
compileArtifact.result_digest = digestArtifact(compileArtifact);
|
|
1256
|
-
} else {
|
|
1257
|
-
compileArtifact = await readBoundedJson(planPath, 'plan artifact');
|
|
1258
|
-
await verifyRunStartGate({ request, compileArtifact, repoRoot });
|
|
1107
|
+
const result = await compileFromRepo({
|
|
1108
|
+
request,
|
|
1109
|
+
cwd: repoRoot,
|
|
1110
|
+
planRef: `plan-${request.request_id}-e1`,
|
|
1111
|
+
planEpoch: 1,
|
|
1112
|
+
predecessorRefs: [],
|
|
1113
|
+
});
|
|
1114
|
+
if (result.outcome !== 'dispatchable') {
|
|
1115
|
+
throw new CliContractError(result.code, 'dispatchable planを発行できない', result.detail);
|
|
1259
1116
|
}
|
|
1117
|
+
const compileArtifact = {
|
|
1118
|
+
schema: COMPILE_RESULT_SCHEMA,
|
|
1119
|
+
request_digest: request.request_digest,
|
|
1120
|
+
plan: result.plan,
|
|
1121
|
+
manifests: result.manifests,
|
|
1122
|
+
schedule: result.schedule,
|
|
1123
|
+
graph_digest: result.graph_digest,
|
|
1124
|
+
};
|
|
1125
|
+
compileArtifact.result_digest = digestArtifact(compileArtifact);
|
|
1260
1126
|
const events = initializeRunEvents({
|
|
1261
1127
|
runId: request.request_id,
|
|
1262
1128
|
request,
|
|
1263
|
-
plan:
|
|
1264
|
-
manifests:
|
|
1129
|
+
plan: result.plan,
|
|
1130
|
+
manifests: result.manifests,
|
|
1265
1131
|
recordedAt: new Date().toISOString().replace(/\.\d+Z$/u, '.000Z'),
|
|
1266
1132
|
});
|
|
1267
1133
|
const runDir = runStorePath(repoRoot, request.request_id);
|
|
@@ -1279,7 +1145,7 @@ async function runStart({ requestPath, planPath = null, executorAdapter, cwd, st
|
|
|
1279
1145
|
schema: 'lattice.run_meta.v1',
|
|
1280
1146
|
run_id: request.request_id,
|
|
1281
1147
|
executor_adapter: executorAdapter,
|
|
1282
|
-
plan_digest:
|
|
1148
|
+
plan_digest: result.plan.plan_digest,
|
|
1283
1149
|
};
|
|
1284
1150
|
try {
|
|
1285
1151
|
await writeJsonFile(path.join(temporaryDir, 'request.json'), request);
|
|
@@ -1299,7 +1165,7 @@ async function runStart({ requestPath, planPath = null, executorAdapter, cwd, st
|
|
|
1299
1165
|
run_id: request.request_id,
|
|
1300
1166
|
run_dir: path.relative(repoRoot, runDir),
|
|
1301
1167
|
executor_adapter: executorAdapter,
|
|
1302
|
-
plan_digest:
|
|
1168
|
+
plan_digest: result.plan.plan_digest,
|
|
1303
1169
|
events_digest: digestArtifact(events.map(({ event_digest: digest }) => digest)),
|
|
1304
1170
|
};
|
|
1305
1171
|
output.result_digest = digestArtifact(output);
|
|
@@ -4035,13 +3901,6 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
|
|
|
4035
3901
|
&& argv[0] === 'plan' && argv[1] === 'compile' && argv[2] === '--request'
|
|
4036
3902
|
&& typeof argv[3] === 'string' && argv[3].length > 0) {
|
|
4037
3903
|
action = () => planCompile({ requestPath: path.resolve(cwd, argv[3]), cwd, stdout });
|
|
4038
|
-
} else if (argv.length === 6
|
|
4039
|
-
&& argv[0] === 'plan' && argv[1] === 'compile' && argv[2] === '--request'
|
|
4040
|
-
&& typeof argv[3] === 'string' && argv[3].length > 0
|
|
4041
|
-
&& argv[4] === '--todo-plan' && typeof argv[5] === 'string' && argv[5].length > 0) {
|
|
4042
|
-
action = () => planCompile({
|
|
4043
|
-
requestPath: path.resolve(cwd, argv[3]), todoPlanKey: argv[5], cwd, stdout,
|
|
4044
|
-
});
|
|
4045
3904
|
} else if (argv.length === 6
|
|
4046
3905
|
&& argv[0] === 'plan' && argv[1] === 'verify'
|
|
4047
3906
|
&& argv[2] === '--request' && typeof argv[3] === 'string' && argv[3].length > 0
|
|
@@ -4062,15 +3921,6 @@ export async function runRuntimeCli({ argv, cwd, stdout, stderr }) {
|
|
|
4062
3921
|
cwd,
|
|
4063
3922
|
stdout,
|
|
4064
3923
|
});
|
|
4065
|
-
} else if (argv.length === 8
|
|
4066
|
-
&& argv[0] === 'run' && argv[1] === 'start'
|
|
4067
|
-
&& argv[2] === '--request' && typeof argv[3] === 'string' && argv[3].length > 0
|
|
4068
|
-
&& argv[4] === '--plan' && typeof argv[5] === 'string' && argv[5].length > 0
|
|
4069
|
-
&& argv[6] === '--executor' && typeof argv[7] === 'string' && argv[7].length > 0) {
|
|
4070
|
-
action = () => runStart({
|
|
4071
|
-
requestPath: path.resolve(cwd, argv[3]), planPath: path.resolve(cwd, argv[5]),
|
|
4072
|
-
executorAdapter: argv[7], cwd, stdout,
|
|
4073
|
-
});
|
|
4074
3924
|
} else if (argv.length === 4
|
|
4075
3925
|
&& argv[0] === 'run' && argv[1] === 'activate'
|
|
4076
3926
|
&& argv[2] === '--run' && typeof argv[3] === 'string' && argv[3].length > 0) {
|
|
@@ -215,8 +215,7 @@ async function replaceDurableJson(directory, name, value) {
|
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
function validateLegacyInputs({ request, compileArtifact, legacyMeta }) {
|
|
218
|
-
const compileKeys = ['schema', 'request_digest', 'plan', 'manifests', 'schedule', 'graph_digest',
|
|
219
|
-
'todo_plan_binding', 'result_digest'];
|
|
218
|
+
const compileKeys = ['schema', 'request_digest', 'plan', 'manifests', 'schedule', 'graph_digest', 'result_digest'];
|
|
220
219
|
const metaKeys = ['schema', 'run_id', 'executor_adapter', 'plan_digest'];
|
|
221
220
|
const compileBody = { ...compileArtifact };
|
|
222
221
|
delete compileBody.result_digest;
|
package/src/todo-cli.mjs
CHANGED
|
@@ -2292,7 +2292,12 @@ export async function renderTodoGanttForProject({
|
|
|
2292
2292
|
return { store, metadata, memberBindings, rendered };
|
|
2293
2293
|
}
|
|
2294
2294
|
|
|
2295
|
-
/**
|
|
2295
|
+
/**
|
|
2296
|
+
* 公開配信面はToDo本体の設計メモを表示し、append-only作業記録は除外する。
|
|
2297
|
+
*
|
|
2298
|
+
* loopbackのdashboardと`gantt serve`は公開配信面ではない。作業者本人が読む面なので
|
|
2299
|
+
* 記録込みで描く。この入口はrepo外へHTMLを出す面が生えた時に使う。
|
|
2300
|
+
*/
|
|
2296
2301
|
export async function renderPublicTodoGanttForProject(options = {}) {
|
|
2297
2302
|
return renderTodoGanttForProject({ ...options, includeNotes: false });
|
|
2298
2303
|
}
|
|
@@ -2306,7 +2311,7 @@ async function serveGantt({ repoRoot, port, stdout, env, scope = DEFAULT_GANTT_S
|
|
|
2306
2311
|
port,
|
|
2307
2312
|
render: async () => {
|
|
2308
2313
|
const store = await readTodoStoreStable({ repoRoot });
|
|
2309
|
-
const { rendered } = await
|
|
2314
|
+
const { rendered } = await renderTodoGanttForProject({
|
|
2310
2315
|
repoRoot, stable: true, displayName: identity.displayName, scope, readModel: store,
|
|
2311
2316
|
});
|
|
2312
2317
|
return { html: rendered.html, head_digest: await ganttLiveHeadDigest({ repoRoot, store }) };
|
|
@@ -60,20 +60,59 @@ export function dispatchBasis(summary) {
|
|
|
60
60
|
return `${parts.join('、')}。未検査は依存線が無くても並列可の根拠になりません。`;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
const COVERAGE_REASON = Object.freeze({
|
|
64
|
+
missing: 'このplanには独立性の記録がまだありません。',
|
|
65
|
+
stale: '記録はありますが、その後のdiffで失効しています。',
|
|
66
|
+
superseded: '記録は旧plan versionのもので、現在のtopologyには読めません。',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 未検査を黙って消さない。記録が無い状態こそ最も警告が要る(ADR 0127)。
|
|
71
|
+
*
|
|
72
|
+
* 枠ごと消すと「競合なし」と読まれる。判定していないことと、次の一手を必ず言う。
|
|
73
|
+
*/
|
|
74
|
+
function unverifiedIndependence(planKey, coverage) {
|
|
75
|
+
const reason = COVERAGE_REASON[coverage] ?? COVERAGE_REASON.missing;
|
|
76
|
+
return `<p class="readiness-note"><strong>並列可否:</strong> 未検査です。競合が無いのではなく、まだ判定していません。${escapeHtmlText(reason)}宣言を書いて <code>lattice todo independence compile --plan ${escapeHtmlText(planKey)} --input <ref></code> を通すと判定できます。</p>`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function conflictItems(pairs) {
|
|
80
|
+
return pairs.map((pair) => `<li>${escapeHtmlText(pair.other)} — ${escapeHtmlText(SEVERABILITY_LABEL[pair.severability] ?? pair.severability)}(資源 ${escapeHtmlText(pair.detail)})</li>`).join('');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 作業中の工程は自分がready frontierに居ないため、conflictの相手側として現れる。
|
|
85
|
+
* 「今これを動かしている間、何が同時に始められないか」がその工程での並列可否である。
|
|
86
|
+
*/
|
|
87
|
+
function activeIndependence(plan, taskId) {
|
|
88
|
+
const blocked = plan.conflicts_with_active
|
|
89
|
+
.filter((entry) => entry.active_task_id === taskId)
|
|
90
|
+
.map((entry) => ({
|
|
91
|
+
other: entry.ready_task_id, severability: entry.severability, detail: entry.detail,
|
|
92
|
+
}));
|
|
93
|
+
if (blocked.length > 0) {
|
|
94
|
+
return `<p class="readiness-note"><strong>並列可否:</strong> この工程が作業中のあいだ、次の着手候補は同時に始められません。</p><ul class="independence-conflicts">${conflictItems(blocked)}</ul>`;
|
|
95
|
+
}
|
|
96
|
+
if (plan.coverage !== 'verified') return unverifiedIndependence(plan.plan_key, plan.coverage);
|
|
97
|
+
return '<p class="readiness-note"><strong>並列可否:</strong> 着手済のため同時着手の判定対象ではありません。現在の着手候補に、この工程と競合するものはありません。</p>';
|
|
98
|
+
}
|
|
99
|
+
|
|
63
100
|
/** 個別ToDoについて、競合相手と切断可能性を言葉で示す。 */
|
|
64
|
-
export function renderIndependenceNote(ref, node, summary) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
101
|
+
export function renderIndependenceNote(ref, node, summary, status) {
|
|
102
|
+
// 完了した工程は同時着手の判断材料ではない。ここだけは黙ってよい。
|
|
103
|
+
if (status === 'done') return '';
|
|
104
|
+
const plan = summary === null ? undefined : summary.byPlan.get(ref.plan_key);
|
|
105
|
+
if (plan === undefined) return unverifiedIndependence(ref.plan_key, 'missing');
|
|
68
106
|
const taskId = ref.task_id;
|
|
69
|
-
const state = node
|
|
70
|
-
if (state === null)
|
|
107
|
+
const state = node?.visibility?.independence ?? null;
|
|
108
|
+
if (state === null) {
|
|
109
|
+
if (status === 'in-progress') return activeIndependence(plan, taskId);
|
|
110
|
+
return '<p class="readiness-note"><strong>並列可否:</strong> まだ着手候補ではないため判定していません。前提工程が片付いて着手候補に入った時点で判定します。</p>';
|
|
111
|
+
}
|
|
71
112
|
if (state === 'verified') {
|
|
72
113
|
return '<p class="readiness-note"><strong>並列可否:</strong> 独立検証済です。記録時点の宣言境界では他のready工程と干渉しません。</p>';
|
|
73
114
|
}
|
|
74
|
-
if (state === 'unknown')
|
|
75
|
-
return '<p class="readiness-note"><strong>並列可否:</strong> 未検査です。競合が無いのではなく、まだ判定していません。</p>';
|
|
76
|
-
}
|
|
115
|
+
if (state === 'unknown') return unverifiedIndependence(plan.plan_key, plan.coverage);
|
|
77
116
|
const pairs = [
|
|
78
117
|
...plan.serialize_pairs
|
|
79
118
|
.filter((pair) => pair.task_ids.includes(taskId))
|
|
@@ -90,8 +129,7 @@ export function renderIndependenceNote(ref, node, summary) {
|
|
|
90
129
|
detail: entry.detail,
|
|
91
130
|
})),
|
|
92
131
|
];
|
|
93
|
-
|
|
94
|
-
return `<p class="readiness-note"><strong>並列可否:</strong> 要直列です。</p><ul class="independence-conflicts">${items}</ul>`;
|
|
132
|
+
return `<p class="readiness-note"><strong>並列可否:</strong> 要直列です。</p><ul class="independence-conflicts">${conflictItems(pairs)}</ul>`;
|
|
95
133
|
}
|
|
96
134
|
|
|
97
135
|
export function renderRightPane(
|
|
@@ -157,7 +195,9 @@ export function renderRightPane(
|
|
|
157
195
|
const readiness = node?.visibility.next_ready
|
|
158
196
|
? `<p class="readiness-note">ready frontierの一員です。${ready.length > 1 ? '他のready工程と同時着手できるかは下の並列可否で判断してください。' : '現在の唯一の着手候補です。'}</p>`
|
|
159
197
|
: incoming.get(key).length === 0 ? '<p class="readiness-note">登録済みの前提工程はありません。図だけではdispatch可否を判定しません。</p>' : '';
|
|
160
|
-
const independenceNote = renderIndependenceNote(
|
|
198
|
+
const independenceNote = renderIndependenceNote(
|
|
199
|
+
section.ref, node, independenceSummary, section.state.status,
|
|
200
|
+
);
|
|
161
201
|
// Say it plainly when the reader will not find this ToDo on the diagram.
|
|
162
202
|
const foldedNote = !folds.has(key) ? ''
|
|
163
203
|
: expandable
|
|
@@ -105,7 +105,10 @@ export function renderRelationList(relations, sectionByKey, lookup, emptyText, f
|
|
|
105
105
|
// Say which ones the diagram does not draw, so the reader stops looking.
|
|
106
106
|
const reference = folds.has(targetKey)
|
|
107
107
|
? `${taskReference(target, lookup)}(図では非表示)` : taskReference(target, lookup);
|
|
108
|
-
|
|
108
|
+
// 前提が終わっているか、後続が動き出せるかは、相手の状態を見ないと判断できない。
|
|
109
|
+
const status = DOCUMENT_STATUS[target.state.status] ?? { mark: '?', label: '状態不明' };
|
|
110
|
+
const badge = `<span class="relation-status status-${escapeHtmlAttribute(target.state.status)}">${escapeHtmlText(`${status.mark} ${status.label}`)}</span>`;
|
|
111
|
+
return `<li><button type="button" data-select-node-key="${escapeHtmlAttribute(targetKey)}"><span class="relation-head">${badge}<strong>${escapeHtmlText(reference)}</strong></span><span>${escapeHtmlText(target.task.title)}</span></button>${join}</li>`;
|
|
109
112
|
}).join('')}</ul>`;
|
|
110
113
|
}
|
|
111
114
|
|
|
@@ -63,6 +63,7 @@ body{display:grid;grid-template-rows:minmax(0,1fr);height:100vh;margin:0;backgro
|
|
|
63
63
|
.work-log-origin,.work-log-head,.note-warning{color:var(--text-secondary);font-size:12px;overflow-wrap:anywhere}.note-warning{color:var(--critical)}
|
|
64
64
|
.gantt-warning{margin:16px 0;padding:12px;border:1px solid var(--critical);background:var(--surface-2)}.gantt-warning h2{margin:0 0 8px}.gantt-warning ul{margin:0;padding-left:20px}
|
|
65
65
|
.relation-list button{display:flex;width:100%;flex-direction:column}.relation-list button span{font-weight:400}.relation-kind{display:block;margin-top:4px;color:var(--text-secondary);font-size:12px}
|
|
66
|
+
.relation-head{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}.relation-status{color:var(--text-secondary);font-size:12px;font-weight:600;white-space:nowrap}.relation-status.status-in-progress{color:var(--accent)}.relation-status.status-done{color:var(--good)}.relation-status.status-blocked{color:var(--critical)}
|
|
66
67
|
.task-diagnostics{margin:16px 0;color:var(--text-secondary);font-size:12px}.task-diagnostics summary{cursor:pointer;color:var(--text-primary);font-weight:600}.task-diagnostics dl{display:grid;grid-template-columns:auto 1fr;gap:4px 12px}.task-diagnostics dd{margin:0;overflow-wrap:anywhere}
|
|
67
68
|
.task-index>h1{margin:0 0 8px;font-size:19px;font-weight:650;line-height:1.45}.task-index>p{margin:0 0 24px;color:var(--text-secondary)}
|
|
68
69
|
.task-index-plan+.task-index-plan{margin-top:32px}.task-index-plan h2{margin:0 0 12px;font-size:16px;font-weight:600}.task-index-plan h2 code{font-size:inherit}
|