@patronage/software-factory 1.0.0-alpha.5 → 1.0.0-alpha.6

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/dist/index.d.ts CHANGED
@@ -145,8 +145,6 @@ interface HqIngestDependencies {
145
145
  * `timeoutMs`: see the note there.
146
146
  */
147
147
  journalFlushBudgetMs?: number;
148
- /** @deprecated Legacy sidecars are now streamed directly; retained for API compatibility. */
149
- sidecarReclaimMs?: number;
150
148
  /**
151
149
  * Setup budget for callers that own the process lifetime — honored AS GIVEN,
152
150
  * above the `DEFAULT_HQ_INGEST_TIMEOUT_MS` ceiling that clamps `timeoutMs`.
@@ -184,12 +182,6 @@ interface HqSpoolEntryOutcome {
184
182
  kind: string;
185
183
  spool: string;
186
184
  /**
187
- * `migrated` belongs to the legacy JSONL journal only: the row was moved
188
- * into the current spool without being delivered. The spool pass that runs
189
- * after it in the same drain supersedes that line with a real outcome when
190
- * it gets to the row; a `migrated` line that survives the run means the row
191
- * is still waiting.
192
- *
193
185
  * `undeliverable` is the one terminal verdict (#445). Every other status
194
186
  * describes a moment: HQ was unreachable, HQ refused this content today, the
195
187
  * row moved. Retrying is meaningful for all of them. A wrong-origin entry is
@@ -198,13 +190,11 @@ interface HqSpoolEntryOutcome {
198
190
  * run. Leaving it spooled asks the operator to retry something that provably
199
191
  * cannot succeed, and the count it inflates is the one doctor goes red on.
200
192
  */
201
- status: "delivered" | "duplicate" | "migrated" | "rejected" | "undeliverable" | "unreachable";
193
+ status: "delivered" | "duplicate" | "rejected" | "undeliverable" | "unreachable";
202
194
  }
