@quolu/lattice 0.67.4 → 0.67.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.67.4",
3
+ "version": "0.67.5",
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
@@ -110,6 +110,7 @@ Write commands:
110
110
  reopen --plan <key> --task <id> --reason <text> [--override-reason <text>]
111
111
  evidence promote --plan <key> --task <id> --evidence <file>
112
112
  # done状態と完了時刻を維持し、最新doneへ追記eventで証拠を再束縛する
113
+ start --plan <key> --task <id> --rebind --reason <text> # 改訂でcarryされたin-progressへ現版のstart束縛を積み直す(ADR 0191)
113
114
  dependency connect --from-plan <key> --from-task <id> --to-plan <key> --to-task <id> --reason <text>
114
115
  dependency disconnect --from-plan <key> --from-task <id> --to-plan <key> --to-task <id> --reason <text>
115
116
  # 開発中に発見した依存を明示接続する(同一plan内・plan跨ぎとも)。依存の自動推定は行わない
package/src/todo-cli.mjs CHANGED
@@ -901,6 +901,47 @@ async function startTask({
901
901
  structureContext });
902
902
  }
903
903
 
904
+ // plan改訂でcarryされたin-progress ToDoの再束縛入口(ADR 0191)。task状態・開始時刻は
905
+ // 変えず、現plan_version・現actorのstart束縛だけをjournalへ積む。runtime intakeは
906
+ // literal startだけを束縛に使いcarryを推定しないため、改訂を跨いだ進行中工程の
907
+ // 再開はこの入口だけが正規経路になる。advisory・independence gateは初回startで
908
+ // 済んでいるため再適用しない。
909
+ async function rebindStart({ repoRoot, env, planKey, taskId, reason }) {
910
+ const store = await readTodoStore({ repoRoot });
911
+ const member = store.members.find(({ descriptor }) => descriptor.plan_key === planKey);
912
+ if (member === undefined) throw new TodoStoreError('STORE_INCONSISTENT', 'plan_not_active');
913
+ const task = member.tasks.find(({ task_id: candidate }) => candidate === taskId);
914
+ if (task === undefined) {
915
+ throw new TodoStoreError('TASK_NOT_FOUND', 'task_not_found', undefined, {
916
+ requested_task_id: taskId,
917
+ });
918
+ }
919
+ if (task.status !== 'in-progress') {
920
+ throw new TodoStoreError('TASK_START_BINDING_UNSUPPORTED', 'rebind_requires_in_progress', undefined, {
921
+ task_id: taskId, status: task.status,
922
+ });
923
+ }
924
+ let noteContext;
925
+ try {
926
+ ({ context: noteContext } = await readTodoNoteContext({ repoRoot, store, planKey, taskId }));
927
+ } catch (error) {
928
+ if (!['NOTE_LOG_CORRUPT', 'NOTE_PROJECTION_INVALID'].includes(error?.code)) throw error;
929
+ noteContext = {
930
+ schema: 'lattice.todo_note_context.unreadable.v1',
931
+ project_id: member.plan.project_id,
932
+ plan_key: planKey,
933
+ task_id: taskId,
934
+ code: error.code,
935
+ reason: error.detail?.reason ?? 'note_context_unreadable',
936
+ next_action: `lattice todo note list --plan ${planKey} --json`,
937
+ };
938
+ }
939
+ return mutate({
940
+ repoRoot, env, planKey, taskId, kind: 'start',
941
+ payload: { start_mode: 'rebind', reason }, evidenceRef: null, noteContext,
942
+ });
943
+ }
944
+
904
945
  async function retractStart({ repoRoot, env, planKey, taskId, reason }) {
905
946
  const actor = mutationActor(env);
906
947
  const store = await readTodoStore({ repoRoot });
@@ -3970,11 +4011,18 @@ export async function runTodoCli({ argv, cwd, stdout, stderr, env = process.env
3970
4011
  action = (repoRoot) => phaseBaseline({ repoRoot, env, reason: argv[3], exceptPlanKeys });
3971
4012
  } else if (argv[0] === 'start') {
3972
4013
  const flags = matchFlagCommand(argv, ['start'], {
3973
- known: ['plan', 'task', 'parallel-frontier', 'override-reason', 'serial-confirmed'],
4014
+ known: ['plan', 'task', 'parallel-frontier', 'override-reason', 'serial-confirmed', 'rebind', 'reason'],
3974
4015
  required: ['plan', 'task'],
3975
- booleans: ['parallel-frontier', 'serial-confirmed'],
4016
+ booleans: ['parallel-frontier', 'serial-confirmed', 'rebind'],
3976
4017
  });
3977
- if (flags !== null && isTodoIdentifier(flags.plan) && isTodoIdentifier(flags.task)
4018
+ if (flags !== null && flags.rebind === true) {
4019
+ if (isTodoIdentifier(flags.plan) && isTodoIdentifier(flags.task)
4020
+ && typeof flags.reason === 'string' && flags.reason.length > 0) {
4021
+ action = (repoRoot) => rebindStart({
4022
+ repoRoot, env, planKey: flags.plan, taskId: flags.task, reason: flags.reason,
4023
+ });
4024
+ }
4025
+ } else if (flags !== null && isTodoIdentifier(flags.plan) && isTodoIdentifier(flags.task)
3978
4026
  && (flags['override-reason'] === undefined || (typeof flags['override-reason'] === 'string'
3979
4027
  && flags['override-reason'].length > 0))) {
3980
4028
  action = (repoRoot) => startTask({
@@ -532,6 +532,12 @@ function validPayload(event) {
532
532
  && isTodoDigest(payload.target_event_digest)
533
533
  && nullableText(payload.reason) && payload.reason !== null;
534
534
  }
535
+ // rebind: plan改訂でcarryされたin-progress ToDoへ、現plan_versionの明示的start束縛を積み直す
536
+ // (runtime intakeはliteral startだけを束縛に使い、carryを推定しない。ADR 0191)
537
+ if (event.kind === 'start' && payload?.start_mode === 'rebind') {
538
+ return exactRecord(payload, ['start_mode', 'reason'])
539
+ && nullableText(payload.reason) && payload.reason !== null;
540
+ }
535
541
  if (event.kind === 'start') return exactRecord(payload, ['override_reason']) && nullableText(payload.override_reason);
536
542
  if (event.kind === 'start_retracted') return exactRecord(payload, ['reason', 'target_start_digest'])
537
543
  && nullableText(payload.reason) && payload.reason !== null
@@ -678,6 +678,17 @@ function replay(plan, events, { now = new Date(), verifyEvidence, verifyImportSo
678
678
  if (state === undefined) fail('STORE_INCONSISTENT', 'event_task_missing');
679
679
  const dependenciesDone = localPredecessors(plan, event.task_id).every((id) => states.get(id)?.status === 'done');
680
680
  if (event.kind === 'start') {
681
+ if (event.payload.start_mode === 'rebind') {
682
+ // 改訂でcarryされたin-progressの再束縛。状態・開始時刻は動かさず、start束縛だけを
683
+ // 現actor・現plan_versionのeventへ更新する(ADR 0191)
684
+ if (state.status !== 'in-progress') {
685
+ fail('STORE_INCONSISTENT', 'invalid_rebind_start_transition');
686
+ }
687
+ authoredStartBindings.set(event.task_id, {
688
+ actor: structuredClone(event.actor), event_digest: event.event_digest,
689
+ });
690
+ continue;
691
+ }
681
692
  if (event.payload.start_mode === 'historical_import') {
682
693
  if (!importedGenesis || state.status !== 'pending') {
683
694
  fail('STORE_INCONSISTENT', 'invalid_historical_import_start_transition');
@@ -1125,6 +1136,7 @@ function validateMergedTransition(store, member, event) {
1125
1136
  const phaseAcceptReady = mergedPhaseAcceptReady(store, targetKey);
1126
1137
  const dependenciesDone = taskDependenciesDone && phaseAcceptReady;
1127
1138
  if (event.kind === 'start' && event.payload.start_mode !== 'historical_import'
1139
+ && event.payload.start_mode !== 'rebind'
1128
1140
  && (!phaseAcceptReady || (!taskDependenciesDone && event.payload.override_reason === null))) {
1129
1141
  fail('STORE_INCONSISTENT', 'invalid_start_transition');
1130
1142
  }