@akagilnc/pi-workflow-roles 0.1.4932 → 0.1.4951

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.
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { createHash } from "node:crypto";
8
8
  import { appendFileSync } from "node:fs";
9
- import { readFile } from "node:fs/promises";
9
+ import { readFile, unlink } from "node:fs/promises";
10
10
  import { basename, dirname, join, resolve } from "node:path";
11
11
 
12
12
  import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
@@ -25,6 +25,7 @@ import {
25
25
  import { isSafePositiveTicketNumber } from "./run-ticket-number.ts";
26
26
  import { adaptSessionDialogue, nativeEventId } from "./session-dialogue.ts";
27
27
  import {
28
+ appendSitianRecordBlock,
28
29
  appendSitianRecord,
29
30
  resolveSitianRecordPath,
30
31
  type SitianRecordInput,
@@ -113,14 +114,16 @@ export function ticketProvenanceSubject(ticketNumber: number): string {
113
114
 
114
115
  /** Topology input only — no destination parameter (ADR 0065 record-entry). */
115
116
  function ticketProvenanceRecordInput(
116
- ticketNumber: number,
117
+ ticketNumber: number | null,
117
118
  cwd: string,
118
119
  home?: string,
120
+ runDirectory?: string,
119
121
  ): SitianRecordInput {
120
122
  return {
121
123
  level: "event",
122
124
  kind: TICKET_PROVENANCE_KIND,
123
- subject: ticketProvenanceSubject(ticketNumber),
125
+ ...(ticketNumber === null ? {} : { subject: ticketProvenanceSubject(ticketNumber) }),
126
+ ...(runDirectory === undefined ? {} : { runDirectory }),
124
127
  cwd,
125
128
  ...(home === undefined ? {} : { home }),
126
129
  };
@@ -215,11 +218,14 @@ export type ReadTicketProvenanceResult = {
215
218
 
216
219
  /** Read the unique diary file (empty/absent → no header, no lines). */
217
220
  export async function readTicketProvenance(
218
- ticketNumber: number,
221
+ ticketNumber: number | null,
219
222
  cwd: string,
220
223
  home?: string,
224
+ runDirectory?: string,
221
225
  ): Promise<ReadTicketProvenanceResult> {
222
- const { recordFile } = resolveTicketProvenanceVolume(ticketNumber, cwd, home);
226
+ const { recordFile } = ticketNumber === null
227
+ ? resolveSitianRecordPath(ticketProvenanceRecordInput(null, cwd, home, runDirectory))
228
+ : resolveTicketProvenanceVolume(ticketNumber, cwd, home);
223
229
  let text: string;
224
230
  try {
225
231
  text = await readFile(recordFile, "utf8");
@@ -320,7 +326,7 @@ export async function readTicketProvenance(
320
326
  repo: header?.repo ?? resolveBookKeyFromGit(cwd),
321
327
  ticket: ticketNumber,
322
328
  createdAt: header?.createdAt ?? now,
323
- updatedAt: now,
329
+ updatedAt: header?.updatedAt !== undefined && header.updatedAt > now ? header.updatedAt : now,
324
330
  sessions: merged.sessions,
325
331
  };
326
332
  const carried = lines.map((entry) => ({
@@ -364,6 +370,25 @@ export function ensureTicketProvenanceVolume(
364
370
  return { recordFile: path.recordFile, volumeDir: path.sessionDir };
365
371
  }
366
372
 
373
+ /** Assign a run-owned unbound diary as its original block through Sitian. */
374
+ export async function rehomeUnboundTicketProvenance(
375
+ runDirectory: string,
376
+ ticketNumber: number,
377
+ cwd: string,
378
+ home: string,
379
+ ): Promise<void> {
380
+ const source = join(runDirectory, "records.jsonl");
381
+ let content: string;
382
+ try {
383
+ content = await readFile(source, "utf8");
384
+ } catch (error) {
385
+ if (errnoCode(error) === "ENOENT") return;
386
+ throw error;
387
+ }
388
+ appendSitianRecordBlock(ticketProvenanceRecordInput(ticketNumber, cwd, home), content);
389
+ await unlink(source);
390
+ }
391
+
367
392
  export type ReprojectTicketProvenanceResult = {
368
393
  readonly recordFile: string;
369
394
  readonly header: TicketProvenanceHeader;
@@ -713,12 +738,13 @@ async function projectSessionRanges(input: {
713
738
  * Persistence appends one immutable commit through the Sitian appender seam.
714
739
  */
715
740
  export async function reprojectTicketProvenance(input: {
716
- readonly ticketNumber: number;
741
+ readonly ticketNumber: number | null;
717
742
  readonly cwd: string;
718
743
  readonly home?: string;
744
+ readonly runDirectory?: string;
719
745
  readonly sessions: readonly TicketProvenanceSession[];
720
746
  }): Promise<ReprojectTicketProvenanceResult> {
721
- const prior = await readTicketProvenance(input.ticketNumber, input.cwd, input.home);
747
+ const prior = await readTicketProvenance(input.ticketNumber, input.cwd, input.home, input.runDirectory);
722
748
  if (input.sessions.length === 0) {
723
749
  const now = new Date().toISOString();
724
750
  return {
@@ -802,7 +828,7 @@ export async function reprojectTicketProvenance(input: {
802
828
  });
803
829
  const identity = `ticket-provenance:${createHash("sha256").update(identityMaterial).digest("hex")}`;
804
830
  const pointer = appendSitianRecord({
805
- ...ticketProvenanceRecordInput(input.ticketNumber, input.cwd, input.home),
831
+ ...ticketProvenanceRecordInput(input.ticketNumber, input.cwd, input.home, input.runDirectory),
806
832
  identity,
807
833
  payload: {
808
834
  type: "ticket-provenance-append",
@@ -810,7 +836,7 @@ export async function reprojectTicketProvenance(input: {
810
836
  lines: [...fresh, ...raw],
811
837
  },
812
838
  });
813
- const folded = await readTicketProvenance(input.ticketNumber, input.cwd, input.home);
839
+ const folded = await readTicketProvenance(input.ticketNumber, input.cwd, input.home, input.runDirectory);
814
840
  return {
815
841
  recordFile: pointer.recordFile,
816
842
  header: folded.header ?? header,