@akagilnc/pi-workflow-roles 0.1.2193 → 0.1.2207
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/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/dist/activation-ledger-git.js +12 -2
- package/dist/engine-detour.js +13 -3
- package/dist/public-cli/main.js +194 -50
- package/package.json +1 -1
- package/src/activation-ledger-git.ts +33 -5
- package/src/engine-detour.ts +13 -3
- package/src/public-cli/cli.ts +6 -7
- package/src/public-cli/invocation.ts +115 -11
- package/src/public-cli/option-definitions.ts +10 -9
- package/src/public-cli/taishi-run.ts +25 -1
- package/src/taishi-book-key.ts +66 -0
- package/src/taishi-cohort.ts +19 -7
- package/src/taishi-entry.ts +26 -21
- package/src/taishi-index.ts +53 -8
- package/src/taishi-ledger.ts +13 -3
package/src/taishi-index.ts
CHANGED
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
* #399 D9: retained solely for cohort (and sweep producers that feed it).
|
|
9
9
|
* Ticket CLI query path must not read this index (no bootstrap prerequisite).
|
|
10
10
|
* Row address includes book identity so cross-book same ticket numbers do not merge (D5).
|
|
11
|
-
* C2 joins cohort groups by issueNumber → page reference. Missing row =
|
|
11
|
+
* C2 joins cohort groups by (bookKey, issueNumber) → page reference. Missing row =
|
|
12
12
|
* typed vacancy entry — never silent skip, never live recompute.
|
|
13
|
+
* #412: bare cohort issue numbers resolve inside one book; no cross-book silent find.
|
|
14
|
+
* Legacy rows lacking bookKey heal via the single shared projectRoot→bookKey rule
|
|
15
|
+
* (git common-dir when resolvable, else `root:<identity>`) on read/ingest (F1/F3).
|
|
13
16
|
*
|
|
14
17
|
* Multi-process issue/sweep writers coordinate the whole read→upsert→write
|
|
15
18
|
* on one exclusive lock next to the index (atomic rename still prevents torn
|
|
@@ -22,7 +25,9 @@ import { writeFileAtomically } from "./atomic-write.ts";
|
|
|
22
25
|
import {
|
|
23
26
|
assertLedgerFileInsideHome,
|
|
24
27
|
ensureRealDirectoryTree,
|
|
28
|
+
physicalPathIdentity,
|
|
25
29
|
} from "./activation-ledger-topology.ts";
|
|
30
|
+
import { resolveTaishiBookKey } from "./taishi-book-key.ts";
|
|
26
31
|
import type {
|
|
27
32
|
TaishiIssueMetricsPage,
|
|
28
33
|
TaishiOptionalMetricNumber,
|
|
@@ -128,10 +133,27 @@ export function rowFromIssueMetricsPage(
|
|
|
128
133
|
};
|
|
129
134
|
}
|
|
130
135
|
|
|
136
|
+
/**
|
|
137
|
+
* #412 F1/F3: pre-#399 library-index rows omit bookKey. Heal with the single
|
|
138
|
+
* shared projectRoot→bookKey rule (#399 / ADR 0048) — the same rule the
|
|
139
|
+
* issue/sweep path uses, never a second resolver.
|
|
140
|
+
*/
|
|
141
|
+
export function normalizeTaishiLibraryIndexRow(
|
|
142
|
+
row: TaishiLibraryIndexRow,
|
|
143
|
+
): TaishiLibraryIndexRow {
|
|
144
|
+
const rawBook = (row as { readonly bookKey?: unknown }).bookKey;
|
|
145
|
+
if (typeof rawBook === "string" && rawBook !== "") {
|
|
146
|
+
if (rawBook === row.bookKey) return row;
|
|
147
|
+
return { ...row, bookKey: rawBook };
|
|
148
|
+
}
|
|
149
|
+
const projectRoot = physicalPathIdentity(row.projectRoot);
|
|
150
|
+
return { ...row, projectRoot, bookKey: resolveTaishiBookKey(projectRoot) };
|
|
151
|
+
}
|
|
152
|
+
|
|
131
153
|
function sortRows(
|
|
132
154
|
rows: readonly TaishiLibraryIndexRow[],
|
|
133
155
|
): TaishiLibraryIndexRow[] {
|
|
134
|
-
// Stable book → projectRoot sort (C1 listing). Cohort join is by
|
|
156
|
+
// Stable book → projectRoot sort (C1 listing). Cohort join is by (book, issue) find,
|
|
135
157
|
// not row order — issueNumber secondary keeps C2 rows deterministic too.
|
|
136
158
|
return [...rows].sort((a, b) => {
|
|
137
159
|
const byBook = a.bookKey.localeCompare(b.bookKey);
|
|
@@ -153,20 +175,24 @@ export function buildTaishiLibraryIndexPage(
|
|
|
153
175
|
): TaishiLibraryIndexPage {
|
|
154
176
|
return {
|
|
155
177
|
kind: "taishi-library-index",
|
|
156
|
-
rows: sortRows(rows),
|
|
178
|
+
rows: sortRows(rows.map(normalizeTaishiLibraryIndexRow)),
|
|
157
179
|
};
|
|
158
180
|
}
|
|
159
181
|
|
|
160
182
|
/**
|
|
161
|
-
* Look up the
|
|
183
|
+
* Look up the index row for (bookKey, issueNumber).
|
|
184
|
+
* #412: no cross-book silent scan — caller supplies the book (cwd book or book:N).
|
|
162
185
|
* Absence is a lawful cohort vacancy signal — not an error.
|
|
163
186
|
*/
|
|
164
187
|
export function findTaishiLibraryIndexRow(
|
|
165
188
|
index: TaishiLibraryIndexPage | undefined,
|
|
166
189
|
issueNumber: number,
|
|
190
|
+
bookKey: string,
|
|
167
191
|
): TaishiLibraryIndexRow | undefined {
|
|
168
192
|
if (index === undefined) return undefined;
|
|
169
|
-
return index.rows.find(
|
|
193
|
+
return index.rows.find(
|
|
194
|
+
(row) => row.issueNumber === issueNumber && row.bookKey === bookKey,
|
|
195
|
+
);
|
|
170
196
|
}
|
|
171
197
|
|
|
172
198
|
/** Row map key: book + root keeps cross-book same-ticket rows distinct (D5). */
|
|
@@ -193,6 +219,7 @@ export function upsertTaishiLibraryIndexRows(
|
|
|
193
219
|
const keyByIssue = new Map<string, string>();
|
|
194
220
|
|
|
195
221
|
const ingest = (row: TaishiLibraryIndexRow): void => {
|
|
222
|
+
row = normalizeTaishiLibraryIndexRow(row);
|
|
196
223
|
const key = indexRowKey(row);
|
|
197
224
|
// C2 uniqueness within book: one row per issueNumber — drop prior if number moved.
|
|
198
225
|
if (row.issueNumber !== undefined) {
|
|
@@ -230,8 +257,10 @@ export function upsertTaishiLibraryIndexRows(
|
|
|
230
257
|
|
|
231
258
|
/**
|
|
232
259
|
* Read existing library index, or undefined when absent.
|
|
233
|
-
* Single typed producer writes this file — JSON.parse failure is loud;
|
|
234
|
-
*
|
|
260
|
+
* Single typed producer writes this file — JSON.parse failure is loud; a
|
|
261
|
+
* syntactically valid but malformed shape (null / non-object / rows not an
|
|
262
|
+
* array) is rejected at this sole read boundary with the file path and real
|
|
263
|
+
* shape, so no type assertion lets garbage reach consumers (#413 r2 U1).
|
|
235
264
|
*/
|
|
236
265
|
export async function readTaishiLibraryIndexPage(
|
|
237
266
|
ledgerHome: string,
|
|
@@ -250,7 +279,23 @@ export async function readTaishiLibraryIndexPage(
|
|
|
250
279
|
}
|
|
251
280
|
throw error;
|
|
252
281
|
}
|
|
253
|
-
|
|
282
|
+
const parsed: unknown = JSON.parse(raw);
|
|
283
|
+
if (
|
|
284
|
+
parsed === null
|
|
285
|
+
|| typeof parsed !== "object"
|
|
286
|
+
|| !Array.isArray((parsed as { readonly rows?: unknown }).rows)
|
|
287
|
+
) {
|
|
288
|
+
const shape = parsed === null
|
|
289
|
+
? "null"
|
|
290
|
+
: typeof parsed !== "object"
|
|
291
|
+
? typeof parsed
|
|
292
|
+
: `object with non-array rows (${typeof (parsed as { rows?: unknown }).rows})`;
|
|
293
|
+
throw new Error(
|
|
294
|
+
`taishi library-index at ${path} is malformed (${shape}; expected an index page with a rows array) — rejected at the read boundary`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
// Heal legacy rows at the read boundary so every consumer sees defined bookKey.
|
|
298
|
+
return buildTaishiLibraryIndexPage((parsed as TaishiLibraryIndexPage).rows);
|
|
254
299
|
}
|
|
255
300
|
|
|
256
301
|
/**
|
package/src/taishi-ledger.ts
CHANGED
|
@@ -401,7 +401,9 @@ async function classifyScopedRun(input: {
|
|
|
401
401
|
/**
|
|
402
402
|
* Scan ledger home books/<book>/runs for runs in the issue scope.
|
|
403
403
|
* #399: scope = book × optional ticket.
|
|
404
|
-
* - bookKey set → that book only; whole-book when no ticket
|
|
404
|
+
* - bookKey set → that book only; whole-book when no ticket and no projectRoot;
|
|
405
|
+
* ticket filters alone; bookKey + projectRoot (cohort ensure, #412) narrows
|
|
406
|
+
* to that root inside the book.
|
|
405
407
|
* - projectRoot without bookKey (sweep/legacy): git-resolved → whole that book;
|
|
406
408
|
* non-git → path-narrow across books (fixture isolation).
|
|
407
409
|
* Damaged required sources become unreadable exclusions.
|
|
@@ -429,8 +431,16 @@ export async function scanTaishiIssueRuns(input: {
|
|
|
429
431
|
|
|
430
432
|
if (input.bookKey !== undefined && input.bookKey.trim() !== "") {
|
|
431
433
|
bookNames = [input.bookKey];
|
|
432
|
-
|
|
433
|
-
|
|
434
|
+
if (input.projectRoot !== undefined) {
|
|
435
|
+
// Cohort ensure conjunction (#412): the index join already selected the
|
|
436
|
+
// row — a cache-miss recompute must stay inside this root of this book,
|
|
437
|
+
// never inhale sibling roots' runs into one issue page.
|
|
438
|
+
wholeBook = false;
|
|
439
|
+
scopeRootIdentity = physicalPathIdentity(input.projectRoot);
|
|
440
|
+
} else {
|
|
441
|
+
// CLI book scope: whole book unless ticket filters. Never path-narrow.
|
|
442
|
+
wholeBook = true;
|
|
443
|
+
}
|
|
434
444
|
} else if (input.projectRoot !== undefined) {
|
|
435
445
|
const resolved = tryResolveBookKeyFromProjectRoot(input.projectRoot);
|
|
436
446
|
if (resolved !== undefined) {
|