@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
@@ -203,15 +203,20 @@ export function synthesizeWitnessRunRequest(witnessSet, { baseSha, requestId })
203
203
  /**
204
204
  * 1 taskのconcern anchor宣言を検査する。
205
205
  *
206
- * `within`はそのtask自身が`owns`で主張している資源に限る。所有していない資源の内側に
207
- * 担当を主張させない。symbol名の実在・資源内包含・task間排他はsensorとcompile側が見る。
206
+ * `within`は`owns`または`writes`が指す資源に限る。触ると宣言した面の内側に担当を書ける。
207
+ * 所有も書き込みも無い資源の内側には主張させない。
208
208
  */
209
- function explainConcernAnchors(anchors, owns, at) {
209
+ function explainConcernAnchors(anchors, owns, writes, at) {
210
210
  const reject = (reason, path) => ({ valid: false, reason, path });
211
211
  if (!Array.isArray(anchors) || anchors.length > TODO_CONCERN_ANCHOR_LIMIT) {
212
212
  return reject('bounded_collection_violation', at);
213
213
  }
214
214
  const ownedKeys = new Set(owns.map((own) => `${own.kind}\0${own.target}`));
215
+ if (Array.isArray(writes)) {
216
+ for (const write of writes) {
217
+ if (typeof write === 'string') ownedKeys.add(`path\0${write}`);
218
+ }
219
+ }
215
220
  for (const [index, entry] of anchors.entries()) {
216
221
  const entryAt = `${at}/${index}`;
217
222
  if (!exactRecord(entry, ['within', 'symbols'])) {
@@ -297,7 +302,9 @@ export function explainTodoWitnessSet(value) {
297
302
  if (!CONCERN_ANCHOR_SCHEMAS.includes(value.schema)) {
298
303
  return reject('concern_anchors_require_witness_set_v2', at);
299
304
  }
300
- const anchors = explainConcernAnchors(witness.concern_anchors, witness.owns, at);
305
+ const anchors = explainConcernAnchors(
306
+ witness.concern_anchors, witness.owns, witness.writes, at,
307
+ );
301
308
  if (!anchors.valid) return reject(anchors.reason, anchors.path);
302
309
  }
303
310
  return { valid: true };
@@ -304,8 +304,8 @@ export function selectSeamProposalGuidance({ coverage, unknownKinds = [] }) {
304
304
  */
305
305
  export const TODO_INDEPENDENCE_WORKFLOW = Object.freeze([
306
306
  '1. 宣言する: .lattice/todo/witness/<plan_key>.json へ、ToDoごとのowns/reads/writes/affected_testsを書く',
307
- ' 係争資源しか所有していないToDoは、その資源の中で自分が触るsymbolをconcern_anchorsへ宣言できる(witness set v2)。並列可否の判定には写らず、切断候補の束縛だけに効く',
308
- '2. 判定する: lattice todo independence compile --plan <key> --input <ref>(実sensorを引き、clean worktreeが要る)',
307
+ ' 係争資源しか所有していないToDoは、ownsまたはwritesの内側で自分が触るsymbolをconcern_anchorsへ宣言できる(witness set v2)。並列可否の判定には写らず、切断候補の束縛だけに効く',
308
+ '2. 判定する: lattice todo independence compile --plan <key> --input <ref>(実sensorを引く。pretty-printやdigest未計算の下書きは機械が直す)',
309
309
  '3. 読む: lattice todo independence --plan <key> --json(sensorを引かず、記録とHEAD照合だけで返る)',
310
310
  '4. 追従する: plan改訂後は lattice todo independence witness migrate --plan <key> で宣言を写してから再compileする',
311
311
  ]);
@@ -319,8 +319,8 @@ export const TODO_INDEPENDENCE_WORKFLOW = Object.freeze([
319
319
  */
320
320
  const SCAFFOLD_CATALOG = Object.freeze({
321
321
  anchor_outside_owned: {
322
- message: 'concern_anchorのwithinが、自分の所有pathを指していない。所有していない資源の内側に担当を主張できない。',
323
- next_action: 'align_anchor_with_owns',
322
+ message: 'concern_anchorのwithinが、ownsにもwritesにも無い。触ると宣言した面の内側にだけ担当を書ける。',
323
+ next_action: 'align_anchor_with_owns_or_writes',
324
324
  },
325
325
  draft_invalid: {
326
326
  message: '下書きがlattice.todo_witness_draft契約を満たしていない。',
@@ -1040,7 +1040,11 @@ function validateMergedTransition(store, member, event) {
1040
1040
  if (event.kind === 'reopen' && event.payload.override_reason === null) {
1041
1041
  const startedSuccessor = mergedSuccessorKeys(store, targetKey)
1042
1042
  .some((key) => states.get(key) !== 'pending');
1043
- if (startedSuccessor) fail('STORE_INCONSISTENT', 'reopen_has_started_successor');
1043
+ if (startedSuccessor) {
1044
+ fail('STORE_INCONSISTENT', 'reopen_has_started_successor', {
1045
+ next_action: `lattice todo split --plan ${member.plan.plan_key} --input <file>`,
1046
+ });
1047
+ }
1044
1048
  }
1045
1049
  if (event.kind === 'phase_reopen' && event.payload.override_reason === null) {
1046
1050
  const startedSuccessor = member.plan.schema === 'lattice.todo_plan.v4'
@@ -1838,6 +1842,7 @@ function resolveCanonicalTaskId(plan, requestedTaskId) {
1838
1842
  }
1839
1843
 
1840
1844
  async function enforceTodoStructureLifecycleGate(repoRoot, member, eventInput) {
1845
+ if (eventInput.kind === 'done') return;
1841
1846
  const binding = await readTodoStructureBinding({
1842
1847
  repoRoot, planKey: member.plan.plan_key, planVersion: member.plan.plan_version,
1843
1848
  });
@@ -1846,35 +1851,6 @@ async function enforceTodoStructureLifecycleGate(repoRoot, member, eventInput) {
1846
1851
  if (source === null || source.structure_set_digest !== binding.structure_set_digest) {
1847
1852
  fail('STRUCTURE_LIFECYCLE_GATE_FAILED', 'enabled_structure_source_unreadable');
1848
1853
  }
1849
- if (eventInput.kind === 'done') {
1850
- const task = source.tasks.find(({ task_id: id }) => id === eventInput.task_id);
1851
- if (task?.applicability !== 'graph') return;
1852
- const chain = await readTodoStructureRealizationChain({
1853
- repoRoot, structureSet: source, taskId: eventInput.task_id,
1854
- });
1855
- const latest = chain.at(-1);
1856
- if (latest === undefined) {
1857
- fail('STRUCTURE_REALIZATION_REQUIRED', 'fresh_realization_missing', {
1858
- plan_key: member.plan.plan_key, task_id: eventInput.task_id,
1859
- next_action: `lattice todo structure realize --plan ${member.plan.plan_key} --task ${eventInput.task_id} --realized <actual-structure.json>`,
1860
- });
1861
- }
1862
- let currentHead;
1863
- try {
1864
- currentHead = gitSync(['rev-parse', '--verify', 'HEAD^{commit}'], {
1865
- cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
1866
- }).trim();
1867
- } catch {
1868
- fail('STRUCTURE_GIT_HEAD_UNAVAILABLE', 'current_head_unavailable');
1869
- }
1870
- if (latest.head_sha !== currentHead) {
1871
- fail('STRUCTURE_REALIZATION_REQUIRED', 'realization_head_stale', {
1872
- plan_key: member.plan.plan_key, task_id: eventInput.task_id,
1873
- realization_head_sha: latest.head_sha, current_head_sha: currentHead,
1874
- next_action: `lattice todo structure realize --plan ${member.plan.plan_key} --task ${eventInput.task_id} --realized <actual-structure.json>`,
1875
- });
1876
- }
1877
- }
1878
1854
  if (!['phase_accept', 'phase_close_unaudited'].includes(eventInput.kind)
1879
1855
  || member.tasks.some(({ status }) => status !== 'done')) return;
1880
1856
  const finalization = await readTodoStructureFinalization({
@@ -65,6 +65,7 @@ export function validateWitnessDraft(value) {
65
65
  && task !== null && typeof task === 'object' && !Array.isArray(task)
66
66
  && draftOwnEntries(task, value.schema) !== null
67
67
  && (task.reads === undefined || (Array.isArray(task.reads) && task.reads.every(isTodoRef)))
68
+ && (task.writes === undefined || (Array.isArray(task.writes) && task.writes.every(isTodoRef)))
68
69
  && (task.unknowns === undefined || (Array.isArray(task.unknowns)
69
70
  && task.unknowns.every((entry) => entry !== null && typeof entry === 'object'
70
71
  && isTodoIdentifier(entry.kind) && typeof entry.ref === 'string' && entry.ref.length > 0)))
@@ -113,20 +114,26 @@ export function buildWitnessSet({ draft, observationByPath } = {}) {
113
114
  const entries = draftOwnEntries(task, draft.schema) ?? [];
114
115
  const owns = [...new Map(entries.map((entry) => [entry.target, entry])).values()]
115
116
  .sort((left, right) => compareText(left.target, right.target));
116
- const ownedTargets = new Set(owns.map(({ target }) => target));
117
+ const writeTargets = sortedUnique([
118
+ ...owns.map(({ target }) => target),
119
+ ...(Array.isArray(task.writes) ? task.writes : []),
120
+ ]);
121
+ const declaredTargets = new Set(writeTargets);
117
122
  const affected = sortedUnique(owns.flatMap(({ target }) => (
118
123
  observationByPath?.[target]?.affectedTests ?? []
119
124
  )));
120
125
  for (const anchor of task.concern_anchors ?? []) {
121
- // `within`は自分が所有している資源に限る。所有していない資源の内側に担当を主張させない。
122
- if (!ownedTargets.has(anchor.within)) reasons.push(`anchor_outside_owned:${taskId}:${anchor.within}`);
126
+ // `within`はownsまたはwritesが指す資源に限る。
127
+ if (!declaredTargets.has(anchor.within)) {
128
+ reasons.push(`anchor_outside_owned:${taskId}:${anchor.within}`);
129
+ }
123
130
  }
124
131
  manualWitness[taskId] = {
125
132
  owns: owns.map(({ target, creates }) => (
126
133
  creates ? { kind: 'path', target, creates: true } : { kind: 'path', target }
127
134
  )),
128
135
  reads: sortedUnique(task.reads ?? []),
129
- writes: owns.map(({ target }) => target),
136
+ writes: writeTargets,
130
137
  resources: [],
131
138
  state_effects: [],
132
139
  sensor_provenance: {