@quolu/lattice 0.61.2 → 0.62.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.
Files changed (66) hide show
  1. package/LICENSE +147 -147
  2. package/README.ja.md +374 -374
  3. package/README.md +300 -300
  4. package/bin/lattice-mcp.mjs +0 -0
  5. package/bin/lattice-scripted-adapter.mjs +0 -0
  6. package/bin/lattice-scripted-worker.mjs +0 -0
  7. package/bin/lattice-work-order-adapter.mjs +0 -0
  8. package/bin/lattice.mjs +20 -9
  9. package/docs/bridge-setup.md +248 -248
  10. package/docs/schemas/lattice.executor_packet.v1.schema.json +57 -57
  11. package/docs/schemas/lattice.executor_receipt.v1.schema.json +66 -66
  12. package/docs/schemas/lattice.phase_todo_revision.v3.schema.json +360 -360
  13. package/docs/schemas/lattice.plan_create_input.v1.schema.json +56 -56
  14. package/docs/schemas/lattice.plan_create_input.v2.schema.json +72 -72
  15. package/docs/schemas/lattice.plan_create_input.v3.schema.json +81 -81
  16. package/docs/schemas/lattice.plan_create_input.v4.schema.json +85 -85
  17. package/docs/schemas/lattice.plan_scope_review.v1.schema.json +55 -55
  18. package/docs/schemas/lattice.run_request.v1.schema.json +238 -238
  19. package/docs/schemas/lattice.runtime_adapter_capabilities.v2.schema.json +55 -55
  20. package/docs/schemas/lattice.runtime_adapter_registration_input.v1.schema.json +78 -78
  21. package/docs/schemas/lattice.runtime_adapter_registration_input.v2.schema.json +86 -86
  22. package/docs/schemas/lattice.todo_extraction.v2.schema.json +298 -298
  23. package/docs/schemas/lattice.todo_extraction.v3.schema.json +150 -150
  24. package/docs/schemas/lattice.todo_extraction.v4.schema.json +161 -161
  25. package/docs/schemas/lattice.todo_revision.v2.schema.json +260 -260
  26. package/docs/schemas/lattice.todo_revision_set.v3.schema.json +363 -363
  27. package/docs/schemas/lattice.todo_structure_binding.v1.schema.json +47 -47
  28. package/docs/schemas/lattice.todo_structure_realization.v1.schema.json +55 -55
  29. package/docs/schemas/lattice.todo_structure_set.v1.schema.json +264 -264
  30. package/package.json +109 -109
  31. package/sensor/LICENSE +21 -21
  32. package/sensor/NOTICE +19 -19
  33. package/sensor/dist/bin/lattice-sensor.js +9 -9
  34. package/sensor/dist/db/index.js +24 -24
  35. package/sensor/dist/db/migrations.js +41 -41
  36. package/sensor/dist/db/queries.js +164 -164
  37. package/sensor/dist/db/schema.sql +205 -205
  38. package/sensor/dist/directory.js +5 -5
  39. package/sensor/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  40. package/sensor/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  41. package/sensor/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  42. package/sensor/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  43. package/sensor/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  44. package/sensor/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  45. package/sensor/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
  46. package/sensor/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
  47. package/sensor/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
  48. package/sensor/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  49. package/sensor/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  50. package/sensor/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
  51. package/sensor/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
  52. package/sensor/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
  53. package/sensor/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  54. package/sensor/dist/mcp/liveness-watchdog.js +53 -53
  55. package/sensor/dist/mcp/server-instructions.js +95 -95
  56. package/sensor/package.json +56 -56
  57. package/src/cli-help.mjs +9 -5
  58. package/src/project-cli.mjs +22 -71
  59. package/src/runtime-diff-observer.mjs +34 -2
  60. package/src/runtime-pull-intake.mjs +15 -1
  61. package/src/todo-authoring-input.mjs +183 -0
  62. package/src/todo-cli.mjs +367 -382
  63. package/src/todo-independence-contracts.mjs +11 -4
  64. package/src/todo-independence-guidance.mjs +4 -4
  65. package/src/todo-store.mjs +6 -30
  66. package/src/witness-scaffold.mjs +11 -4