203
195
  interface HqSpoolFlushInput {
204
196
  clientId: string;
205
197
  clientSecret: string;
206
- /** Repository root whose legacy `.factory-memory` spool is also drained. */
207
- cwd: string;
208
198
  /** The profile's HQ origin; entries recorded against another are refused. */
209
199
  endpoint: string;
210
200
  /** Operator-named spool directories; replaces the default two locations. */
@@ -239,13 +229,13 @@ interface HqSpoolFlushSummary {
239
229
  interface HqSpoolWorkCount {
240
230
  /**
241
231
  * The earliest moment learned across pending spool files (their own write
242
- * time) and legacy journal rows (their own `failedAt`). Absent only when
232
+ * time). Absent only when
243
233
  * `pending` is `0`, or when every timestamp source was unreadable within
244
234
  * budget — an estimate for doctor's remediation message (#394), never a
245
235
  * precise audit trail.
246
236
  */
247
237
  oldestQueuedAt?: string;
248
- /** Spooled events and replayable journals waiting in the locations below. */
238
+ /** Spooled events waiting in the locations below. */
249
239
  pending: number;
250
240
  /** Locations that exist and hold spooled work. */
251
241
  spools: string[];
@@ -266,7 +256,7 @@ interface HqSpoolWorkCount {
266
256
  * same locations `flushHqSpool` drains are inspected here, read-only: no
267
257
  * directory is created, nothing is secured, and nothing is delivered.
268
258
  */
269
- declare function countHqSpoolWork(input: Pick<HqSpoolFlushInput, "cwd" | "explicitDirectories" | "repository">, dependencies?: {
259
+ declare function countHqSpoolWork(input: Pick<HqSpoolFlushInput, "explicitDirectories" | "repository">, dependencies?: {
270
260
  budgetMs?: number;
271
261
  env?: NodeJS.ProcessEnv;
272
262
  }): Promise<HqSpoolWorkCount>;
@@ -275,7 +265,7 @@ interface HqSpoolOrphan {
275
265
  /** The `hq-retry-spool` directory itself, ready to pass to `--dir`. */
276
266
  directory: string;
277
267
  oldestQueuedAt?: string;
278
- /** Spooled events and journal rows waiting there. */
268
+ /** Spooled events waiting there. */
279
269
  pending: number;
280
270
  /**
281
271
  * The location exists but could not be listed. As everywhere else in this
@@ -753,8 +743,8 @@ declare const mergeFreezeStateSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
753
743
  generationId: z.ZodNumber;
754
744
  headSha: z.ZodString;
755
745
  outcome: z.ZodEnum<{
756
- active: "active";
757
746
  stale: "stale";
747
+ active: "active";
758
748
  }>;
759
749
  reason: z.ZodString;
760
750
  recordedAt: z.ZodISODateTime;
@@ -1234,8 +1224,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1234
1224
  "run-interior-cycle": "run-interior-cycle";
1235
1225
  }>;
1236
1226
  stage: z.ZodEnum<{
1237
- gate: "gate";
1238
1227
  interior: "interior";
1228
+ gate: "gate";
1239
1229
  "interior-complete": "interior-complete";
1240
1230
  }>;
1241
1231
  }, z.core.$strip>>;
@@ -1251,10 +1241,10 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1251
1241
  reviewedHeadSha: z.ZodOptional<z.ZodString>;
1252
1242
  reviewedPatchId: z.ZodOptional<z.ZodString>;
1253
1243
  status: z.ZodEnum<{
1254
- blocked: "blocked";
1255
1244
  "not-required": "not-required";
1256
- stale: "stale";
1245
+ blocked: "blocked";
1257
1246
  current: "current";
1247
+ stale: "stale";
1258
1248
  missing: "missing";
1259
1249
  }>;
1260
1250
  }, z.core.$strip>;
@@ -1263,10 +1253,10 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1263
1253
  reviewedHeadSha: z.ZodOptional<z.ZodString>;
1264
1254
  reviewedPatchId: z.ZodOptional<z.ZodString>;
1265
1255
  status: z.ZodEnum<{
1266
- blocked: "blocked";
1267
1256
  "not-required": "not-required";
1268
- stale: "stale";
1257
+ blocked: "blocked";
1269
1258
  current: "current";
1259
+ stale: "stale";
1270
1260
  missing: "missing";
1271
1261
  }>;
1272
1262
  }, z.core.$strip>>;
@@ -1277,8 +1267,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1277
1267
  docsOnlyDeltaAccepted: z.ZodOptional<z.ZodBoolean>;
1278
1268
  docsOnlyVerifiedHeadSha: z.ZodOptional<z.ZodString>;
1279
1269
  prVerify: z.ZodEnum<{
1280
- stale: "stale";
1281
1270
  passed: "passed";
1271
+ stale: "stale";
1282
1272
  missing: "missing";
1283
1273
  }>;
1284
1274
  trivialDeltaAccepted: z.ZodOptional<z.ZodBoolean>;
@@ -1778,10 +1768,10 @@ declare const loadEvidenceEnvelopes: (cwd: string) => LoadedEvidenceEnvelope[];
1778
1768
  //#region src/review-proof-applicability.d.ts
1779
1769
  declare const REVIEW_STATUS_VALUES: readonly ["not-required", "current", "stale", "missing", "blocked"];
1780
1770
  declare const reviewStatusSchema: z.ZodEnum<{
1781
- blocked: "blocked";
1782
1771
  "not-required": "not-required";
1783
- stale: "stale";
1772
+ blocked: "blocked";
1784
1773
  current: "current";
1774
+ stale: "stale";
1785
1775
  missing: "missing";
1786
1776
  }>;
1787
1777
  type ReviewStatus = z.infer<typeof reviewStatusSchema>;
@@ -2187,9 +2177,9 @@ declare const retroEnvelopeV1Schema: z.ZodObject<{
2187
2177
  kind: z.ZodLiteral<"retro-envelope">;
2188
2178
  outcome: z.ZodOptional<z.ZodObject<{
2189
2179
  status: z.ZodEnum<{
2190
- blocked: "blocked";
2191
2180
  success: "success";
2192
2181
  fail: "fail";
2182
+ blocked: "blocked";
2193
2183
  "ship-with-followups": "ship-with-followups";
2194
2184
  }>;
2195
2185
  verdict: z.ZodOptional<z.ZodString>;
@@ -2949,8 +2939,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
2949
2939
  sessionId: z.ZodOptional<z.ZodString>;
2950
2940
  }, z.core.$strict>>;
2951
2941
  stage: z.ZodEnum<{
2952
- gate: "gate";
2953
2942
  interior: "interior";
2943
+ gate: "gate";
2954
2944
  }>;
2955
2945
  usage: z.ZodOptional<z.ZodObject<{
2956
2946
  estimatedCostUsd: z.ZodOptional<z.ZodNumber>;
@@ -3002,8 +2992,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3002
2992
  sessionId: z.ZodOptional<z.ZodString>;
3003
2993
  }, z.core.$strict>>;
3004
2994
  stage: z.ZodEnum<{
3005
- gate: "gate";
3006
2995
  interior: "interior";
2996
+ gate: "gate";
3007
2997
  }>;
3008
2998
  usage: z.ZodOptional<z.ZodObject<{
3009
2999
  estimatedCostUsd: z.ZodOptional<z.ZodNumber>;
@@ -3046,8 +3036,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3046
3036
  sessionId: z.ZodOptional<z.ZodString>;
3047
3037
  }, z.core.$strict>>;
3048
3038
  stage: z.ZodEnum<{
3049
- gate: "gate";
3050
3039
  interior: "interior";
3040
+ gate: "gate";
3051
3041
  }>;
3052
3042
  usage: z.ZodOptional<z.ZodObject<{
3053
3043
  estimatedCostUsd: z.ZodOptional<z.ZodNumber>;
@@ -3077,7 +3067,7 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3077
3067
  staleRepeats: number;
3078
3068
  };
3079
3069
  observedAt: string;
3080
- stage: "gate" | "interior";
3070
+ stage: "interior" | "gate";
3081
3071
  issue?: number | undefined;
3082
3072
  pr?: number | undefined;
3083
3073
  threadId?: string | undefined;
@@ -3333,8 +3323,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3333
3323
  interiorCycle: z.ZodOptional<z.ZodNumber>;
3334
3324
  observedAt: z.ZodString;
3335
3325
  purpose: z.ZodEnum<{
3336
- review: "review";
3337
3326
  code: "code";
3327
+ review: "review";
3338
3328
  }>;
3339
3329
  slotResolution: z.ZodObject<{
3340
3330
  effort: z.ZodEnum<{
@@ -3412,8 +3402,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3412
3402
  interiorCycle: z.ZodOptional<z.ZodNumber>;
3413
3403
  observedAt: z.ZodString;
3414
3404
  purpose: z.ZodEnum<{
3415
- review: "review";
3416
3405
  code: "code";
3406
+ review: "review";
3417
3407
  }>;
3418
3408
  slotResolution: z.ZodObject<{
3419
3409
  effort: z.ZodEnum<{
@@ -3482,8 +3472,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3482
3472
  interiorCycle: z.ZodOptional<z.ZodNumber>;
3483
3473
  observedAt: z.ZodString;
3484
3474
  purpose: z.ZodEnum<{
3485
- review: "review";
3486
3475
  code: "code";
3476
+ review: "review";
3487
3477
  }>;
3488
3478
  slotResolution: z.ZodObject<{
3489
3479
  effort: z.ZodEnum<{
@@ -3555,7 +3545,7 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3555
3545
  exitCode: number | null;
3556
3546
  harness: string;
3557
3547
  observedAt: string;
3558
- purpose: "review" | "code";
3548
+ purpose: "code" | "review";
3559
3549
  slotResolution: {
3560
3550
  effort: "high" | "low" | "medium" | "xhigh";
3561
3551
  engine: string;
@@ -4076,13 +4066,13 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
4076
4066
  reviews: {
4077
4067
  correctness: {
4078
4068
  required: boolean;
4079
- status: "blocked" | "not-required" | "stale" | "current" | "missing";
4069
+ status: "not-required" | "blocked" | "current" | "stale" | "missing";
4080
4070
  reviewedHeadSha?: string | undefined;
4081
4071
  reviewedPatchId?: string | undefined;
4082
4072
  };
4083
4073
  security?: {
4084
4074
  required: boolean;
4085
- status: "blocked" | "not-required" | "stale" | "current" | "missing";
4075
+ status: "not-required" | "blocked" | "current" | "stale" | "missing";
4086
4076
  reviewedHeadSha?: string | undefined;
4087
4077
  reviewedPatchId?: string | undefined;
4088
4078
  } | undefined;
@@ -4090,7 +4080,7 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
4090
4080
  schemaVersion: 1;
4091
4081
  verification: {
4092
4082
  command: "patronage-factory pr:verify";
4093
- prVerify: "stale" | "passed" | "missing";
4083
+ prVerify: "passed" | "stale" | "missing";
4094
4084
  docsOnlyDeltaAccepted?: boolean | undefined;
4095
4085
  docsOnlyVerifiedHeadSha?: string | undefined;
4096
4086
  trivialDeltaAccepted?: boolean | undefined;
@@ -4133,7 +4123,7 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
4133
4123
  interior: number;
4134
4124
  };
4135
4125
  nextAction: "run-gate-cycle" | "accept-nonblocking-findings" | "escalate-to-triage" | "ready-for-human" | "advance-to-gate" | "run-interior-cycle";
4136
- stage: "gate" | "interior" | "interior-complete";
4126
+ stage: "interior" | "gate" | "interior-complete";
4137
4127
  forcedTransition?: "gate-cap-exhausted" | "interior-cap-reached" | undefined;
4138
4128
  } | undefined;
4139
4129
  reviewRuns?: PrReviewResult[] | undefined;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as __exportAll } from "./chunk-pbuEa-1d.js";
3
- import { appendFileSync, constants, cpSync, createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { appendFileSync, constants, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { Command, InvalidArgumentError } from "commander";
6
6
  import path from "node:path";
@@ -9,7 +9,6 @@ import { z } from "zod";
9
9
  import { execFileSync, spawnSync } from "node:child_process";
10
10
  import { link, lstat, mkdir, open, readFile, readdir, realpath, rename, stat, unlink } from "node:fs/promises";
11
11
  import os, { homedir } from "node:os";
12
- import { createInterface } from "node:readline";
13
12
  import { setImmediate } from "node:timers";
14
13
  import { setImmediate as setImmediate$1, setTimeout as setTimeout$1 } from "node:timers/promises";
15
14
  import { Worker } from "node:worker_threads";
@@ -18,7 +17,7 @@ import { parse } from "yaml";
18
17
  import { promisify } from "node:util";
19
18
  import { GitHubApiError, mintInstallationToken } from "@patronage/factory-ci";
20
19
  //#region package.json
21
- var version = "1.0.0-alpha.5";
20
+ var version = "1.0.0-alpha.6";
22
21
  //#endregion
23
22
  //#region src/review-rungs.ts
24
23
  const EVIDENCE_REVIEW_RUNGS$1 = [
@@ -1348,7 +1347,6 @@ function formatUserConfigError(configPath, error) {
1348
1347
  }
1349
1348
  const DEFAULT_HQ_TRANSPORT_TIMEOUT_MS = 2500;
1350
1349
  const HQ_RETRY_SPOOL_DIRNAME = "hq-retry-spool";
1351
- const HQ_RETRY_JOURNAL_BASENAME = "hq-retry-journal.jsonl";
1352
1350
  /**
1353
1351
  * Appended to a spooled event that can never be delivered (#445).
1354
1352
  *
@@ -1389,9 +1387,6 @@ const SPOOL_REPOSITORY_MARKER = ".repository-identity";
1389
1387
  * and the extra level is slack for a key scheme that nests one deeper.
1390
1388
  */
1391
1389
  const HQ_SPOOL_SWEEP_MAX_DEPTH = 4;
1392
- `${HQ_RETRY_JOURNAL_BASENAME}`;
1393
- `${HQ_RETRY_SPOOL_DIRNAME}`;
1394
- const LEGACY_FACTORY_MEMORY_DIRNAME = ".factory-memory";
1395
1390
  const HQ_SPOOL_STATE_SEGMENTS = ["patronage-factory", "hq-spool"];
1396
1391
  const MAX_HQ_CONFIG_BYTES = 1024 * 1024;
1397
1392
  const MAX_HQ_INGEST_PAYLOAD_BYTES = 256 * 1024;
@@ -1503,12 +1498,6 @@ const repositorySpoolLayout = (repository, env = process.env) => ({
1503
1498
  HQ_RETRY_SPOOL_DIRNAME
1504
1499
  ]
1505
1500
  });
1506
- /** Read-only drain source for spools written before the relocation. */
1507
- const legacySpoolLayout = (cwd) => ({
1508
- create: false,
1509
- root: cwd,
1510
- segments: [LEGACY_FACTORY_MEMORY_DIRNAME, HQ_RETRY_SPOOL_DIRNAME]
1511
- });
1512
1501
  /**
1513
1502
  * An operator-named directory (`hq:flush --dir`). Accepts either the spool
1514
1503
  * itself or the directory holding it, so a `.factory-memory` path and a
@@ -1580,30 +1569,10 @@ const secureSpoolLayout = async (layout, deadline) => {
1580
1569
  const memoryIdentity = identities.at(-2);
1581
1570
  if (spool === void 0 || memory === void 0 || spoolIdentity === void 0 || memoryIdentity === void 0 || !await directoryIdentityMatches(memory, memoryIdentity, deadline) || !await directoryIdentityMatches(spool, spoolIdentity, deadline)) return;
1582
1571
  return {
1583
- memory,
1584
- memoryIdentity,
1585
1572
  spool,
1586
1573
  spoolIdentity
1587
1574
  };
1588
1575
  };
1589
- /**
1590
- * The legacy JSONL journal lives one level above the spool. Securing it on its
1591
- * own lets a pre-spool `.factory-memory` (journal but no spool directory)
1592
- * drain without the read path creating anything inside the worktree.
1593
- */
1594
- const secureJournalDirectory = async (layout, deadline) => {
1595
- const segments = layout.segments.slice(0, -1);
1596
- if (segments.length === 0) return;
1597
- const chain = await secureChain(layout, segments, deadline);
1598
- if (chain === void 0) return;
1599
- const directory = chain.directories.at(-1);
1600
- const identity = chain.identities.at(-1);
1601
- if (directory === void 0 || identity === void 0 || !await directoryIdentityMatches(directory, identity, deadline)) return;
1602
- return {
1603
- directory,
1604
- identity
1605
- };
1606
- };
1607
1576
  const readDirectoryIdentity = async (directory, deadline) => {
1608
1577
  const opened = await openWithin(directory, constants.O_RDONLY + constants.O_DIRECTORY + constants.O_NOFOLLOW, remainingMs(deadline));
1609
1578
  if (opened.status !== "fulfilled") return;
@@ -2333,225 +2302,6 @@ const replayCloseoutSpool = async (layout, endpoint, clientId, clientSecret, req
2333
2302
  if (!await mutateBoundDirectory(spool, spoolIdentity, performance.now() + flushBudgetMs, () => outcome.ok ? unlink(claimPath) : rename(claimPath, eventPath))) return;
2334
2303
  }
2335
2304
  };
2336
- const writeLegacyCursor = async (directory, directoryIdentity, cursorPath, offset, deadline) => {
2337
- if (path.dirname(cursorPath) !== directory || !await directoryIdentityMatches(directory, directoryIdentity, deadline)) return false;
2338
- const temporary = `${cursorPath}.${randomUUID()}.tmp`;
2339
- const opened = await openWithin(temporary, constants.O_CREAT + constants.O_EXCL + constants.O_WRONLY + constants.O_NOFOLLOW, remainingMs(deadline), 384);
2340
- if (opened.status !== "fulfilled") return false;
2341
- const wrote = await settleWithin((async () => {
2342
- await opened.value.writeFile(String(offset), "utf-8");
2343
- await opened.value.sync();
2344
- })(), remainingMs(deadline));
2345
- const closed = await settleWithin(opened.value.close(), remainingMs(deadline));
2346
- if (wrote.status !== "fulfilled" || closed.status !== "fulfilled") {
2347
- await mutateBoundDirectory(directory, directoryIdentity, deadline, async () => unlink(temporary));
2348
- return false;
2349
- }
2350
- if (!await mutateBoundDirectory(directory, directoryIdentity, deadline, async () => rename(temporary, cursorPath))) {
2351
- await mutateBoundDirectory(directory, directoryIdentity, deadline, async () => unlink(temporary));
2352
- return false;
2353
- }
2354
- return await syncBoundDirectory(directory, directoryIdentity, deadline);
2355
- };
2356
- const readLegacyCursor = async (cursorPath, deadline) => {
2357
- const file = await readBoundedTextFileNoFollow(cursorPath, deadline, 128);
2358
- if (file === void 0) return 0;
2359
- const offset = Number(file.contents);
2360
- return Number.isSafeInteger(offset) && offset >= 0 ? offset : 0;
2361
- };
2362
- const claimLegacySource = async (sourcePath, target, directory, directoryIdentity, deadline) => {
2363
- if (path.dirname(sourcePath) !== directory || !await directoryIdentityMatches(directory, directoryIdentity, deadline)) return;
2364
- const metadata = await settleWithin(lstat(sourcePath), remainingMs(deadline));
2365
- if (metadata.status !== "fulfilled" || !metadata.value.isFile() || metadata.value.isSymbolicLink()) return;
2366
- const claimPath = `${target}.legacy-claim-${randomUUID()}`;
2367
- if (!await mutateBoundDirectory(directory, directoryIdentity, deadline, async () => rename(sourcePath, claimPath))) return;
2368
- return claimPath;
2369
- };
2370
- const releaseLegacyClaim = async (claimPath, target, directory, directoryIdentity, deadline) => {
2371
- if (path.dirname(claimPath) !== directory) return false;
2372
- const readyPath = `${target}.legacy-ready-${randomUUID()}`;
2373
- return mutateBoundDirectory(directory, directoryIdentity, deadline, async () => rename(claimPath, readyPath));
2374
- };
2375
- async function replayRetryJournal(layout, writeLayout, endpoint, clientId, clientSecret, request, dependencies, transportBudgetMs, setupDeadline, replayDeadline, options = {}) {
2376
- const maxEntries = options.maxEntries ?? MAX_HQ_REPLAY_ENTRIES;
2377
- const { report } = options;
2378
- const secured = await settleWithin(secureJournalDirectory(layout, setupDeadline), remainingMs(setupDeadline));
2379
- if (secured.status !== "fulfilled" || secured.value === void 0) return;
2380
- const { directory, identity: directoryIdentity } = secured.value;
2381
- const target = path.join(directory, HQ_RETRY_JOURNAL_BASENAME);
2382
- const baseName = path.basename(target);
2383
- const listing = await settleWithin(readdir(directory), remainingMs(setupDeadline));
2384
- if (listing.status !== "fulfilled") return;
2385
- let delivered = 0;
2386
- let corruptDropped = 0;
2387
- const sources = listing.value.filter((name) => name === baseName || name.startsWith(`${baseName}.legacy-ready-`) || name.startsWith(`${baseName}.draining-`) || name.startsWith(`${baseName}.legacy-claim-`)).filter((name) => !name.includes(".cursor")).toSorted();
2388
- for (const name of sources) {
2389
- if (delivered >= maxEntries || remainingMs(replayDeadline) <= 0) break;
2390
- const sourcePath = path.join(directory, name);
2391
- if (name.startsWith(`${baseName}.draining-`) || name.startsWith(`${baseName}.legacy-claim-`)) {
2392
- const metadata = await settleWithin(lstat(sourcePath), remainingMs(setupDeadline));
2393
- if (metadata.status !== "fulfilled" || !metadata.value.isFile() || metadata.value.isSymbolicLink() || Date.now() - Math.max(metadata.value.mtimeMs, metadata.value.ctimeMs) < STALE_SPOOL_ARTIFACT_MS) continue;
2394
- }
2395
- const claimPath = await claimLegacySource(sourcePath, target, directory, directoryIdentity, setupDeadline);
2396
- if (claimPath === void 0) continue;
2397
- const opened = await openWithin(claimPath, constants.O_RDONLY + constants.O_NOFOLLOW, remainingMs(setupDeadline));
2398
- if (opened.status !== "fulfilled") {
2399
- await releaseLegacyClaim(claimPath, target, directory, directoryIdentity, setupDeadline);
2400
- continue;
2401
- }
2402
- const sourceMetadata = await settleWithin(opened.value.stat(), remainingMs(setupDeadline));
2403
- if (sourceMetadata.status !== "fulfilled" || !sourceMetadata.value.isFile()) {
2404
- await settleWithin(opened.value.close(), remainingMs(setupDeadline));
2405
- await releaseLegacyClaim(claimPath, target, directory, directoryIdentity, setupDeadline);
2406
- continue;
2407
- }
2408
- const cursorPath = `${target}.legacy-cursor-${String(sourceMetadata.value.dev)}-${String(sourceMetadata.value.ino)}`;
2409
- let offset = await readLegacyCursor(cursorPath, setupDeadline);
2410
- if (offset >= sourceMetadata.value.size) {
2411
- await settleWithin(opened.value.close(), remainingMs(setupDeadline));
2412
- const current = await settleWithin(lstat(claimPath), remainingMs(setupDeadline));
2413
- if (current.status === "fulfilled" && current.value.isFile() && !current.value.isSymbolicLink() && current.value.dev === sourceMetadata.value.dev && current.value.ino === sourceMetadata.value.ino && current.value.size === sourceMetadata.value.size) {
2414
- await mutateBoundDirectory(directory, directoryIdentity, setupDeadline, async () => unlink(claimPath));
2415
- await mutateBoundDirectory(directory, directoryIdentity, setupDeadline, async () => unlink(cursorPath));
2416
- }
2417
- continue;
2418
- }
2419
- const abort = new AbortController();
2420
- const streamBudget = Math.min(remainingMs(setupDeadline), remainingMs(replayDeadline));
2421
- const abortTimer = setTimeout(() => abort.abort(), Math.max(0, streamBudget));
2422
- abortTimer.unref();
2423
- const stream = createReadStream(claimPath, {
2424
- autoClose: false,
2425
- encoding: "utf-8",
2426
- fd: opened.value.fd,
2427
- signal: abort.signal,
2428
- start: offset
2429
- });
2430
- const lines = createInterface({
2431
- crlfDelay: Infinity,
2432
- input: stream
2433
- });
2434
- let reachedEof = true;
2435
- try {
2436
- for await (const line of lines) {
2437
- if (delivered >= maxEntries || remainingMs(replayDeadline) <= 0 || remainingMs(setupDeadline) <= 0) {
2438
- reachedEof = false;
2439
- break;
2440
- }
2441
- const nextOffset = Math.min(sourceMetadata.value.size, offset + Buffer.byteLength(line, "utf-8") + 1);
2442
- if (line.trim().length === 0) {
2443
- offset = nextOffset;
2444
- if (!await writeLegacyCursor(directory, directoryIdentity, cursorPath, offset, setupDeadline)) {
2445
- reachedEof = false;
2446
- break;
2447
- }
2448
- continue;
2449
- }
2450
- let parsed;
2451
- try {
2452
- parsed = JSON.parse(line);
2453
- } catch {
2454
- corruptDropped += 1;
2455
- offset = nextOffset;
2456
- if (!await writeLegacyCursor(directory, directoryIdentity, cursorPath, offset, setupDeadline)) {
2457
- reachedEof = false;
2458
- break;
2459
- }
2460
- continue;
2461
- }
2462
- if (!isReplayableEntry(parsed)) {
2463
- offset = nextOffset;
2464
- if (!await writeLegacyCursor(directory, directoryIdentity, cursorPath, offset, setupDeadline)) {
2465
- reachedEof = false;
2466
- break;
2467
- }
2468
- continue;
2469
- }
2470
- const entryEndpoint = validatedEndpoint(parsed.endpoint);
2471
- let handled = false;
2472
- const row = {
2473
- eventId: parsed.event.eventId,
2474
- kind: parsed.event.kind
2475
- };
2476
- let rowOutcome = {
2477
- detail: `recorded endpoint ${entryEndpoint?.origin ?? "(unusable)"} is not this repository's authorized HQ origin ${endpoint.origin}`,
2478
- ...row,
2479
- spool: directory,
2480
- status: "migrated"
2481
- };
2482
- if (entryEndpoint?.origin === endpoint.origin) {
2483
- rowOutcome = {
2484
- ...rowOutcome,
2485
- detail: "event could not be serialized for delivery"
2486
- };
2487
- let body;
2488
- try {
2489
- body = JSON.stringify(parsed.event);
2490
- } catch {
2491
- body = void 0;
2492
- }
2493
- if (body !== void 0) {
2494
- const outcome = await attemptTransport(request, endpoint, clientId, clientSecret, body, Math.min(transportBudgetMs, remainingMs(replayDeadline)), report !== void 0);
2495
- handled = outcome.ok;
2496
- if (handled) delivered += 1;
2497
- rowOutcome = outcome.ok ? {
2498
- ...row,
2499
- spool: directory,
2500
- status: outcome.duplicate === true ? "duplicate" : "delivered"
2501
- } : {
2502
- ...row,
2503
- detail: outcome.detail === void 0 ? outcome.reason : `${outcome.reason}: ${outcome.detail}`,
2504
- spool: directory,
2505
- status: drainFailureStatus(outcome.reason)
2506
- };
2507
- }
2508
- }
2509
- if (!handled) {
2510
- const retained = await appendCloseoutSpool(writeLayout, parsed, performance.now() + journalFlushBudgetFor(dependencies));
2511
- if (retained === void 0) options.onMigrate?.();
2512
- if (retained !== void 0) {
2513
- report?.({
2514
- ...rowOutcome,
2515
- detail: `${rowOutcome.detail ?? rowOutcome.status}; migration into the current spool ${retained}`,
2516
- status: "unreachable"
2517
- });
2518
- reachedEof = false;
2519
- break;
2520
- }
2521
- }
2522
- report?.(rowOutcome);
2523
- offset = nextOffset;
2524
- if (!await writeLegacyCursor(directory, directoryIdentity, cursorPath, offset, setupDeadline)) {
2525
- reachedEof = false;
2526
- break;
2527
- }
2528
- }
2529
- } catch {
2530
- reachedEof = false;
2531
- } finally {
2532
- clearTimeout(abortTimer);
2533
- abort.abort();
2534
- lines.close();
2535
- stream.destroy();
2536
- await settleWithin(opened.value.close(), remainingMs(setupDeadline));
2537
- }
2538
- if (reachedEof && offset >= sourceMetadata.value.size) {
2539
- const current = await settleWithin(lstat(claimPath), remainingMs(setupDeadline));
2540
- if (current.status === "fulfilled" && current.value.isFile() && !current.value.isSymbolicLink() && current.value.dev === sourceMetadata.value.dev && current.value.ino === sourceMetadata.value.ino && current.value.size === sourceMetadata.value.size && await directoryIdentityMatches(directory, directoryIdentity, setupDeadline)) {
2541
- await mutateBoundDirectory(directory, directoryIdentity, setupDeadline, async () => unlink(claimPath));
2542
- await mutateBoundDirectory(directory, directoryIdentity, setupDeadline, async () => unlink(cursorPath));
2543
- }
2544
- } else await releaseLegacyClaim(claimPath, target, directory, directoryIdentity, setupDeadline);
2545
- }
2546
- await reportReplayOutcome(dependencies, delivered, corruptDropped);
2547
- }
2548
- async function reportReplayOutcome(dependencies, delivered, corruptDropped) {
2549
- if (delivered <= 0 && corruptDropped <= 0) return;
2550
- const parts = [];
2551
- if (delivered > 0) parts.push(`${delivered} queued event(s) delivered from the retry journal`);
2552
- if (corruptDropped > 0) parts.push(`${corruptDropped} unparseable journal line(s) dropped (partial write recovered, #120)`);
2553
- await reportDiagnostic(dependencies, performance.now() + journalFlushBudgetFor(dependencies), parts.join("; "), delivered > 0 ? "HQ ingest confirmed" : "HQ ingest recovered");
2554
- }
2555
2305
  async function deliverHqIngest(input, dependencies, setupDeadline, timeoutMs, transportBudgetMs) {
2556
2306
  let config;
2557
2307
  try {
@@ -2595,7 +2345,6 @@ async function deliverHqIngest(input, dependencies, setupDeadline, timeoutMs, tr
2595
2345
  owner: input.profile.repository.owner,
2596
2346
  repo: input.profile.repository.name
2597
2347
  }, env);
2598
- const readLayouts = [writeLayout, legacySpoolLayout(input.cwd)];
2599
2348
  const now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
2600
2349
  const makeEventId = dependencies.randomUUID ?? randomUUID;
2601
2350
  let event;
@@ -2666,10 +2415,7 @@ async function deliverHqIngest(input, dependencies, setupDeadline, timeoutMs, tr
2666
2415
  throw new Error(reason);
2667
2416
  }
2668
2417
  const replayDeadline = performance.now() + timeoutMs;
2669
- for (const readLayout of readLayouts) {
2670
- await replayCloseoutSpool(readLayout, endpoint, clientId, clientSecret, request, transportBudgetMs, replayDeadline, replayDeadline, journalFlushBudgetFor(dependencies), persistedEventPath);
2671
- await replayRetryJournal(readLayout, writeLayout, endpoint, clientId, clientSecret, request, dependencies, transportBudgetMs, replayDeadline, replayDeadline);
2672
- }
2418
+ await replayCloseoutSpool(writeLayout, endpoint, clientId, clientSecret, request, transportBudgetMs, replayDeadline, replayDeadline, journalFlushBudgetFor(dependencies), persistedEventPath);
2673
2419
  const outcome = await attemptTransport(request, endpoint, clientId, clientSecret, body, transportBudgetMs);
2674
2420
  if (!outcome.ok) {
2675
2421
  ({reason, unconfirmed} = outcome);
@@ -2748,20 +2494,7 @@ async function awaitPendingHqIngest() {
2748
2494
  const DEFAULT_HQ_FLUSH_BUDGET_MS = 600 * 1e3;
2749
2495
  /** ENOENT is the only filesystem answer that means "this location is absent". */
2750
2496
  const isMissingEntryError = (error) => typeof error === "object" && error !== null && error.code === "ENOENT";
2751
- const journalRowCount = (contents) => contents.split("\n").filter((line) => line.trim() !== "").length;
2752
- const journalLineTimestamps = (contents) => {
2753
- const timestamps = [];
2754
- for (const line of contents.split("\n")) {
2755
- const trimmed = line.trim();
2756
- if (trimmed === "") continue;
2757
- try {
2758
- const parsed = JSON.parse(trimmed);
2759
- if (typeof parsed.failedAt === "string") timestamps.push(parsed.failedAt);
2760
- } catch {}
2761
- }
2762
- return timestamps;
2763
- };
2764
- const countRemainingSpoolWork = async (spools, drainLayouts, deadline) => {
2497
+ const countRemainingSpoolWork = async (spools, deadline) => {
2765
2498
  let remaining = 0;
2766
2499
  let unlistableScans = 0;
2767
2500
  let oldestMs;
@@ -2790,27 +2523,6 @@ const countRemainingSpoolWork = async (spools, drainLayouts, deadline) => {
2790
2523
  if (probed.status === "fulfilled") noteTimestamp(probed.value.mtime.toISOString());
2791
2524
  }
2792
2525
  }
2793
- for (const layout of drainLayouts) {
2794
- const journalDir = path.dirname(layoutPath(layout));
2795
- const journalListing = await settleWithin(readdir(journalDir), remainingMs(deadline));
2796
- if (journalListing.status !== "fulfilled") {
2797
- await countUnlistable(journalDir);
2798
- continue;
2799
- }
2800
- const journalNames = journalListing.value.filter((name) => name === "hq-retry-journal.jsonl" || name.startsWith(`hq-retry-journal.jsonl.legacy-ready-`) || name.startsWith(`hq-retry-journal.jsonl.legacy-claim-`) || name.startsWith(`hq-retry-journal.jsonl.draining-`));
2801
- for (const name of journalNames) {
2802
- const journalFilePath = path.join(journalDir, name);
2803
- const read = await settleWithin(readFile(journalFilePath, "utf-8"), remainingMs(deadline));
2804
- if (read.status === "fulfilled") {
2805
- remaining += journalRowCount(read.value);
2806
- for (const timestamp of journalLineTimestamps(read.value)) noteTimestamp(timestamp);
2807
- continue;
2808
- }
2809
- remaining += 1;
2810
- const probed = await settleWithin(lstat(journalFilePath), remainingMs(deadline));
2811
- if (probed.status === "fulfilled") noteTimestamp(probed.value.mtime.toISOString());
2812
- }
2813
- }
2814
2526
  return {
2815
2527
  ...oldestMs === void 0 ? {} : { oldestQueuedAt: new Date(oldestMs).toISOString() },
2816
2528
  remaining,
@@ -2831,10 +2543,10 @@ const DEFAULT_HQ_SPOOL_INSPECT_BUDGET_MS = 5e3;
2831
2543
  */
2832
2544
  async function countHqSpoolWork(input, dependencies = {}) {
2833
2545
  const env = dependencies.env ?? process.env;
2834
- const layouts = input.explicitDirectories && input.explicitDirectories.length > 0 ? input.explicitDirectories.map(explicitSpoolLayout) : [repositorySpoolLayout(input.repository, env), legacySpoolLayout(input.cwd)];
2546
+ const layouts = input.explicitDirectories && input.explicitDirectories.length > 0 ? input.explicitDirectories.map(explicitSpoolLayout) : [repositorySpoolLayout(input.repository, env)];
2835
2547
  const deadline = performance.now() + (dependencies.budgetMs ?? DEFAULT_HQ_SPOOL_INSPECT_BUDGET_MS);
2836
2548
  const paths = layouts.map((layout) => layoutPath(layout));
2837
- const { oldestQueuedAt, remaining, unlistableScans } = await countRemainingSpoolWork(paths, layouts, deadline);
2549
+ const { oldestQueuedAt, remaining, unlistableScans } = await countRemainingSpoolWork(paths, deadline);
2838
2550
  const existing = await Promise.all(paths.map(async (spool) => {
2839
2551
  return (await settleWithin(lstat(spool), remainingMs(deadline))).status === "fulfilled" ? spool : void 0;
2840
2552
  }));
@@ -3002,7 +2714,7 @@ async function sweepHqSpoolOrphans(input, dependencies = {}) {
3002
2714
  });
3003
2715
  continue;
3004
2716
  }
3005
- const counted = await countRemainingSpoolWork([spool], [explicitSpoolLayout(spool)], deadline);
2717
+ const counted = await countRemainingSpoolWork([spool], deadline);
3006
2718
  if (counted.remaining === 0 && counted.unlistableScans === 0) continue;
3007
2719
  orphans.push({
3008
2720
  directory: spool,
@@ -3022,7 +2734,7 @@ async function sweepHqSpoolOrphans(input, dependencies = {}) {
3022
2734
  });
3023
2735
  continue;
3024
2736
  }
3025
- const counted = await countRemainingSpoolWork([spool], [explicitSpoolLayout(spool)], deadline);
2737
+ const counted = await countRemainingSpoolWork([spool], deadline);
3026
2738
  if (counted.remaining === 0 && counted.unlistableScans === 0) continue;
3027
2739
  orphans.push({
3028
2740
  directory: spool,
@@ -3042,8 +2754,8 @@ async function sweepHqSpoolOrphans(input, dependencies = {}) {
3042
2754
  * This is the deliberate counterpart to the sink's advisory emit path: the
3043
2755
  * caller has already resolved credentials explicitly, so there is no cap, no
3044
2756
  * daemon, and no background retry — one pass, bounded, over the repo-keyed
3045
- * location plus the legacy cwd location (or the operator's `--dir`). Delivery
3046
- * itself is the sink's own replay, so there is exactly one POST path.
2757
+ * location (or the operator's `--dir`). Delivery itself is the sink's own
2758
+ * replay, so there is exactly one POST path.
3047
2759
  */
3048
2760
  async function flushHqSpool(input, dependencies = {}) {
3049
2761
  const endpoint = validatedIngestEndpoint(input.endpoint);
@@ -3056,8 +2768,7 @@ async function flushHqSpool(input, dependencies = {}) {
3056
2768
  journalFlushBudgetMs: dependencies.journalFlushBudgetMs
3057
2769
  });
3058
2770
  const deadline = performance.now() + (dependencies.budgetMs ?? DEFAULT_HQ_FLUSH_BUDGET_MS);
3059
- const writeLayout = repositorySpoolLayout(input.repository, env);
3060
- const layouts = input.explicitDirectories && input.explicitDirectories.length > 0 ? input.explicitDirectories.map(explicitSpoolLayout) : [writeLayout, legacySpoolLayout(input.cwd)];
2771
+ const layouts = input.explicitDirectories && input.explicitDirectories.length > 0 ? input.explicitDirectories.map(explicitSpoolLayout) : [repositorySpoolLayout(input.repository, env)];
3061
2772
  const outcomeById = /* @__PURE__ */ new Map();
3062
2773
  let rejectedFiles = 0;
3063
2774
  let unreachableFiles = 0;
@@ -3066,31 +2777,13 @@ async function flushHqSpool(input, dependencies = {}) {
3066
2777
  outcomeById.set(outcome.eventId, outcome);
3067
2778
  dependencies.report?.(outcome);
3068
2779
  };
3069
- const journalReport = (outcome) => {
3070
- if (outcome.status === "unreachable" && outcome.detail?.includes("migration into the current spool")) unreachableFiles += 1;
3071
- record(outcome);
3072
- };
3073
2780
  const spoolReport = (outcome) => {
3074
2781
  if (outcome.status === "rejected") rejectedFiles += 1;
3075
2782
  if (outcome.status === "unreachable") unreachableFiles += 1;
3076
2783
  record(outcome);
3077
2784
  };
3078
2785
  const spools = [];
3079
- let migratedIntoWriteLayout = false;
3080
- const journalDrainOptions = {
3081
- maxEntries: Number.POSITIVE_INFINITY,
3082
- onMigrate: () => {
3083
- migratedIntoWriteLayout = true;
3084
- },
3085
- report: journalReport
3086
- };
3087
- for (const layout of layouts) await replayRetryJournal(layout, writeLayout, endpoint, input.clientId, input.clientSecret, request, {
3088
- env,
3089
- fetch: request,
3090
- journalFlushBudgetMs: flushBudgetMs
3091
- }, transportBudgetMs, deadline, deadline, journalDrainOptions);
3092
- const drainLayouts = migratedIntoWriteLayout && !layouts.some((layout) => layoutPath(layout) === layoutPath(writeLayout)) ? [...layouts, writeLayout] : layouts;
3093
- for (const layout of drainLayouts) {
2786
+ for (const layout of layouts) {
3094
2787
  await replayCloseoutSpool(layout, endpoint, input.clientId, input.clientSecret, request, transportBudgetMs, deadline, deadline, flushBudgetMs, void 0, {
3095
2788
  maxEntries: Number.POSITIVE_INFINITY,
3096
2789
  report: spoolReport
@@ -3100,7 +2793,7 @@ async function flushHqSpool(input, dependencies = {}) {
3100
2793
  if ((await settleWithin(lstat(layoutPath(layout)), remainingMs(deadline))).status === "fulfilled") unsecurableSpools += 1;
3101
2794
  } else spools.push(secured.spool);
3102
2795
  }
3103
- const { remaining, unlistableScans } = await countRemainingSpoolWork(spools, drainLayouts, deadline);
2796
+ const { remaining, unlistableScans } = await countRemainingSpoolWork(spools, deadline);
3104
2797
  const outcomes = [...outcomeById.values()];
3105
2798
  const count = (status) => outcomes.filter((outcome) => outcome.status === status).length;
3106
2799
  const rejected = count("rejected");
@@ -12128,10 +11821,10 @@ const describeOrphans = (orphans) => {
12128
11821
  return ` ${orphans.length} spool location(s) hold this repository's evidence under an earlier key it no longer resolves to — a key-format change strands evidence there where no drain looks: ${described}. Drain each with \`psf hq:flush --dir <path>\`.`;
12129
11822
  };
12130
11823
  /**
12131
- * Red when HQ evidence is waiting for this repository: its own spool or legacy
12132
- * journal, or a sibling location written under an earlier key of its own that
12133
- * it no longer resolves to (#420). Another repository's spool under the shared
12134
- * root is that repository's business, never this check's (#446).
11824
+ * Red when HQ evidence is waiting for this repository: its own spool or a
11825
+ * sibling location written under an earlier key of its own that it no longer
11826
+ * resolves to (#420). Another repository's spool under the shared root is that
11827
+ * repository's business, never this check's (#446).
12135
11828
  *
12136
11829
  * Reuses `countHqSpoolWork` (#414) — the same read-only, credential-free
12137
11830
  * inspection `hq:flush` itself consults before ever resolving a secret — so
@@ -12147,13 +11840,10 @@ const describeOrphans = (orphans) => {
12147
11840
  async function hqSpoolDoctorCheck(input, dependencies = {}) {
12148
11841
  const countSpool = dependencies.countSpool ?? countHqSpoolWork;
12149
11842
  const sweepOrphans = dependencies.sweepOrphans ?? sweepHqSpoolOrphans;
12150
- const [counted, swept] = await Promise.all([countSpool({
12151
- cwd: input.cwd,
12152
- repository: input.repository
12153
- }, { env: input.env }), sweepOrphans({ repository: input.repository }, { env: input.env })]);
11843
+ const [counted, swept] = await Promise.all([countSpool({ repository: input.repository }, { env: input.env }), sweepOrphans({ repository: input.repository }, { env: input.env })]);
12154
11844
  const orphanNote = swept.orphans.length === 0 ? "" : describeOrphans(swept.orphans);
12155
11845
  if (counted.pending === 0 && counted.unlistable === 0 && swept.orphans.length === 0) return {
12156
- message: "HQ spool and journal are empty; no evidence is waiting to be drained.",
11846
+ message: "HQ spool is empty; no evidence is waiting to be drained.",
12157
11847
  name: HQ_SPOOL_CHECK_NAME,
12158
11848
  status: "ok"
12159
11849
  };
@@ -12357,7 +12047,6 @@ async function doctorProjectProfile(input = {}) {
12357
12047
  const cwd = input.cwd ?? process.cwd();
12358
12048
  const userConfig = resolveDoctorUserConfig(env, input.userConfig);
12359
12049
  const [hqSpoolCheck, hqRetroReadbackCheck] = await Promise.all([hqSpoolDoctorCheck({
12360
- cwd,
12361
12050
  env,
12362
12051
  repository: {
12363
12052
  owner: profile.repository.owner,
@@ -13035,7 +12724,6 @@ async function runHqFlush(args, dependencies = {}) {
13035
12724
  };
13036
12725
  const explicitDirectories = args.dir && args.dir.length > 0 ? { explicitDirectories: args.dir } : {};
13037
12726
  const pending = await (dependencies.countSpool ?? countHqSpoolWork)({
13038
- cwd: args.cwd,
13039
12727
  ...explicitDirectories,
13040
12728
  repository
13041
12729
  }, { env });
@@ -13080,7 +12768,6 @@ async function runHqFlush(args, dependencies = {}) {
13080
12768
  ...await (dependencies.flush ?? flushHqSpool)({
13081
12769
  clientId: resolution.credentials.clientId,
13082
12770
  clientSecret: resolution.credentials.clientSecret,
13083
- cwd: args.cwd,
13084
12771
  endpoint: profile.hq.endpoint,
13085
12772
  ...explicitDirectories,
13086
12773
  repository
@@ -13107,7 +12794,7 @@ function renderHqFlush(result) {
13107
12794
  `hq:flush ${result.endpoint}`,
13108
12795
  ...result.spools.length === 0 ? ["no spool directory found"] : result.spools.map((spool) => `spool: ${spool}`),
13109
12796
  ...result.outcomes.map(outcomeLine),
13110
- `delivered ${result.delivered}, duplicate ${result.duplicate}, rejected ${result.rejected}, undeliverable ${result.undeliverable}, unreachable ${result.unreachable}, migrated ${result.outcomes.filter((outcome) => outcome.status === "migrated").length}`,
12797
+ `delivered ${result.delivered}, duplicate ${result.duplicate}, rejected ${result.rejected}, undeliverable ${result.undeliverable}, unreachable ${result.unreachable}`,
13111
12798
  ...result.undeliverable > 0 ? [`${result.undeliverable} event(s) can never be delivered and were dispositioned in place, renamed with \`.undeliverable\` and left readable; they no longer count as work waiting.`] : [],
13112
12799
  result.incomplete ? `INCOMPLETE: ${result.remaining} event(s) still spooled; the drain did not finish. Run hq:flush again.` : `spool drained: ${result.remaining} event(s) remain (rejections stay until HQ accepts them)`,
13113
12800
  ...orphanLines(result.orphans)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patronage/software-factory",
3
- "version": "1.0.0-alpha.5",
3
+ "version": "1.0.0-alpha.6",
4
4
  "description": "Shared Patronage software factory CLI and project-profile validation tools",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,7 +37,7 @@
37
37
  "picomatch": "^4.0.5",
38
38
  "yaml": "^2.9.0",
39
39
  "zod": "4.4.3",
40
- "@patronage/factory-ci": "1.0.0-alpha.5"
40
+ "@patronage/factory-ci": "1.0.0-alpha.6"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "24.13.3",
@@ -77,6 +77,8 @@
77
77
  "profile:schema": "tsx scripts/generate-profile-schema.ts",
78
78
  "pretest": "bash ../scripts/ensure-worktree-bootstrap.sh",
79
79
  "test": "vitest run",
80
+ "pretest:profile": "bash ../scripts/ensure-worktree-bootstrap.sh",
81
+ "test:profile": "tsx scripts/vitest-profile.ts",
80
82
  "pretest:stress": "bash ../scripts/ensure-worktree-bootstrap.sh",
81
83
  "test:stress": "node scripts/stress-tests.mjs",
82
84
  "pretypecheck": "bash ../scripts/ensure-worktree-bootstrap.sh",