@quolu/lattice 0.59.1 → 0.60.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/lattice.mjs +5 -5
- package/docs/schemas/lattice.todo_extraction.v3.schema.json +5 -1
- package/docs/schemas/lattice.todo_extraction.v4.schema.json +161 -0
- package/package.json +2 -1
- package/src/cli-help.mjs +2 -2
- package/src/plan-scope-review.mjs +7 -4
- package/src/project-cli.mjs +26 -3
- package/src/runtime-pull-intake.mjs +34 -4
- package/src/sensor-node-runtime.mjs +7 -9
- package/src/todo-cli.mjs +106 -42
- package/src/todo-independence-authoritative-observation.mjs +116 -0
- package/src/todo-independence-guidance.mjs +25 -0
- package/src/todo-migration.mjs +124 -48
- package/src/todo-store.mjs +517 -40
- package/src/todo-structure-store.mjs +28 -0
package/bin/lattice.mjs
CHANGED
|
@@ -2,22 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { installPipeCloseGuard } from '../src/cli-stdio.mjs';
|
|
5
|
-
import { runRuntimeCli } from '../src/runtime-cli.mjs';
|
|
6
5
|
import { renderCliHelp } from '../src/cli-help.mjs';
|
|
7
|
-
import {
|
|
6
|
+
import { relaunchForNode22IfNeeded } from '../src/sensor-node-runtime.mjs';
|
|
8
7
|
import packageJson from '../package.json' with { type: 'json' };
|
|
9
8
|
|
|
10
9
|
installPipeCloseGuard();
|
|
11
10
|
|
|
12
11
|
const args = process.argv.slice(2);
|
|
13
|
-
const
|
|
12
|
+
const node22RelaunchStatus = relaunchForNode22IfNeeded({
|
|
14
13
|
args,
|
|
15
14
|
scriptPath: fileURLToPath(import.meta.url),
|
|
16
15
|
});
|
|
17
16
|
const help = renderCliHelp(args);
|
|
18
17
|
|
|
19
|
-
if (
|
|
20
|
-
process.exitCode =
|
|
18
|
+
if (node22RelaunchStatus !== null) {
|
|
19
|
+
process.exitCode = node22RelaunchStatus;
|
|
21
20
|
} else if (help !== null) {
|
|
22
21
|
process.stdout.write(help);
|
|
23
22
|
} else if (args.length === 1 && args[0] === '--version') {
|
|
@@ -125,6 +124,7 @@ if (sensorRelaunchStatus !== null) {
|
|
|
125
124
|
});
|
|
126
125
|
} else {
|
|
127
126
|
try {
|
|
127
|
+
const { runRuntimeCli } = await import('../src/runtime-cli.mjs');
|
|
128
128
|
process.exitCode = await runRuntimeCli({
|
|
129
129
|
argv: args,
|
|
130
130
|
cwd: process.cwd(),
|
|
@@ -22,7 +22,11 @@
|
|
|
22
22
|
"type": "array", "maxItems": 2048,
|
|
23
23
|
"items": {
|
|
24
24
|
"type": "object", "additionalProperties": false, "required": ["from", "to"],
|
|
25
|
-
"properties": {
|
|
25
|
+
"properties": {
|
|
26
|
+
"from": { "$ref": "#/$defs/nodeRef" },
|
|
27
|
+
"to": { "$ref": "#/$defs/nodeRef" },
|
|
28
|
+
"reason": { "$ref": "#/$defs/text" }
|
|
29
|
+
}
|
|
26
30
|
}
|
|
27
31
|
},
|
|
28
32
|
"joins": {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/kitepon-rgb/Lattice/blob/main/docs/schemas/lattice.todo_extraction.v4.schema.json",
|
|
4
|
+
"title": "lattice.todo_extraction.v4",
|
|
5
|
+
"description": "AI-authored ToDo migration artifact. Every task carries a non-blank Markdown design_memo. After the complete plan is written, run `lattice plan scope-review` and confirm that every registered task preserves an owner-supplied work specification without adding a product purpose. If no plan exists, write NO_PLAN explicitly. Runtime validation accepts at most one explicit, reasoned cross-plan dependency; an empty tasks array is reserved for that one dependency between existing tasks, with the top-level plan identity naming the source plan and both endpoint topology digests.",
|
|
6
|
+
"$comment": "あなたがこのToDoに対して、何も考えていないならば、設計メモに `NO_PLAN` と書いてください。Runtime validation also enforces UTF-8 byte bounds, exact keys, strict sort order, referential integrity, disposition consistency, the cross-plan dependency limit, and the canonical self-digest.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"required": [
|
|
10
|
+
"schema", "project_id", "plan_key", "plan_version", "actor", "recorded_at",
|
|
11
|
+
"tasks", "hard_dependencies", "joins", "extraction_digest"
|
|
12
|
+
],
|
|
13
|
+
"properties": {
|
|
14
|
+
"schema": { "const": "lattice.todo_extraction.v4" },
|
|
15
|
+
"project_id": { "$ref": "#/$defs/identifier" },
|
|
16
|
+
"plan_key": { "$ref": "#/$defs/identifier" },
|
|
17
|
+
"plan_version": { "$ref": "#/$defs/identifier" },
|
|
18
|
+
"actor": { "$ref": "#/$defs/actor" },
|
|
19
|
+
"recorded_at": { "$ref": "#/$defs/strictTimestamp" },
|
|
20
|
+
"tasks": { "type": "array", "minItems": 0, "maxItems": 512, "items": { "$ref": "#/$defs/task" } },
|
|
21
|
+
"hard_dependencies": {
|
|
22
|
+
"type": "array", "maxItems": 2048,
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "object", "additionalProperties": false, "required": ["from", "to"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"from": { "$ref": "#/$defs/nodeRef" },
|
|
27
|
+
"to": { "$ref": "#/$defs/nodeRef" },
|
|
28
|
+
"reason": { "$ref": "#/$defs/text" }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"joins": {
|
|
33
|
+
"type": "array", "maxItems": 128,
|
|
34
|
+
"items": {
|
|
35
|
+
"type": "object", "additionalProperties": false, "required": ["id", "after", "before"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"id": { "$ref": "#/$defs/identifier" },
|
|
38
|
+
"after": { "type": "array", "minItems": 1, "maxItems": 512, "items": { "$ref": "#/$defs/nodeRef" } },
|
|
39
|
+
"before": { "$ref": "#/$defs/nodeRef" }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"extraction_digest": { "$ref": "#/$defs/digest" }
|
|
44
|
+
},
|
|
45
|
+
"allOf": [
|
|
46
|
+
{
|
|
47
|
+
"if": { "properties": { "tasks": { "maxItems": 0 } }, "required": ["tasks"] },
|
|
48
|
+
"then": {
|
|
49
|
+
"properties": {
|
|
50
|
+
"hard_dependencies": { "minItems": 1, "maxItems": 1 },
|
|
51
|
+
"joins": { "maxItems": 0 }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"$defs": {
|
|
57
|
+
"identifier": { "type": "string", "pattern": "^[0-9A-Za-z](?:[0-9A-Za-z._-]{0,127})$" },
|
|
58
|
+
"digest": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
59
|
+
"repoRef": {
|
|
60
|
+
"type": "string", "minLength": 1, "maxLength": 1024,
|
|
61
|
+
"pattern": "^(?!/)(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*\\\\)(?!.*[\\u0000-\\u001f\\u007f])[^/]+(?:/[^/]+)*$"
|
|
62
|
+
},
|
|
63
|
+
"text": { "type": "string", "minLength": 1, "maxLength": 16384 },
|
|
64
|
+
"designMemo": {
|
|
65
|
+
"type": "string", "minLength": 1, "maxLength": 16384, "pattern": "\\S",
|
|
66
|
+
"description": "Markdown design memo. Use the literal NO_PLAN only when no plan exists."
|
|
67
|
+
},
|
|
68
|
+
"nullableText": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/text" }] },
|
|
69
|
+
"strictTimestamp": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$"
|
|
72
|
+
},
|
|
73
|
+
"actor": {
|
|
74
|
+
"type": "object", "additionalProperties": false, "required": ["host", "session", "agent"],
|
|
75
|
+
"properties": {
|
|
76
|
+
"host": { "$ref": "#/$defs/identifier" },
|
|
77
|
+
"session": { "$ref": "#/$defs/identifier" },
|
|
78
|
+
"agent": { "$ref": "#/$defs/identifier" }
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"nodeRef": {
|
|
82
|
+
"type": "object", "additionalProperties": false, "required": ["project_id", "plan_key", "task_id"],
|
|
83
|
+
"properties": {
|
|
84
|
+
"project_id": { "$ref": "#/$defs/identifier" },
|
|
85
|
+
"plan_key": { "$ref": "#/$defs/identifier" },
|
|
86
|
+
"task_id": { "$ref": "#/$defs/identifier" },
|
|
87
|
+
"expected_topology_digest": { "$ref": "#/$defs/digest" }
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"sourceLocation": {
|
|
91
|
+
"type": "object", "additionalProperties": false,
|
|
92
|
+
"required": ["origin_plan_ref", "origin_line", "source_commit", "heading_path", "markdown_depth", "parent_task_id", "checkbox_state"],
|
|
93
|
+
"properties": {
|
|
94
|
+
"origin_plan_ref": { "$ref": "#/$defs/repoRef" },
|
|
95
|
+
"origin_line": { "type": "integer", "minimum": 1 },
|
|
96
|
+
"source_commit": { "type": "string", "pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$" },
|
|
97
|
+
"heading_path": { "type": "array", "maxItems": 32, "items": { "type": "string", "minLength": 1, "maxLength": 1024 } },
|
|
98
|
+
"markdown_depth": { "type": "integer", "minimum": 0, "maximum": 64 },
|
|
99
|
+
"parent_task_id": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/identifier" }] },
|
|
100
|
+
"checkbox_state": { "enum": ["checked", "unchecked", "absent", "ambiguous"] }
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"migrationContext": {
|
|
104
|
+
"type": "object", "additionalProperties": false,
|
|
105
|
+
"required": ["external_canonical_ref", "carry_over_ref", "h_required", "condition", "evidence_refs", "notes"],
|
|
106
|
+
"properties": {
|
|
107
|
+
"external_canonical_ref": { "$ref": "#/$defs/nullableText" },
|
|
108
|
+
"carry_over_ref": { "$ref": "#/$defs/nullableText" },
|
|
109
|
+
"h_required": { "type": "boolean" },
|
|
110
|
+
"condition": { "$ref": "#/$defs/nullableText" },
|
|
111
|
+
"evidence_refs": { "type": "array", "maxItems": 64, "items": { "type": "string", "minLength": 1, "maxLength": 4096 } },
|
|
112
|
+
"notes": { "type": "array", "maxItems": 64, "items": { "type": "string", "minLength": 1, "maxLength": 4096 } }
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"historicalStart": {
|
|
116
|
+
"type": "object", "additionalProperties": false, "required": ["start_mode", "status", "started_at"],
|
|
117
|
+
"properties": {
|
|
118
|
+
"start_mode": { "const": "historical_import" }, "status": { "const": "in-progress" },
|
|
119
|
+
"started_at": { "oneOf": [{ "const": "unknown_requires_evidence" }, { "$ref": "#/$defs/strictTimestamp" }] }
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"historicalCompletion": {
|
|
123
|
+
"type": "object", "additionalProperties": false, "required": ["done_mode", "completed_at"],
|
|
124
|
+
"properties": {
|
|
125
|
+
"done_mode": { "const": "historical_import" },
|
|
126
|
+
"completed_at": { "oneOf": [{ "const": "unknown_requires_evidence" }, { "$ref": "#/$defs/strictTimestamp" }] }
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"task": {
|
|
130
|
+
"type": "object", "additionalProperties": false,
|
|
131
|
+
"required": [
|
|
132
|
+
"task_id", "title", "lane", "design_memo", "narrative_ref", "compile_binding",
|
|
133
|
+
"disposition", "start", "completion", "source", "migration_context"
|
|
134
|
+
],
|
|
135
|
+
"properties": {
|
|
136
|
+
"task_id": { "$ref": "#/$defs/identifier" },
|
|
137
|
+
"title": { "$ref": "#/$defs/text" },
|
|
138
|
+
"lane": { "$ref": "#/$defs/identifier" },
|
|
139
|
+
"design_memo": { "$ref": "#/$defs/designMemo" },
|
|
140
|
+
"narrative_ref": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/repoRef" }] },
|
|
141
|
+
"compile_binding": { "type": "null" },
|
|
142
|
+
"disposition": { "enum": ["register_pending", "register_in_progress", "register_done", "exclude_superseded", "exclude_compatibility_record", "unknown_requires_evidence"] },
|
|
143
|
+
"start": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/historicalStart" }] },
|
|
144
|
+
"completion": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/historicalCompletion" }] },
|
|
145
|
+
"source": { "$ref": "#/$defs/sourceLocation" },
|
|
146
|
+
"migration_context": { "$ref": "#/$defs/migrationContext" }
|
|
147
|
+
},
|
|
148
|
+
"allOf": [
|
|
149
|
+
{
|
|
150
|
+
"if": { "properties": { "disposition": { "const": "register_in_progress" } }, "required": ["disposition"] },
|
|
151
|
+
"then": { "properties": { "start": { "$ref": "#/$defs/historicalStart" }, "completion": { "type": "null" } } },
|
|
152
|
+
"else": {
|
|
153
|
+
"if": { "properties": { "disposition": { "const": "register_done" } }, "required": ["disposition"] },
|
|
154
|
+
"then": { "properties": { "start": { "type": "null" }, "completion": { "$ref": "#/$defs/historicalCompletion" } } },
|
|
155
|
+
"else": { "properties": { "start": { "type": "null" }, "completion": { "type": "null" } } }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.1",
|
|
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",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"docs/schemas/lattice.phase_todo_revision.v3.schema.json",
|
|
36
36
|
"docs/schemas/lattice.todo_extraction.v2.schema.json",
|
|
37
37
|
"docs/schemas/lattice.todo_extraction.v3.schema.json",
|
|
38
|
+
"docs/schemas/lattice.todo_extraction.v4.schema.json",
|
|
38
39
|
"docs/schemas/lattice.todo_structure_set.v1.schema.json",
|
|
39
40
|
"docs/schemas/lattice.todo_structure_realization.v1.schema.json",
|
|
40
41
|
"docs/schemas/lattice.todo_structure_binding.v1.schema.json",
|
package/src/cli-help.mjs
CHANGED
|
@@ -91,7 +91,7 @@ Write commands:
|
|
|
91
91
|
dashboard remove <project_id> --json # 登録簿から1件外す(対象repoの外からも叩ける)
|
|
92
92
|
note --plan <key> [--task <id>] (--message <text>|--input <file>)
|
|
93
93
|
# ToDoへ作業継続に必要な方針・調査結果・注意をappend-onlyで追記する
|
|
94
|
-
migrate --input <extraction.json> [--serialization-reviewed]
|
|
94
|
+
migrate --input <extraction.json> [--serialization-reviewed] [--json]
|
|
95
95
|
migrate --input <extraction.json> --dry-run --json [--serialization-reviewed]
|
|
96
96
|
# 既存storeへplanを追加する(plan createは空store初期化専用)。
|
|
97
97
|
# 依存グラフがほぼ一直線なら一度突き返し、再考後の --serialization-reviewed で通す
|
|
@@ -273,7 +273,7 @@ const SUBCOMMAND_USAGE = Object.freeze({
|
|
|
273
273
|
'todo revise': 'todo revise --plan <key> --input <file> | --schema --json',
|
|
274
274
|
'todo revise-phase': 'todo revise-phase --plan <key> --input <file> | --schema --json',
|
|
275
275
|
'todo revise-set': 'todo revise-set --input <file> | --schema --json',
|
|
276
|
-
'todo migrate': 'todo migrate --input <extraction.json> [--serialization-reviewed] | --input <extraction.json> --dry-run --json [--serialization-reviewed] | --schema --json',
|
|
276
|
+
'todo migrate': 'todo migrate --input <extraction.json> [--serialization-reviewed] [--json] | --input <extraction.json> --dry-run --json [--serialization-reviewed] | --schema --json',
|
|
277
277
|
'sensor init': 'sensor init [path] --json',
|
|
278
278
|
'sensor sync': 'sensor sync [path] --json',
|
|
279
279
|
'sensor diff': 'sensor diff <rootA> <rootB> [--subtree-a <rel>] [--subtree-b <rel>]'
|
|
@@ -15,7 +15,10 @@ import { TodoStoreError } from './todo-store.mjs';
|
|
|
15
15
|
const REVIEW_SCHEMA = 'lattice.plan_scope_review.v1';
|
|
16
16
|
const RESULT_SCHEMA = 'lattice.plan_scope_review_result.v1';
|
|
17
17
|
const PLAN_CREATE_SCHEMA = 'lattice.plan_create_input.v4';
|
|
18
|
-
const
|
|
18
|
+
const TODO_EXTRACTION_SCHEMAS = new Set([
|
|
19
|
+
'lattice.todo_extraction.v3',
|
|
20
|
+
'lattice.todo_extraction.v4',
|
|
21
|
+
]);
|
|
19
22
|
const MAX_INPUT_BYTES = 8_388_608;
|
|
20
23
|
const JUDGMENTS = new Set(['required', 'out_of_scope']);
|
|
21
24
|
|
|
@@ -73,10 +76,10 @@ async function readJsonWithinRepo(repoRoot, inputRef) {
|
|
|
73
76
|
|
|
74
77
|
function authoredPlan(value) {
|
|
75
78
|
const digestField = value?.schema === PLAN_CREATE_SCHEMA ? 'input_digest'
|
|
76
|
-
: value?.schema
|
|
79
|
+
: TODO_EXTRACTION_SCHEMAS.has(value?.schema) ? 'extraction_digest' : null;
|
|
77
80
|
if (digestField === null) {
|
|
78
81
|
throw new TodoStoreError('PLAN_SCOPE_REVIEW_INVALID', 'authoring_schema_unsupported', undefined, {
|
|
79
|
-
pointer: '/schema', expected: [PLAN_CREATE_SCHEMA,
|
|
82
|
+
pointer: '/schema', expected: [PLAN_CREATE_SCHEMA, ...TODO_EXTRACTION_SCHEMAS],
|
|
80
83
|
actual: typeof value?.schema === 'string' ? value.schema : null,
|
|
81
84
|
});
|
|
82
85
|
}
|
|
@@ -86,7 +89,7 @@ function authoredPlan(value) {
|
|
|
86
89
|
pointer: `/${digestField}`,
|
|
87
90
|
});
|
|
88
91
|
}
|
|
89
|
-
const selected = value.schema
|
|
92
|
+
const selected = TODO_EXTRACTION_SCHEMAS.has(value.schema)
|
|
90
93
|
? value.tasks?.filter((task) => typeof task?.disposition === 'string'
|
|
91
94
|
&& task.disposition.startsWith('register_'))
|
|
92
95
|
: value.tasks;
|
package/src/project-cli.mjs
CHANGED
|
@@ -19,7 +19,10 @@ import { projectTodoStatus } from './todo-status.mjs';
|
|
|
19
19
|
import { readTodoPlanNotesForStatus } from './todo-note-store.mjs';
|
|
20
20
|
import { projectIndependenceFrontier } from './todo-independence.mjs';
|
|
21
21
|
import { readTodoParallelCandidatesForStatus } from './todo-parallel-candidates.mjs';
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
readTodoStructureArtifactDiagnostics,
|
|
24
|
+
readTodoStructureFinalizationsForStatus,
|
|
25
|
+
} from './todo-structure-store.mjs';
|
|
23
26
|
import { isTodoIndependenceLegacyMarker } from './todo-independence-contracts.mjs';
|
|
24
27
|
import { selectIndependenceGuidance } from './todo-independence-guidance.mjs';
|
|
25
28
|
import { ensureTodoDashboardActivity } from './todo-dashboard-registry.mjs';
|
|
@@ -223,7 +226,7 @@ function invalidStatus({ cliVersion, repoRoot, reason, nextAction = null }) {
|
|
|
223
226
|
* store読みはここでしか行わない——session開始経路が同じstoreを二度払っていたのが
|
|
224
227
|
* ADR 0131で直した欠陥である。
|
|
225
228
|
*/
|
|
226
|
-
async function resolveProjectState({ cwd, cliVersion }) {
|
|
229
|
+
async function resolveProjectState({ cwd, cliVersion, diagnoseStructureArtifacts = false }) {
|
|
227
230
|
const repoRoot = resolveRepoRoot(cwd);
|
|
228
231
|
if (repoRoot === null) {
|
|
229
232
|
return { exitCode: 1, repoRoot: null, store: null, todo: null,
|
|
@@ -271,6 +274,14 @@ async function resolveProjectState({ cwd, cliVersion }) {
|
|
|
271
274
|
}
|
|
272
275
|
try {
|
|
273
276
|
const store = await readTodoStore({ repoRoot });
|
|
277
|
+
if (diagnoseStructureArtifacts) {
|
|
278
|
+
const diagnostics = await readTodoStructureArtifactDiagnostics({ repoRoot, store });
|
|
279
|
+
if (diagnostics.length > 0) {
|
|
280
|
+
throw new TodoStoreError(
|
|
281
|
+
'STRUCTURE_ARTIFACT_INVALID', diagnostics[0].reason, undefined, { diagnostics },
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
274
285
|
const todo = projectTodoStatus(store, {
|
|
275
286
|
planNotes: await readTodoPlanNotesForStatus({ repoRoot, store }),
|
|
276
287
|
parallelCandidates: await readTodoParallelCandidatesForStatus({ repoRoot, store, gitHead }),
|
|
@@ -314,6 +325,7 @@ async function resolveProjectState({ cwd, cliVersion }) {
|
|
|
314
325
|
});
|
|
315
326
|
return { exitCode: 0, repoRoot, store, todo, result };
|
|
316
327
|
} catch (error) {
|
|
328
|
+
if (error?.code === 'STRUCTURE_ARTIFACT_INVALID') throw error;
|
|
317
329
|
const reason = error instanceof TodoStoreError
|
|
318
330
|
? `${error.code}:${error.detail?.reason ?? error.message}` : 'store_validation_failed';
|
|
319
331
|
return invalid(reason);
|
|
@@ -322,7 +334,9 @@ async function resolveProjectState({ cwd, cliVersion }) {
|
|
|
322
334
|
|
|
323
335
|
export async function runProjectStatus({ cwd, stdout, cliVersion, env = process.env,
|
|
324
336
|
ensureDashboardActivity = ensureTodoDashboardActivity }) {
|
|
325
|
-
const state = await resolveProjectState({
|
|
337
|
+
const state = await resolveProjectState({
|
|
338
|
+
cwd, cliVersion, diagnoseStructureArtifacts: true,
|
|
339
|
+
});
|
|
326
340
|
// dashboard活動の登録はdiscovery面の副作用として維持する(ADR 0131 Decision 4で
|
|
327
341
|
// session-context側だけが持たない、と決めた面である)。
|
|
328
342
|
if (state.store !== null && env.LATTICE_DASHBOARD_AUTOSTART !== '0') {
|
|
@@ -680,6 +694,15 @@ export async function runPlanShow({ cwd, planKey, stdout }) {
|
|
|
680
694
|
}
|
|
681
695
|
|
|
682
696
|
export function projectStatusFailure({ cwd, stdout, cliVersion, error }) {
|
|
697
|
+
if (error?.code === 'STRUCTURE_ARTIFACT_INVALID') {
|
|
698
|
+
const payload = {
|
|
699
|
+
schema: 'lattice.cli_error.v2', code: error.code,
|
|
700
|
+
message: error.message ?? 'structure artifact invalid',
|
|
701
|
+
detail: error.detail,
|
|
702
|
+
};
|
|
703
|
+
stdout.write(`${JSON.stringify(payload)}\n`);
|
|
704
|
+
return 1;
|
|
705
|
+
}
|
|
683
706
|
const projectRootConflict = error?.code === 'PROJECT_ROOT_CONFLICT';
|
|
684
707
|
const result = invalidStatus({
|
|
685
708
|
cliVersion, repoRoot: resolveRepoRoot(cwd),
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
BOUNDARY_MANIFEST_SCHEMA, selfDigest, validateRuntimeBoundaryManifest, validateRuntimePlan,
|
|
19
19
|
} from './runtime-contracts.mjs';
|
|
20
20
|
import { TODO_INDEPENDENCE_SCHEMA } from './todo-independence-contracts.mjs';
|
|
21
|
+
import { pullIntakeReadinessGuidance } from './todo-independence-guidance.mjs';
|
|
21
22
|
import {
|
|
22
23
|
readTodoIndependenceArtifact, readTodoStore, readTodoWitnessSet,
|
|
23
24
|
} from './todo-store.mjs';
|
|
@@ -315,6 +316,29 @@ function activeMember(store, planKey) {
|
|
|
315
316
|
return member;
|
|
316
317
|
}
|
|
317
318
|
|
|
319
|
+
async function pullPlanReadiness({ repoRoot, planKey }) {
|
|
320
|
+
const store = await readTodoStore({ repoRoot });
|
|
321
|
+
const member = activeMember(store, planKey);
|
|
322
|
+
const [artifact, witnessSet] = await Promise.all([
|
|
323
|
+
readTodoIndependenceArtifact({ repoRoot, store, planKey }),
|
|
324
|
+
readTodoWitnessSet({ repoRoot, planKey }),
|
|
325
|
+
]);
|
|
326
|
+
const ready = artifact !== null
|
|
327
|
+
&& artifact.schema === TODO_INDEPENDENCE_SCHEMA
|
|
328
|
+
&& artifact.project_id === store.project_id
|
|
329
|
+
&& artifact.plan_key === planKey
|
|
330
|
+
&& artifact.plan_version === member.plan.plan_version
|
|
331
|
+
&& artifact.topology_digest === member.plan.topology_digest
|
|
332
|
+
&& artifact.outcome === 'compiled'
|
|
333
|
+
&& witnessSet?.project_id === store.project_id
|
|
334
|
+
&& witnessSet?.plan_key === planKey
|
|
335
|
+
&& witnessSet?.witness_set_digest === artifact.witness_set_digest;
|
|
336
|
+
return pullIntakeReadinessGuidance({
|
|
337
|
+
coordinationMode: member.coordination?.mode ?? null,
|
|
338
|
+
ready,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
318
342
|
function literalEvent(member, { kind, taskId, actor }) {
|
|
319
343
|
return member.journal?.events?.findLast((event) => (
|
|
320
344
|
event.kind === kind && event.task_id === taskId
|
|
@@ -354,6 +378,10 @@ function pathTouches(boundary, changed) {
|
|
|
354
378
|
}
|
|
355
379
|
|
|
356
380
|
async function boundaryVerdict({ repoRoot, store, member, meta, taskId, intakeBase }) {
|
|
381
|
+
const pullRequired = pullIntakeReadinessGuidance({
|
|
382
|
+
coordinationMode: member.coordination?.mode ?? null,
|
|
383
|
+
ready: false,
|
|
384
|
+
});
|
|
357
385
|
let artifact;
|
|
358
386
|
let witnessSet;
|
|
359
387
|
try {
|
|
@@ -362,7 +390,7 @@ async function boundaryVerdict({ repoRoot, store, member, meta, taskId, intakeBa
|
|
|
362
390
|
readTodoWitnessSet({ repoRoot, planKey: meta.plan_key }),
|
|
363
391
|
]);
|
|
364
392
|
} catch (error) {
|
|
365
|
-
return { state: 'hold', reason: 'boundary_unverified', next_action:
|
|
393
|
+
return { state: 'hold', reason: 'boundary_unverified', next_action: pullRequired.next_action,
|
|
366
394
|
detail: { cause: error?.code ?? 'artifact_read_failed' }, artifact: null, witnessSet: null };
|
|
367
395
|
}
|
|
368
396
|
const bindingValid = artifact !== null
|
|
@@ -375,11 +403,11 @@ async function boundaryVerdict({ repoRoot, store, member, meta, taskId, intakeBa
|
|
|
375
403
|
&& witnessSet?.plan_key === meta.plan_key
|
|
376
404
|
&& witnessSet?.witness_set_digest === artifact.witness_set_digest;
|
|
377
405
|
if (!bindingValid) {
|
|
378
|
-
return { state: 'hold', reason: 'boundary_unverified', next_action:
|
|
406
|
+
return { state: 'hold', reason: 'boundary_unverified', next_action: pullRequired.next_action,
|
|
379
407
|
detail: { cause: 'artifact_binding_mismatch' }, artifact, witnessSet };
|
|
380
408
|
}
|
|
381
409
|
if (artifact.outcome !== 'compiled') {
|
|
382
|
-
return { state: 'hold', reason: 'boundary_unverified', next_action:
|
|
410
|
+
return { state: 'hold', reason: 'boundary_unverified', next_action: pullRequired.next_action,
|
|
383
411
|
detail: { cause: 'independence_outcome_unknown' }, artifact, witnessSet };
|
|
384
412
|
}
|
|
385
413
|
const boundary = boundaryFor(artifact, taskId);
|
|
@@ -603,6 +631,7 @@ export async function startPullRun({ repoRoot, runDir, runId, planKey, equipment
|
|
|
603
631
|
|| !identifier(runId) || !identifier(planKey) || equipment !== 'detached-worktree') {
|
|
604
632
|
fail('INVALID_PULL_START', 'pull start入力が不正');
|
|
605
633
|
}
|
|
634
|
+
const intakeReadiness = await pullPlanReadiness({ repoRoot, planKey });
|
|
606
635
|
const temporary = await mkdtemp(path.join(path.dirname(runDir), `.${runId}.pull-tmp-`));
|
|
607
636
|
const meta = {
|
|
608
637
|
schema: PULL_RUN_META_SCHEMA,
|
|
@@ -634,9 +663,10 @@ export async function startPullRun({ repoRoot, runDir, runId, planKey, equipment
|
|
|
634
663
|
throw error;
|
|
635
664
|
}
|
|
636
665
|
return {
|
|
637
|
-
schema: 'lattice.pull_run_start_result.
|
|
666
|
+
schema: 'lattice.pull_run_start_result.v2',
|
|
638
667
|
outcome: 'started', run_id: runId, plan_key: planKey,
|
|
639
668
|
selection: 'pull', equipment, intake_count: 0,
|
|
669
|
+
intake_readiness: intakeReadiness,
|
|
640
670
|
};
|
|
641
671
|
}
|
|
642
672
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
|
|
3
3
|
export const SQLITE_EXPERIMENTAL_WARNING_FLAG = '--disable-warning=ExperimentalWarning';
|
|
4
|
-
const RELAUNCH_GUARD_ENV = '
|
|
4
|
+
const RELAUNCH_GUARD_ENV = 'LATTICE_SQLITE_WARNING_RELAUNCHED';
|
|
5
5
|
|
|
6
6
|
export function sensorNodeRuntimeFlags(nodeVersion = process.versions.node) {
|
|
7
7
|
const major = Number(nodeVersion.split('.')[0]);
|
|
8
8
|
return major === 22 ? [SQLITE_EXPERIMENTAL_WARNING_FLAG] : [];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function
|
|
11
|
+
export function node22RelaunchArgv(
|
|
12
12
|
scriptPath,
|
|
13
13
|
args,
|
|
14
14
|
execArgv = process.execArgv,
|
|
@@ -20,20 +20,18 @@ export function sensorRelaunchArgv(
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Node 22 prints node:sqlite's ExperimentalWarning to stderr.
|
|
24
|
-
*
|
|
25
|
-
* surface with Node's warning-category flag before node:sqlite is imported.
|
|
23
|
+
* Node 22 prints node:sqlite's ExperimentalWarning to stderr. The CLI has a
|
|
24
|
+
* JSON-only output contract, so relaunch before any command can import node:sqlite.
|
|
26
25
|
*/
|
|
27
|
-
export function
|
|
26
|
+
export function relaunchForNode22IfNeeded({ args, scriptPath, env = process.env }) {
|
|
28
27
|
const flags = sensorNodeRuntimeFlags();
|
|
29
|
-
if (
|
|
30
|
-
|| flags.length === 0
|
|
28
|
+
if (flags.length === 0
|
|
31
29
|
|| flags.every((flag) => process.execArgv.includes(flag))
|
|
32
30
|
|| env[RELAUNCH_GUARD_ENV] === '1') return null;
|
|
33
31
|
|
|
34
32
|
const result = spawnSync(
|
|
35
33
|
process.execPath,
|
|
36
|
-
|
|
34
|
+
node22RelaunchArgv(scriptPath, args),
|
|
37
35
|
{
|
|
38
36
|
cwd: process.cwd(),
|
|
39
37
|
env: { ...env, [RELAUNCH_GUARD_ENV]: '1' },
|