@@ -0,0 +1,183 @@
1
+ /**
2
+ * AIが書くauthoring入力の受理。
3
+ *
4
+ * storeへ書くbytesはこれまでどおりcanonical。入口がpretty-print・digest未計算・
5
+ * repo内絶対pathを拒否するのは、機械が直せるものを儀式にしている。
6
+ * 空の設計メモは直さない——NO_PLANは無策の明示申告であり、欠落から作らない。
7
+ */
8
+
9
+ import { lstat, readFile, realpath } from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import { parseTree } from 'jsonc-parser';
12
+
13
+ import { todoSelfDigest } from './todo-contracts.mjs';
14
+ import { TodoStoreError } from './todo-store.mjs';
15
+
16
+ const MAX_AUTHORING_INPUT_BYTES = 8_388_608;
17
+
18
+ export const AUTHORING_DIGEST_FIELDS = Object.freeze({
19
+ 'lattice.todo_witness_set.v1': 'witness_set_digest',
20
+ 'lattice.todo_witness_set.v2': 'witness_set_digest',
21
+ 'lattice.todo_witness_set.v3': 'witness_set_digest',
22
+ 'lattice.todo_witness_set.v4': 'witness_set_digest',
23
+ 'lattice.todo_witness_set.v5': 'witness_set_digest',
24
+ 'lattice.todo_extraction.v2': 'extraction_digest',
25
+ 'lattice.todo_extraction.v3': 'extraction_digest',
26
+ 'lattice.todo_extraction.v4': 'extraction_digest',
27
+ 'lattice.plan_create_input.v1': 'input_digest',
28
+ 'lattice.plan_create_input.v2': 'input_digest',
29
+ 'lattice.plan_create_input.v3': 'input_digest',
30
+ 'lattice.plan_create_input.v4': 'input_digest',
31
+ 'lattice.todo_revision.v1': 'revision_digest',
32
+ 'lattice.todo_revision.v2': 'revision_digest',
33
+ 'lattice.phase_todo_revision.v1': 'revision_digest',
34
+ 'lattice.phase_todo_revision.v2': 'revision_digest',
35
+ 'lattice.phase_todo_revision.v3': 'revision_digest',
36
+ 'lattice.todo_revision_set.v1': 'revision_set_digest',
37
+ 'lattice.todo_revision_set.v2': 'revision_set_digest',
38
+ 'lattice.todo_revision_set.v3': 'revision_set_digest',
39
+ 'lattice.phase_accept_input.v1': 'input_digest',
40
+ 'lattice.phase_reject_input.v1': 'input_digest',
41
+ });
42
+
43
+ function fail(code, reason, detail) {
44
+ throw new TodoStoreError(code, reason, undefined, detail);
45
+ }
46
+
47
+ function within(root, candidate) {
48
+ return candidate === root || candidate.startsWith(`${root}${path.sep}`);
49
+ }
50
+
51
+ function hasDuplicateJsonKey(node) {
52
+ if (node?.type === 'object') {
53
+ const keys = new Set();
54
+ for (const property of node.children ?? []) {
55
+ const [key, value] = property.children ?? [];
56
+ if (keys.has(key?.value) || hasDuplicateJsonKey(value)) return true;
57
+ keys.add(key?.value);
58
+ }
59
+ } else if (node?.type === 'array') {
60
+ return (node.children ?? []).some(hasDuplicateJsonKey);
61
+ }
62
+ return false;
63
+ }
64
+
65
+ /** CLI引数がauthoring fileを指しているか。repo相対でも絶対でもよい。 */
66
+ export function isAuthoringPathToken(value) {
67
+ return typeof value === 'string' && value.length > 0 && !value.startsWith('--');
68
+ }
69
+
70
+ export function parseAuthoringJson(text, { invalidCode = 'INVALID_JSON' } = {}) {
71
+ if (typeof text !== 'string') fail(invalidCode, 'input_json_invalid');
72
+ const normalized = text.replace(/^\uFEFF/u, '').replace(/\r\n/gu, '\n').replace(/\r/gu, '\n');
73
+ const parseErrors = [];
74
+ const tree = parseTree(normalized, parseErrors, { allowTrailingComma: false, disallowComments: true });
75
+ if (parseErrors.length > 0 || tree === undefined) fail(invalidCode, 'json_parse_failed');
76
+ if (hasDuplicateJsonKey(tree)) fail(invalidCode, 'duplicate_key');
77
+ try {
78
+ return JSON.parse(normalized);
79
+ } catch {
80
+ fail(invalidCode, 'json_parse_failed');
81
+ }
82
+ return null;
83
+ }
84
+
85
+ export function repairAuthoringArtifact(value) {
86
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return value;
87
+ const field = AUTHORING_DIGEST_FIELDS[value.schema];
88
+ if (field === undefined) return value;
89
+ const next = { ...value, [field]: typeof value[field] === 'string' ? value[field] : '' };
90
+ next[field] = todoSelfDigest(next, field);
91
+ return next;
92
+ }
93
+
94
+ export async function resolveAuthoringInputPath(repoRoot, inputRef) {
95
+ if (typeof inputRef !== 'string' || inputRef.length === 0) {
96
+ fail('INPUT_UNREADABLE', 'input_ref_invalid', { input_ref: inputRef });
97
+ }
98
+ const canonicalRoot = await realpath(repoRoot);
99
+ const candidate = path.isAbsolute(inputRef)
100
+ ? inputRef
101
+ : path.resolve(canonicalRoot, inputRef);
102
+ let stats;
103
+ try { stats = await lstat(candidate); } catch {
104
+ fail('INPUT_UNREADABLE', 'input_missing', { input_ref: inputRef });
105
+ }
106
+ if (stats.isSymbolicLink() || !stats.isFile()) {
107
+ fail('INPUT_UNREADABLE', 'unsafe_input_path', { input_ref: inputRef });
108
+ }
109
+ const resolved = await realpath(candidate);
110
+ if (!within(canonicalRoot, resolved) || resolved === canonicalRoot) {
111
+ fail('INPUT_UNREADABLE', 'input_path_outside_repo', { input_ref: inputRef });
112
+ }
113
+ if (stats.size > MAX_AUTHORING_INPUT_BYTES) fail('INPUT_TOO_LARGE', 'input_size_limit_exceeded');
114
+ const relative = path.relative(canonicalRoot, resolved);
115
+ if (relative.startsWith(`..${path.sep}`) || relative === '..' || path.isAbsolute(relative)) {
116
+ fail('INPUT_UNREADABLE', 'input_path_outside_repo', { input_ref: inputRef });
117
+ }
118
+ return {
119
+ absolute: resolved,
120
+ inputRef: relative.split(path.sep).join('/'),
121
+ maxBytes: MAX_AUTHORING_INPUT_BYTES,
122
+ };
123
+ }
124
+
125
+ export async function readAuthoringJsonFile(repoRoot, inputRef, {
126
+ invalidCode = 'INVALID_JSON',
127
+ } = {}) {
128
+ const located = await resolveAuthoringInputPath(repoRoot, inputRef);
129
+ const bytes = await readFile(located.absolute);
130
+ if (bytes.length > located.maxBytes) fail('INPUT_TOO_LARGE', 'input_size_limit_exceeded');
131
+ let text;
132
+ try { text = new TextDecoder('utf-8', { fatal: true }).decode(bytes); }
133
+ catch { fail(invalidCode, 'invalid_utf8'); }
134
+ const parsed = parseAuthoringJson(text, { invalidCode });
135
+ return repairAuthoringArtifact(parsed);
136
+ }
137
+
138
+ export function parseFlagMap(argv) {
139
+ const flags = Object.create(null);
140
+ const rest = [];
141
+ for (let index = 0; index < argv.length; index += 1) {
142
+ const token = argv[index];
143
+ if (typeof token !== 'string' || !token.startsWith('--') || token === '--') {
144
+ rest.push(token);
145
+ continue;
146
+ }
147
+ const name = token.slice(2);
148
+ const next = argv[index + 1];
149
+ if (next !== undefined && typeof next === 'string' && !next.startsWith('--')) {
150
+ flags[name] = next;
151
+ index += 1;
152
+ } else {
153
+ flags[name] = true;
154
+ }
155
+ }
156
+ return { flags, rest };
157
+ }
158
+
159
+ export function flagMapKeys(flags, expected) {
160
+ const actual = Object.keys(flags).sort();
161
+ const allowed = [...expected].sort();
162
+ return actual.length === allowed.length && actual.every((key, index) => key === allowed[index]);
163
+ }
164
+
165
+ /**
166
+ * 位置ではなくflag名でwriteコマンドを束ねる。未知flag・位置引数は受理しない。
167
+ * boolean flagは値がtrueのときだけ合法。requiredは非空string。
168
+ */
169
+ export function matchFlagCommand(argv, head, { known, required = [], booleans = [] } = {}) {
170
+ if (!Array.isArray(argv) || !Array.isArray(head) || head.some((token, index) => argv[index] !== token)) {
171
+ return null;
172
+ }
173
+ const { flags, rest } = parseFlagMap(argv.slice(head.length));
174
+ if (rest.length > 0) return null;
175
+ if (Object.keys(flags).some((key) => !known.includes(key))) return null;
176
+ for (const key of required) {
177
+ if (typeof flags[key] !== 'string' || flags[key].length === 0) return null;
178
+ }
179
+ for (const key of booleans) {
180
+ if (flags[key] !== undefined && flags[key] !== true) return null;
181
+ }
182
+ return flags;
183
+ }