@quolu/lattice 0.34.2 → 0.36.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 +54 -4
- package/README.md +8 -0
- package/bin/lattice.mjs +15 -3
- package/docs/bridge-setup.md +13 -0
- package/package.json +1 -1
- package/src/cli-help.mjs +20 -9
- package/src/project-cli.mjs +126 -3
- package/src/todo-cli.mjs +224 -22
- package/src/todo-contracts.mjs +2 -2
- package/src/todo-dispatch-shape.mjs +190 -0
- package/src/todo-gantt-html-shared.mjs +2 -1
- package/src/todo-gantt-layout.mjs +13 -3
- package/src/todo-gantt-live.mjs +21 -2
- package/src/todo-gantt-scope.mjs +27 -2
- package/src/todo-migration.mjs +108 -0
- package/src/todo-revision.mjs +568 -2
- package/src/todo-status.mjs +5 -2
- package/src/todo-store.mjs +155 -33
package/README.ja.md
CHANGED
|
@@ -161,12 +161,35 @@ cross-plan topologyを同時に切り替える場合は
|
|
|
161
161
|
Phase付きv5 planでは、通常ToDoの開始順はToDo DAGだけで決まり、Phase前後関係は重監査の順序だけを
|
|
162
162
|
制御します。特定ToDoがPhase受理を本当に必要とする場合だけ`phase_accept_dependencies`で明示します。
|
|
163
163
|
`lattice todo status --json`の`dispatch_frontier`はready全件を同時dispatchする既定を示します。
|
|
164
|
-
readyが複数なら最初のstartに`--parallel-frontier
|
|
165
|
-
`--override-reason <reason
|
|
164
|
+
readyが複数なら最初のstartに`--parallel-frontier`を付けます。subsetだけを直列着手する場合は
|
|
165
|
+
`--override-reason <reason>`で理由を残しますが、**その申告は一度突き返されます**。
|
|
166
166
|
|
|
167
167
|
```bash
|
|
168
168
|
lattice todo start --plan <key> --task <id> --parallel-frontier
|
|
169
|
-
lattice todo start --plan <key> --task <id> --override-reason <reason>
|
|
169
|
+
lattice todo start --plan <key> --task <id> --override-reason <reason> --serial-confirmed
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
直列の申告に対して`PARALLEL_DISPATCH_RECONSIDER`を返し、並列の再検討を促してから、
|
|
173
|
+
同じ理由に`--serial-confirmed`を付けた再実行だけを通します。規則を文書へ書くだけでは
|
|
174
|
+
読み飛ばされるため、再考をコマンドの往復で強制する設計です。**足止めは一度だけで、
|
|
175
|
+
再実行すれば直列で進みます。**
|
|
176
|
+
|
|
177
|
+
ただし理由が**実際の干渉を述べていない**場合——「単一セッションだから」「逐次実行するので」
|
|
178
|
+
のようにworker数・セッション構成・作業者の都合を述べただけの場合——は
|
|
179
|
+
`PARALLEL_DISPATCH_INVALID`で拒否し、`--serial-confirmed`を付けても通しません。
|
|
180
|
+
実行主体が1つしか無いことは並列にできない理由ではない(必要ならworkerを増やす)ためです。
|
|
181
|
+
「両taskが同一fileへ書き込む」のような干渉を書けば、再確認を経て通ります。
|
|
182
|
+
|
|
183
|
+
**同じ検査を計画時点にも掛けます。** 着手時だけを締めても、planそのものが直列に組まれていれば
|
|
184
|
+
並列は生まれません。`lattice plan create`と`lattice todo migrate`は依存グラフから
|
|
185
|
+
`dispatch_shape`(`task_count`/`critical_path_length`/`max_frontier_width`/
|
|
186
|
+
`serialization_ratio`)を計算して結果へ載せ、直列度が閾値を超えるplanを一度突き返します。
|
|
187
|
+
再考した上でなお直列でよいなら`--serialization-reviewed`を付けて再実行します
|
|
188
|
+
(6 task未満のplanは対象外)。判定はstore書込みの前に行うので、拒否された時にstoreへは
|
|
189
|
+
何も書かれません。
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
lattice todo migrate --input <extraction.json> --serialization-reviewed
|
|
170
193
|
```
|
|
171
194
|
|
|
172
195
|
`--parallel-frontier`はhostへ並列dispatch方針を宣言する開始gateです。Lattice自身がAI hostのagentを
|
|
@@ -174,7 +197,34 @@ lattice todo start --plan <key> --task <id> --override-reason <reason>
|
|
|
174
197
|
`active_set`と`next_ready`で観測できます。
|
|
175
198
|
ToDo完了は軽量確認までで、所属ToDoが全てdoneになったPhaseは`gate_ready`となり、`todo phase review`後に
|
|
176
199
|
required evidenceを束縛した`todo phase accept`で重監査の判断を記録します。監査回数やPhase数を自動追加する
|
|
177
|
-
機能ではありません。
|
|
200
|
+
機能ではありません。
|
|
201
|
+
|
|
202
|
+
**監査の既定は「有り」です。** phaseを持たないplanも終端に重監査が要ります(予約Phase
|
|
203
|
+
`terminal-audit`を暗黙に1つ持ちます)。全taskがdoneになった状態は「完走」ではなく`gate_ready`=
|
|
204
|
+
**監査待ち**であり、工程図のlive scopeはそのplanのToDoを畳みません——完走扱いで図から消えることが
|
|
205
|
+
「閉じた」の可視表現なので、監査の記録なしにそこへ行かせません。作成時に拒否はせず、
|
|
206
|
+
`todo migrate`/`plan create`の結果と最後のdoneのadvisoryで`terminal_audit_required`を通知します。
|
|
207
|
+
**終端監査はToDoのdispatch可否へ影響しません**(Phaseは重監査の順序だけを制御し、開始順はToDo DAGが決めます)。
|
|
208
|
+
規約は[ADR 0147](docs/adr/0147-audit-is-on-by-default.md)が正です。
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
lattice todo phase status --plan <key> # phase無しplanでも暗黙Phaseを返す(implicit: true)
|
|
212
|
+
lattice todo phase review --plan <key> --phase terminal-audit --reason <text>
|
|
213
|
+
lattice todo phase accept --plan <key> --phase terminal-audit --input <file>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
誤ってphase無しで作ったplanへ後からPhaseを被せる場合は、`revise-phase`の
|
|
217
|
+
`state_policy: acquire_phase`でdone状態を保ったまま獲得できます(未割当→割当の向きだけを許し、
|
|
218
|
+
既にphaseを持つtaskの付け替えは拒否します)。
|
|
219
|
+
|
|
220
|
+
契約のJSON Schemaは各入口から取れます。入力が合わないときは、違反フィールドのpathが
|
|
221
|
+
error detailの`violation_path`へ載ります(配列のソート違反は`/tasks/1`のようにindexまで名指しします)。
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
lattice plan create --schema --json # 既定は最新v3
|
|
225
|
+
lattice todo revise-phase --schema --json
|
|
226
|
+
lattice plan show <plan_key> --json # planのtask・依存・phase・状態を1コマンドで読む
|
|
227
|
+
```Phase状態は
|
|
178
228
|
`lattice todo phase status --plan <key>`、閲覧中に進捗が更新される工程表は
|
|
179
229
|
`lattice todo gantt serve --port 0`で確認できます。live viewerはloopback-only、read-onlyで、
|
|
180
230
|
`/projects/<project_id>/`というproject固有URLを返します。別projectからそれぞれ起動すれば、独立port・独立SSE経路で同時表示できます。
|
package/README.md
CHANGED
|
@@ -150,6 +150,14 @@ point of use.
|
|
|
150
150
|
transform that cannot be verified is not adopted. A finding that cannot be independently
|
|
151
151
|
re-derived is not recorded.
|
|
152
152
|
|
|
153
|
+
**Heavy audit is on by default.** A plan without explicit phases still carries an implicit
|
|
154
|
+
terminal audit: every task being done means `gate_ready` — *awaiting audit* — not finished. The
|
|
155
|
+
live dependency diagram refuses to fold such a plan away, because folding is how the product
|
|
156
|
+
says "closed", and nothing gets there without an evidence-bound `phase accept`. Creation is never
|
|
157
|
+
rejected over it; the requirement is reported instead. And the audit gate never touches dispatch:
|
|
158
|
+
phases order reviews, the ToDo DAG orders work
|
|
159
|
+
([ADR 0147](docs/adr/0147-audit-is-on-by-default.md)).
|
|
160
|
+
|
|
153
161
|
## Patent
|
|
154
162
|
|
|
155
163
|
The design in this repository is the subject of a Japanese patent application:
|
package/bin/lattice.mjs
CHANGED
|
@@ -30,10 +30,14 @@ if (help !== null) {
|
|
|
30
30
|
cwd: process.cwd(), stdout: process.stdout, cliVersion: packageJson.version, error,
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
-
} else if (args.length === 4
|
|
33
|
+
} else if ((args.length === 4 || args.length === 5) && args[0] === 'plan' && args[1] === 'create'
|
|
34
|
+
&& args[2] === '--input' && (args.length === 4 || args[4] === '--serialization-reviewed')) {
|
|
34
35
|
const { projectCliFailure, runPlanCreate } = await import('../src/project-cli.mjs');
|
|
35
36
|
try {
|
|
36
|
-
process.exitCode = await runPlanCreate({
|
|
37
|
+
process.exitCode = await runPlanCreate({
|
|
38
|
+
cwd: process.cwd(), inputRef: args[3], stdout: process.stdout,
|
|
39
|
+
serializationReviewed: args.length === 5,
|
|
40
|
+
});
|
|
37
41
|
} catch (error) {
|
|
38
42
|
process.exitCode = projectCliFailure(process.stderr, error);
|
|
39
43
|
}
|
|
@@ -46,13 +50,21 @@ if (help !== null) {
|
|
|
46
50
|
process.exitCode = projectCliFailure(process.stderr, error);
|
|
47
51
|
}
|
|
48
52
|
} else if (args.length === 5 && args[0] === 'plan' && args[1] === 'create'
|
|
49
|
-
&& args[2] === '--schema-version' && ['2', '3'].includes(args[3]) && args[4] === '--json') {
|
|
53
|
+
&& args[2] === '--schema-version' && ['1', '2', '3'].includes(args[3]) && args[4] === '--json') {
|
|
50
54
|
const { projectCliFailure, runPlanCreateSchema } = await import('../src/project-cli.mjs');
|
|
51
55
|
try {
|
|
52
56
|
process.exitCode = await runPlanCreateSchema({ stdout: process.stdout, version: Number(args[3]) });
|
|
53
57
|
} catch (error) {
|
|
54
58
|
process.exitCode = projectCliFailure(process.stderr, error);
|
|
55
59
|
}
|
|
60
|
+
} else if (args.length === 4 && args[0] === 'plan' && args[1] === 'show'
|
|
61
|
+
&& typeof args[2] === 'string' && args[2].length > 0 && args[3] === '--json') {
|
|
62
|
+
const { projectCliFailure, runPlanShow } = await import('../src/project-cli.mjs');
|
|
63
|
+
try {
|
|
64
|
+
process.exitCode = await runPlanShow({ cwd: process.cwd(), planKey: args[2], stdout: process.stdout });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
process.exitCode = projectCliFailure(process.stderr, error);
|
|
67
|
+
}
|
|
56
68
|
} else if (args.length === 2 && args[0] === 'factory-diagnostics' && args[1] === '--json') {
|
|
57
69
|
const { buildFactoryDiagnostics } = await import('../src/factory-diagnostics.mjs');
|
|
58
70
|
const diagnostics = await buildFactoryDiagnostics();
|
package/docs/bridge-setup.md
CHANGED
|
@@ -107,6 +107,19 @@ public hostname `lattice.kitepon.dev` を次のoriginへ対応付ける。
|
|
|
107
107
|
3. **Cloudflare public HTTPS**: `https://lattice.kitepon.dev/projects/`がredirectなしで200となり、一覧から開いた
|
|
108
108
|
`/projects/<project_id>/`のHTML titleが`Lattice — <project名> 依存工程図`である。
|
|
109
109
|
|
|
110
|
+
公開viewerの404も、ブラウザとAPIの両契約を別々に確認する。未知URLへ`Accept: text/html`を
|
|
111
|
+
付けたrequestはHTTP 404かつ`Content-Type: text/html`で、`noindex, nofollow`と
|
|
112
|
+
`/projects/`、`https://kitepon.dev/`への戻り先を持つ。`Accept: application/json`では
|
|
113
|
+
HTTP 404かつ`Content-Type: application/json`で、既存の
|
|
114
|
+
`lattice.todo_gantt_http_error.v1`を返す。
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
curl --silent --show-error --include --header 'Accept: text/html' \
|
|
118
|
+
https://lattice.kitepon.dev/unknown
|
|
119
|
+
curl --silent --show-error --include --header 'Accept: application/json' \
|
|
120
|
+
https://lattice.kitepon.dev/unknown
|
|
121
|
+
```
|
|
122
|
+
|
|
110
123
|
外部gateはHTMLだけで閉じず、各projectの
|
|
111
124
|
`https://lattice.kitepon.dev/projects/<project_id>/events`も確認する。応答は200かつ
|
|
112
125
|
`Content-Type: text/event-stream`で、接続直後に`event: state`と現在の`head_digest`を返さなければならない。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.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
|
@@ -23,9 +23,12 @@ const NAMESPACE_HELP = Object.freeze({
|
|
|
23
23
|
plan: `Usage: lattice plan <command> [options]
|
|
24
24
|
|
|
25
25
|
Commands:
|
|
26
|
-
create --input <file>
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
create --input <file> [--serialization-reviewed]
|
|
27
|
+
# 依存グラフがほぼ一直線(serialization_ratioが閾値超)なら一度突き返す。
|
|
28
|
+
# 再考した上でなお直列でよいなら --serialization-reviewed を付けて再実行する
|
|
29
|
+
create --schema --json # 既定は最新版(v3)のJSON Schemaを返す
|
|
30
|
+
create --schema-version <1|2|3> --json
|
|
31
|
+
show <plan_key> --json # task・依存・phase・状態をplan本体から1コマンドで投影する
|
|
29
32
|
compile --request <request.json>
|
|
30
33
|
compile --schema --json # lattice.run_request.v1 の JSON Schema を出す
|
|
31
34
|
verify --request <request.json> --plan <plan.json>
|
|
@@ -70,7 +73,11 @@ Read commands:
|
|
|
70
73
|
phase status --plan <key>
|
|
71
74
|
|
|
72
75
|
Write commands:
|
|
73
|
-
|
|
76
|
+
migrate --input <extraction.json> [--serialization-reviewed]
|
|
77
|
+
# 既存storeへplanを追加する(plan createは空store初期化専用)。
|
|
78
|
+
# 依存グラフがほぼ一直線なら一度突き返し、再考後の --serialization-reviewed で通す
|
|
79
|
+
start --plan <key> --task <id> [--parallel-frontier|--override-reason <text> [--serial-confirmed]]
|
|
80
|
+
# 既定は全ready同時dispatch。直列にするには理由の申告後、再考を経て --serial-confirmed が要る
|
|
74
81
|
block --plan <key> --task <id> --reason <text>
|
|
75
82
|
unblock --plan <key> --task <id>
|
|
76
83
|
done --plan <key> --task <id> --evidence <file>
|
|
@@ -85,6 +92,9 @@ Write commands:
|
|
|
85
92
|
revise --plan <key> --input <file>
|
|
86
93
|
revise-phase --plan <key> --input <file>
|
|
87
94
|
revise-set --input <file>
|
|
95
|
+
<revise|revise-phase|revise-set|migrate> --schema --json
|
|
96
|
+
# 実際に受理する最新契約のJSON Schemaを返す(storeを読まない)。
|
|
97
|
+
# 入力が契約に合わないときは、違反フィールドのpathがerror detailへ載る
|
|
88
98
|
phase review --plan <key> --phase <id> --reason <text>
|
|
89
99
|
phase <accept|reject> --plan <key> --phase <id> --input <file>
|
|
90
100
|
phase reopen --plan <key> --phase <id> --reason <text> [--override-reason <text>]
|
|
@@ -129,7 +139,8 @@ registerはLATTICE_BRIDGE_REGISTRAR_SSH_HOSTとLATTICE_BRIDGE_REGISTRAR_SCRIPT
|
|
|
129
139
|
const SUBCOMMAND_USAGE = Object.freeze({
|
|
130
140
|
status: 'status --json',
|
|
131
141
|
'session-context': 'session-context --json',
|
|
132
|
-
'plan create': 'plan create --input <file> | --schema --json | --schema-version <2|3> --json',
|
|
142
|
+
'plan create': 'plan create --input <file> [--serialization-reviewed] | --schema --json | --schema-version <1|2|3> --json',
|
|
143
|
+
'plan show': 'plan show <plan_key> --json',
|
|
133
144
|
'plan compile': 'plan compile --request <request.json> | --schema --json',
|
|
134
145
|
'plan verify': 'plan verify --request <request.json> --plan <plan.json>',
|
|
135
146
|
'run start': 'run start --request <request.json> --executor <adapter> | --schema --json',
|
|
@@ -171,10 +182,10 @@ const SUBCOMMAND_USAGE = Object.freeze({
|
|
|
171
182
|
'todo reopen': 'todo reopen --plan <key> --task <id> --reason <text> [--override-reason <text>]',
|
|
172
183
|
'todo evidence': 'todo evidence promote --plan <key> --task <id> --evidence <file>',
|
|
173
184
|
'todo evidence promote': 'todo evidence promote --plan <key> --task <id> --evidence <file>',
|
|
174
|
-
'todo revise': 'todo revise --plan <key> --input <file>',
|
|
175
|
-
'todo revise-phase': 'todo revise-phase --plan <key> --input <file>',
|
|
176
|
-
'todo revise-set': 'todo revise-set --input <file>',
|
|
177
|
-
'todo migrate': 'todo migrate --input <extraction.json>',
|
|
185
|
+
'todo revise': 'todo revise --plan <key> --input <file> | --schema --json',
|
|
186
|
+
'todo revise-phase': 'todo revise-phase --plan <key> --input <file> | --schema --json',
|
|
187
|
+
'todo revise-set': 'todo revise-set --input <file> | --schema --json',
|
|
188
|
+
'todo migrate': 'todo migrate --input <extraction.json> [--serialization-reviewed] | --schema --json',
|
|
178
189
|
'sensor init': 'sensor init [path] --json',
|
|
179
190
|
'sensor sync': 'sensor sync [path] --json',
|
|
180
191
|
'runtime-errors snapshot': 'runtime-errors snapshot [--after-cursor <n>] [--limit <n>] --json',
|
package/src/project-cli.mjs
CHANGED
|
@@ -22,10 +22,15 @@ import {
|
|
|
22
22
|
buildTodoPlan,
|
|
23
23
|
createTodoStoreWriter,
|
|
24
24
|
initializeAuthoredTodoStore,
|
|
25
|
+
isPhaselessTodoPlanSchema,
|
|
25
26
|
readTodoIndependenceArtifact,
|
|
26
27
|
readTodoStore,
|
|
27
28
|
TodoStoreError,
|
|
28
29
|
} from './todo-store.mjs';
|
|
30
|
+
import {
|
|
31
|
+
assertTodoDispatchShapeReviewed,
|
|
32
|
+
computeTodoDispatchShapeForPlan,
|
|
33
|
+
} from './todo-dispatch-shape.mjs';
|
|
29
34
|
|
|
30
35
|
const STORE_REF = '.lattice/todo';
|
|
31
36
|
const MANIFEST_REF = `${STORE_REF}/manifest.json`;
|
|
@@ -432,11 +437,21 @@ function validateCreateInput(value) {
|
|
|
432
437
|
} catch { return false; }
|
|
433
438
|
}
|
|
434
439
|
|
|
435
|
-
export async function runPlanCreate({ cwd, inputRef, stdout }) {
|
|
440
|
+
export async function runPlanCreate({ cwd, inputRef, stdout, serializationReviewed = false }) {
|
|
436
441
|
const repoRoot = resolveRepoRoot(cwd);
|
|
437
442
|
if (repoRoot === null) throw new TodoStoreError('REPO_UNRESOLVED', 'git_toplevel_unresolved');
|
|
438
443
|
const input = await readCanonicalInput(repoRoot, inputRef);
|
|
439
444
|
if (!validateCreateInput(input)) throw new TodoStoreError('INPUT_INVALID', 'plan_create_schema_invalid');
|
|
445
|
+
// dispatch_shapeのgateはstore初期化より前に判定する(拒否時にstoreへ何も書かないため、
|
|
446
|
+
// 再考後の再実行がplan_key_already_existsで詰まらない)。
|
|
447
|
+
const dispatchShape = computeTodoDispatchShapeForPlan({
|
|
448
|
+
projectId: input.project_id,
|
|
449
|
+
planKey: input.plan_key,
|
|
450
|
+
taskIds: input.tasks.map(({ task_id: taskId }) => taskId),
|
|
451
|
+
hardDependencies: input.hard_dependencies,
|
|
452
|
+
joins: input.joins,
|
|
453
|
+
});
|
|
454
|
+
assertTodoDispatchShapeReviewed({ shape: dispatchShape, reviewed: serializationReviewed });
|
|
440
455
|
const store = await initializeAuthoredTodoStore({
|
|
441
456
|
repoRoot, writer: createTodoStoreWriter({ caller: 'g5-authoring' }),
|
|
442
457
|
projectId: input.project_id, repositories: [{ repo_id: 'self', path: '.' }],
|
|
@@ -460,14 +475,33 @@ export async function runPlanCreate({ cwd, inputRef, stdout }) {
|
|
|
460
475
|
plan_key: member.plan.plan_key, plan_version: member.plan.plan_version,
|
|
461
476
|
store_ref: STORE_REF, plan_ref: member.descriptor.plan_ref,
|
|
462
477
|
journal_ref: member.descriptor.journal_ref, snapshot_ref: member.descriptor.snapshot_ref,
|
|
463
|
-
plan_digest: member.plan.plan_digest,
|
|
478
|
+
plan_digest: member.plan.plan_digest,
|
|
479
|
+
dispatch_shape: {
|
|
480
|
+
task_count: dispatchShape.task_count,
|
|
481
|
+
critical_path_length: dispatchShape.critical_path_length,
|
|
482
|
+
max_frontier_width: dispatchShape.max_frontier_width,
|
|
483
|
+
serialization_ratio: dispatchShape.serialization_ratio,
|
|
484
|
+
},
|
|
485
|
+
// ADR 0147裁定3: phase無し(v3)のplan createは拒否せず、終端監査が要ることを結果へ
|
|
486
|
+
// 明示するに留める。phase入力(v4/v5)ならfalse——既存のPhase gateがそのまま重監査を担う。
|
|
487
|
+
terminal_audit_required: isPhaselessTodoPlanSchema(member.plan.schema),
|
|
488
|
+
result_digest: '',
|
|
464
489
|
};
|
|
465
490
|
result.result_digest = resultDigest(result);
|
|
466
491
|
stdout.write(`${JSON.stringify(result)}\n`);
|
|
467
492
|
return 0;
|
|
468
493
|
}
|
|
469
494
|
|
|
470
|
-
|
|
495
|
+
/**
|
|
496
|
+
* `--schema --json`(版指定なし)の既定はCURRENT_CREATE_INPUT_SCHEMAと同じv3にする。
|
|
497
|
+
* 既定がv1のままだと、素の`--schema`を叩いたAIが古いv1(Phaseを表現できない)を
|
|
498
|
+
* 受け取り、実運用で通らない入力を作ってしまう(実際に踏んだ)。
|
|
499
|
+
* `--schema-version 1`は互換のため引き続き取得できる(bin/lattice.mjs側で許可)。
|
|
500
|
+
*
|
|
501
|
+
* 「どの版を返したか」は返すJSON Schema自身の`title`(例: `lattice.plan_create_input.v3`)が
|
|
502
|
+
* 既に機械可読に持っている。壊さずに追加のkeyを足す理由が無いので足さない。
|
|
503
|
+
*/
|
|
504
|
+
export async function runPlanCreateSchema({ stdout, version = 3 }) {
|
|
471
505
|
if (![1, 2, 3].includes(version)) throw new TypeError('unsupported plan create schema version');
|
|
472
506
|
const expected = version === 3 ? DECOUPLED_PHASE_CREATE_INPUT_SCHEMA
|
|
473
507
|
: version === 2 ? PHASE_CREATE_INPUT_SCHEMA : CREATE_INPUT_SCHEMA;
|
|
@@ -483,6 +517,95 @@ export async function runPlanCreateSchema({ stdout, version = 1 }) {
|
|
|
483
517
|
}
|
|
484
518
|
}
|
|
485
519
|
|
|
520
|
+
const PLAN_SHOW_RESULT_SCHEMA = 'lattice.plan_show_result.v1';
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* `todo bindings`はcompile_binding付きtaskだけを投影するので、通常planでは空配列を返す。
|
|
524
|
+
* それを見て「planが空だ」と誤読された実績があるため、plan本体(task・依存・phase・状態)を
|
|
525
|
+
* 1コマンドで読める面を別に持つ。読み出しは既存store readerとtodo status projectionの
|
|
526
|
+
* 再利用に留め、journalを独自に再実装しない。
|
|
527
|
+
*/
|
|
528
|
+
export async function runPlanShow({ cwd, planKey, stdout }) {
|
|
529
|
+
const repoRoot = resolveRepoRoot(cwd);
|
|
530
|
+
if (repoRoot === null) throw new TodoStoreError('REPO_UNRESOLVED', 'git_toplevel_unresolved');
|
|
531
|
+
const store = await readTodoStore({ repoRoot });
|
|
532
|
+
const member = store.members.find(({ descriptor }) => descriptor.plan_key === planKey);
|
|
533
|
+
if (member === undefined) {
|
|
534
|
+
// 既存read commands(independence compile等)と同じcode/reasonを踏襲する。
|
|
535
|
+
// 未知のplan_keyを「store不整合」と同じ扱いにするのはこのCLI全体の既定であり、
|
|
536
|
+
// ここだけ別codeへ逸れると呼び出し側の分岐が増える。
|
|
537
|
+
throw new TodoStoreError('STORE_INCONSISTENT', 'plan_not_active', undefined, {
|
|
538
|
+
plan_key: planKey, next_action: 'check_active_plans_via_status',
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
const { plan, tasks, phases } = member;
|
|
542
|
+
const phaseInput = ['lattice.todo_plan.v4', 'lattice.todo_plan.v5'].includes(plan.schema);
|
|
543
|
+
const stateByTaskId = new Map(tasks.map((state) => [state.task_id, state]));
|
|
544
|
+
// snapshot artifactの形式(v1にはphasesキーが無い)には縛られない導出ビューを読む
|
|
545
|
+
// (readTodoStoreが常に member.phases として埋める。ADR 0147)。
|
|
546
|
+
const phaseStatusById = new Map((phases ?? []).map((phase) => [phase.phase_id, phase.status]));
|
|
547
|
+
|
|
548
|
+
// 依存の本数: このplanの中でそのtaskへ入ってくるhard_dependencies辺と、joinで
|
|
549
|
+
// 合流するafter辺の合計。cross-plan参照は数えない(plan showは単一planの投影のため)。
|
|
550
|
+
const dependsOnCount = new Map(plan.tasks.map((task) => [task.task_id, 0]));
|
|
551
|
+
for (const edge of plan.hard_dependencies) {
|
|
552
|
+
if (edge.to.plan_key === planKey && dependsOnCount.has(edge.to.task_id)) {
|
|
553
|
+
dependsOnCount.set(edge.to.task_id, dependsOnCount.get(edge.to.task_id) + 1);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
for (const join of plan.joins) {
|
|
557
|
+
if (join.before.plan_key === planKey && dependsOnCount.has(join.before.task_id)) {
|
|
558
|
+
dependsOnCount.set(join.before.task_id, dependsOnCount.get(join.before.task_id) + join.after.length);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const taskList = plan.tasks.map((task) => ({
|
|
563
|
+
task_id: task.task_id,
|
|
564
|
+
title: task.title,
|
|
565
|
+
lane: task.lane,
|
|
566
|
+
phase_id: phaseInput ? task.phase_id : null,
|
|
567
|
+
state: stateByTaskId.get(task.task_id).status,
|
|
568
|
+
depends_on_count: dependsOnCount.get(task.task_id) ?? 0,
|
|
569
|
+
}));
|
|
570
|
+
|
|
571
|
+
const phaseList = phaseInput ? plan.phases.map((phase) => ({
|
|
572
|
+
phase_id: phase.phase_id,
|
|
573
|
+
title: phase.title,
|
|
574
|
+
gate_policy: phase.gate_policy,
|
|
575
|
+
predecessor_phase_ids: phase.predecessor_phase_ids,
|
|
576
|
+
status: phaseStatusById.get(phase.phase_id) ?? null,
|
|
577
|
+
})) : [];
|
|
578
|
+
|
|
579
|
+
// dispatch形状はplan create時に既に計算している同じ関数を再利用する。
|
|
580
|
+
// critical path長・frontier幅を独自に計算し直さない。
|
|
581
|
+
const dispatchShape = computeTodoDispatchShapeForPlan({
|
|
582
|
+
projectId: plan.project_id, planKey,
|
|
583
|
+
taskIds: plan.tasks.map(({ task_id: taskId }) => taskId),
|
|
584
|
+
hardDependencies: plan.hard_dependencies, joins: plan.joins,
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const result = {
|
|
588
|
+
schema: PLAN_SHOW_RESULT_SCHEMA,
|
|
589
|
+
project_id: plan.project_id,
|
|
590
|
+
plan_key: plan.plan_key,
|
|
591
|
+
plan_version: plan.plan_version,
|
|
592
|
+
plan_schema: plan.schema,
|
|
593
|
+
has_phases: phaseInput,
|
|
594
|
+
phases: phaseList,
|
|
595
|
+
tasks: taskList,
|
|
596
|
+
topology: {
|
|
597
|
+
task_count: dispatchShape.task_count,
|
|
598
|
+
critical_path_length: dispatchShape.critical_path_length,
|
|
599
|
+
max_frontier_width: dispatchShape.max_frontier_width,
|
|
600
|
+
serialization_ratio: dispatchShape.serialization_ratio,
|
|
601
|
+
},
|
|
602
|
+
result_digest: '',
|
|
603
|
+
};
|
|
604
|
+
result.result_digest = resultDigest(result);
|
|
605
|
+
stdout.write(`${JSON.stringify(result)}\n`);
|
|
606
|
+
return 0;
|
|
607
|
+
}
|
|
608
|
+
|
|
486
609
|
export function projectStatusFailure({ cwd, stdout, cliVersion, error }) {
|
|
487
610
|
const result = invalidStatus({
|
|
488
611
|
cliVersion, repoRoot: resolveRepoRoot(cwd),
|