@akagilnc/pi-workflow-roles 0.1.2118 → 0.1.2124

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.
@@ -96,15 +96,26 @@ export type TaishiOptionalTimestamp =
96
96
  * through directory discovery — keep this envelope stable.
97
97
  * C1 efficiency fields (完全耗时 / 排除后改动行数 / 耗时每千行 / 末次活动)
98
98
  * live on the envelope so sweep can project index rows without family dig.
99
- * issueNumber = caller typed field retained for cohort index join (ADR 0068
100
- * page key remains projectRoot; issueNumber is not the mechanical address).
99
+ * #399: page mechanical address includes book identity (and ticket when set).
100
+ * projectRoot is a retained recording/display field — not the address key.
101
101
  */
102
102
  export type TaishiIssueMetricsPage = {
103
103
  readonly kind: "taishi-issue-metrics";
104
104
  readonly mode: "issue";
105
+ /** Ledger book identity — part of the page mechanical address (#399). */
106
+ readonly bookKey: string;
107
+ /**
108
+ * Recording/display face (cwd or sweep workspace pointer).
109
+ * Not the page mechanical key after #399 / ADR 0068 revision.
110
+ */
105
111
  readonly projectRoot: string;
106
112
  /** Caller typed issue number — present only when supplied on the entry. */
107
113
  readonly issueNumber?: number;
114
+ /**
115
+ * Path-narrow address fragment for sweep/legacy fixture pages that share a
116
+ * book but represent distinct workspace roots. Absent on CLI book/ticket pages.
117
+ */
118
+ readonly scopeRootIdentity?: string;
108
119
  readonly legs: readonly TaishiLegEntry[];
109
120
  readonly unreadable: readonly TaishiUnreadableRun[];
110
121
  readonly unreadableCount: number;
@@ -124,13 +135,48 @@ export type TaishiIssueMetricsPage = {
124
135
  readonly lastActivityAt: TaishiOptionalTimestamp;
125
136
  };
126
137
 
127
- export function taishiIssuePageKey(projectRoot: string): string {
128
- const identity = physicalPathIdentity(projectRoot);
129
- return createHash("sha256").update(identity).digest("hex").slice(0, 32);
138
+ /**
139
+ * Page mechanical address (#399): always carries book identity.
140
+ * - book scope (CLI bare): bookKey only
141
+ * - ticket scope (CLI --ticket N): bookKey + issueNumber
142
+ * - root-narrow (sweep/legacy): bookKey + scopeRootIdentity
143
+ */
144
+ export type TaishiIssuePageAddress = {
145
+ readonly bookKey: string;
146
+ readonly issueNumber?: number;
147
+ readonly scopeRootIdentity?: string;
148
+ };
149
+
150
+ export function taishiIssuePageKey(address: TaishiIssuePageAddress): string {
151
+ const parts = ["book", address.bookKey];
152
+ if (address.issueNumber !== undefined) {
153
+ parts.push("ticket", String(address.issueNumber));
154
+ } else if (address.scopeRootIdentity !== undefined) {
155
+ parts.push("root", physicalPathIdentity(address.scopeRootIdentity));
156
+ }
157
+ return createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
158
+ }
159
+
160
+ export function taishiIssuePagePath(
161
+ ledgerHome: string,
162
+ address: TaishiIssuePageAddress,
163
+ ): string {
164
+ return join(ledgerHome, "taishi", "issues", `${taishiIssuePageKey(address)}.json`);
130
165
  }
131
166
 
132
- export function taishiIssuePagePath(ledgerHome: string, projectRoot: string): string {
133
- return join(ledgerHome, "taishi", "issues", `${taishiIssuePageKey(projectRoot)}.json`);
167
+ export function taishiIssuePageAddressFromPage(
168
+ page: Pick<
169
+ TaishiIssueMetricsPage,
170
+ "bookKey" | "issueNumber" | "scopeRootIdentity"
171
+ >,
172
+ ): TaishiIssuePageAddress {
173
+ return {
174
+ bookKey: page.bookKey,
175
+ ...(page.issueNumber === undefined ? {} : { issueNumber: page.issueNumber }),
176
+ ...(page.scopeRootIdentity === undefined
177
+ ? {}
178
+ : { scopeRootIdentity: page.scopeRootIdentity }),
179
+ };
134
180
  }
135
181
 
136
182
  function sortLegs(legs: readonly TaishiLegEntry[]): TaishiLegEntry[] {
@@ -250,6 +296,7 @@ export function summarizeTaishiRunEfficiency(
250
296
  }
251
297
 
252
298
  export async function buildTaishiIssueMetricsPage(input: {
299
+ readonly bookKey: string;
253
300
  readonly projectRoot: string;
254
301
  readonly runs: readonly TaishiReadableRunFacts[];
255
302
  readonly unreadable: readonly TaishiUnreadableRun[];
@@ -259,6 +306,11 @@ export async function buildTaishiIssueMetricsPage(input: {
259
306
  readonly changedLines?: number;
260
307
  /** Caller typed issue number — retained on page for cohort index join. */
261
308
  readonly issueNumber?: number;
309
+ /**
310
+ * When set, page address is book+root (sweep/legacy path-narrow).
311
+ * Must be absent for CLI whole-book / ticket pages so cross-cwd same book collides correctly.
312
+ */
313
+ readonly scopeRootIdentity?: string;
262
314
  }): Promise<TaishiIssueMetricsPage> {
263
315
  // Discover before compose/write — missing family tree fails loud with native
264
316
  // ENOENT/ENOTDIR (no empty-registry wash) and never emits a success page.
@@ -283,9 +335,13 @@ export async function buildTaishiIssueMetricsPage(input: {
283
335
  const envelope = {
284
336
  kind: "taishi-issue-metrics" as const,
285
337
  mode: "issue" as const,
338
+ bookKey: input.bookKey,
286
339
  projectRoot,
287
340
  // exactOptionalPropertyTypes: only materialize when caller supplied it.
288
341
  ...(input.issueNumber === undefined ? {} : { issueNumber: input.issueNumber }),
342
+ ...(input.scopeRootIdentity === undefined
343
+ ? {}
344
+ : { scopeRootIdentity: physicalPathIdentity(input.scopeRootIdentity) }),
289
345
  legs,
290
346
  unreadable,
291
347
  unreadableCount: unreadable.length,
@@ -312,7 +368,7 @@ export async function writeTaishiIssueMetricsPage(
312
368
  ledgerHome: string,
313
369
  page: TaishiIssueMetricsPage,
314
370
  ): Promise<string> {
315
- const path = taishiIssuePagePath(ledgerHome, page.projectRoot);
371
+ const path = taishiIssuePagePath(ledgerHome, taishiIssuePageAddressFromPage(page));
316
372
  ensureRealDirectoryTree(ledgerHome, dirname(path));
317
373
  assertLedgerFileInsideHome(path, ledgerHome);
318
374
  await writeFileAtomically(path, `${JSON.stringify(page, null, 2)}\n`);