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

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 CHANGED
@@ -37,7 +37,7 @@ A stacked release train has one terminal release-owning slice. Interior slices d
37
37
  A stable cut has two phases. Phase 1 is the verified package candidate: steps 1 through 4 below, ending in the attended npm publish, tag, and GitHub release. Phase 2 is post-publication stable-doc convergence: steps 5 and 6, a normal admitted docs PR followed by the read-only convergence assertion from a clean `main`. Stable docs must never claim an unpublished version, so they cannot land in the phase-1 change. Prerelease behavior is unchanged: prerelease notes may land before publish while stable docs keep naming the prior stable version.
38
38
 
39
39
  1. Bump `package.json` to valid SemVer. The assertion derives its expected stable or prerelease channel from the version; it accepts no channel flag.
40
- 2. Publish to npm per the [repository operations runbook](../docs/repository-operations.md#publishing) (attended, hand-cut, `pnpm publish` only). Both factory packages pin `publishConfig.tag: next`, so a bare `pnpm --filter @patronage/software-factory publish` lands on `next` and cannot move `latest`. A prerelease is therefore the bare command; a stable version must say it out loud: `pnpm --filter @patronage/software-factory publish --tag latest`. Forgetting the flag on a stable cut leaves `latest` untouched for the convergence assertion to flag the safe failure direction.
40
+ 2. Publish to npm per the [repository operations runbook](../docs/repository-operations.md#publishing) (attended, hand-cut, `pnpm publish` only). **Always pass `--tag` explicitly, on every publish, prerelease or stable** — `pnpm --filter @patronage/software-factory publish --tag next` for a prerelease, `--tag latest` for a stable cut. A filtered publish takes pnpm's recursive path, which passes its own `--tag` (defaulting to `latest`) to npm and therefore ignores the `publishConfig.tag: next` pin both factory packages carry; that is how 1.0.0-alpha.6 briefly took `latest` (#658). The pins stay as belt and braces, but they are not the control. Omitting the flag is not a safe failure: on the filtered command it moves `latest`, which the convergence assertion can only report after the fact.
41
41
  3. Cut the `software-factory-vX.Y.Z` tag and push it.
42
42
  4. Cut the GitHub release for that tag, with notes covering features, deletions, breaking changes, and closed issues. Mark it as a prerelease exactly when its SemVer version has a prerelease component. When the release changes the profile `schemaVersion`, state it in the notes — the profile schema version is a separate axis from the package version.
43
43
  5. Converge the stable docs after publication. Update affected `software-factory-docs/` pages through a normal admitted docs PR. Stable docs must state the current stable version. Prerelease docs may record the candidate and its migration notes, but must not represent it as the current stable release.
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;
@@ -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.7";
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");
@@ -8860,8 +8553,24 @@ function positiveInteger(name) {
8860
8553
  };
8861
8554
  }
8862
8555
  function resolveCwdOption(cwd) {
8863
- if (typeof cwd !== "string" || cwd.trim() === "") throw new Error("--cwd requires a non-empty path; relative values resolve against the current working directory");
8864
- return path.resolve(cwd);
8556
+ const value = typeof cwd === "string" ? cwd : ".";
8557
+ if (value.trim() === "") throw new Error("--cwd requires a non-empty path; relative values resolve against the current working directory");
8558
+ return path.resolve(value);
8559
+ }
8560
+ function collectCwdOption(rawValue, previous) {
8561
+ if (previous === void 0) return rawValue;
8562
+ const previousResolved = resolveCwdOption(previous);
8563
+ const currentResolved = resolveCwdOption(rawValue);
8564
+ if (previousResolved !== currentResolved) throw new Error(`--cwd was passed multiple times with conflicting values: "${previous}" (resolves to ${previousResolved}) and "${rawValue}" (resolves to ${currentResolved})`);
8565
+ return rawValue;
8566
+ }
8567
+ function markCwdOptionDefault(command) {
8568
+ const option = command.options.find((candidate) => candidate.attributeName() === "cwd");
8569
+ if (option) {
8570
+ option.defaultValue = ".";
8571
+ option.defaultValueDescription = ".";
8572
+ }
8573
+ return command;
8865
8574
  }
8866
8575
  function resolveCheckoutPath(cwd, target) {
8867
8576
  return path.resolve(cwd, target);
@@ -8884,7 +8593,7 @@ const renderReport = (record) => {
8884
8593
  return `${lines.join("\n")}\n`;
8885
8594
  };
8886
8595
  function createBoundaryCheckCommand(output, action) {
8887
- return new Command("boundary:check").description("Boundary readiness gate (T8): require a membership-fresh boundary-review proof at the declared closeout rung. Blocks closeout/enablement, never merge.").requiredOption("--epic <number>", "epic issue number carrying the factory-boundary manifest", positiveInteger("--epic")).option("--repo <owner/name>", "GitHub repository (default: resolved from --cwd)").option("--cwd <path>", "repository working directory", ".").option("--output <path>", "write an additional proof copy to this path").option("--profile <path>", "path to the project profile JSON file").option("--json", "print the full proof record as JSON").action(withGateTiming({
8596
+ return markCwdOptionDefault(new Command("boundary:check").description("Boundary readiness gate (T8): require a membership-fresh boundary-review proof at the declared closeout rung. Blocks closeout/enablement, never merge.").requiredOption("--epic <number>", "epic issue number carrying the factory-boundary manifest", positiveInteger("--epic")).option("--repo <owner/name>", "GitHub repository (default: resolved from --cwd)").option("--cwd <path>", "repository working directory", collectCwdOption).option("--output <path>", "write an additional proof copy to this path").option("--profile <path>", "path to the project profile JSON file").option("--json", "print the full proof record as JSON").action(withGateTiming({
8888
8597
  gate: "boundary:check",
8889
8598
  resolveLedgerRoot: (options) => resolveCwdOption(options.cwd),
8890
8599
  stderr: output.stderr
@@ -8923,7 +8632,7 @@ function createBoundaryCheckCommand(output, action) {
8923
8632
  }
8924
8633
  output.stdout.write(options.json ? `${JSON.stringify(record, null, 2)}\n` : renderReport(record));
8925
8634
  if (record.status !== "ready") throw new Error(`boundary:check refused: ${record.blockingReasons.join("; ")}`);
8926
- }));
8635
+ })));
8927
8636
  }
8928
8637
  //#endregion
8929
8638
  //#region src/checkout-repository.ts
@@ -11526,7 +11235,7 @@ const runDemandWaive = (args, dependencies = {}) => {
11526
11235
  * offers no way to declare a demand met, only to waive it on the record.
11527
11236
  */
11528
11237
  function createDemandWaiveCommand(_output, action = runDemandWaive) {
11529
- return new Command("demand:waive").description("Waive one resolved demand for one candidate, on the operator's identity, with the rationale recorded in the proof").requiredOption("--pr <number>", "pull request number", positiveInteger("--pr")).requiredOption("--demand <key>", "resolved demand key, e.g. review-rung:human, required-check:core, merge-freeze").requiredOption("--rationale <text>", "why this demand is being waived").option("--cwd <path>", "working directory to evaluate", ".").option("--json", "print the recorded waiver as JSON").option("--output <path>", "waiver record JSON path").action((options) => {
11238
+ return markCwdOptionDefault(new Command("demand:waive").description("Waive one resolved demand for one candidate, on the operator's identity, with the rationale recorded in the proof").requiredOption("--pr <number>", "pull request number", positiveInteger("--pr")).requiredOption("--demand <key>", "resolved demand key, e.g. review-rung:human, required-check:core, merge-freeze").requiredOption("--rationale <text>", "why this demand is being waived").option("--cwd <path>", "working directory to evaluate", collectCwdOption).option("--json", "print the recorded waiver as JSON").option("--output <path>", "waiver record JSON path").action((options) => {
11530
11239
  action({
11531
11240
  cwd: resolveCwdOption(options.cwd),
11532
11241
  demand: options.demand,
@@ -11535,7 +11244,7 @@ function createDemandWaiveCommand(_output, action = runDemandWaive) {
11535
11244
  pr: options.pr,
11536
11245
  rationale: options.rationale
11537
11246
  });
11538
- });
11247
+ }));
11539
11248
  }
11540
11249
  //#endregion
11541
11250
  //#region src/demand-resolution.ts
@@ -12128,10 +11837,10 @@ const describeOrphans = (orphans) => {
12128
11837
  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
11838
  };
12130
11839
  /**
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).
11840
+ * Warns when HQ evidence is waiting for this repository: its own spool or a
11841
+ * sibling location written under an earlier key of its own that it no longer
11842
+ * resolves to (#420). Another repository's spool under the shared root is that
11843
+ * repository's business, never this check's (#446).
12135
11844
  *
12136
11845
  * Reuses `countHqSpoolWork` (#414) — the same read-only, credential-free
12137
11846
  * inspection `hq:flush` itself consults before ever resolving a secret — so
@@ -12141,19 +11850,23 @@ const describeOrphans = (orphans) => {
12141
11850
  * the spool resolve exactly one key, so evidence written under an older one is
12142
11851
  * invisible to a drain and, before this, to doctor: the failure epic #389 was
12143
11852
  * chartered to end is evidence that is neither delivered nor visibly stranded.
12144
- * An orphan holding pending events is red for the same reason the repo-keyed
12145
- * spool is.
11853
+ * An orphan holding pending events warns for the same reason the repo-keyed
11854
+ * spool does.
11855
+ *
11856
+ * `warning`, not `error` (#659, epic #663 wave 1): a spooled event is real
11857
+ * HQ-delivery lag, but doctor is a read-only diagnostic that must never gain a
11858
+ * flush side-effect, and a preflight gate that treats doctor's exit code as
11859
+ * the bar must not block admission on delivery lag it cannot fix from here.
11860
+ * `psf hq:flush` — a trusted local session, not doctor — is still how the
11861
+ * spool actually drains.
12146
11862
  */
12147
11863
  async function hqSpoolDoctorCheck(input, dependencies = {}) {
12148
11864
  const countSpool = dependencies.countSpool ?? countHqSpoolWork;
12149
11865
  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 })]);
11866
+ const [counted, swept] = await Promise.all([countSpool({ repository: input.repository }, { env: input.env }), sweepOrphans({ repository: input.repository }, { env: input.env })]);
12154
11867
  const orphanNote = swept.orphans.length === 0 ? "" : describeOrphans(swept.orphans);
12155
11868
  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.",
11869
+ message: "HQ spool is empty; no evidence is waiting to be drained.",
12157
11870
  name: HQ_SPOOL_CHECK_NAME,
12158
11871
  status: "ok"
12159
11872
  };
@@ -12162,17 +11875,17 @@ async function hqSpoolDoctorCheck(input, dependencies = {}) {
12162
11875
  if (counted.pending > 0) return {
12163
11876
  message: `${counted.pending} HQ event(s) are spooled locally.${oldestNote}${locationsNote} ${FLUSH_REMEDY}${orphanNote}`,
12164
11877
  name: HQ_SPOOL_CHECK_NAME,
12165
- status: "error"
11878
+ status: "warning"
12166
11879
  };
12167
11880
  if (counted.unlistable > 0) return {
12168
11881
  message: `${counted.unlistable} spool location(s) exist but could not be listed within budget, so this cannot be reported as empty.${locationsNote} ${FLUSH_REMEDY}${orphanNote}`,
12169
11882
  name: HQ_SPOOL_CHECK_NAME,
12170
- status: "error"
11883
+ status: "warning"
12171
11884
  };
12172
11885
  return {
12173
11886
  message: `This repository's HQ spool is empty, but stranded evidence is waiting under the spool root ${swept.root}.${orphanNote}`,
12174
11887
  name: HQ_SPOOL_CHECK_NAME,
12175
- status: "error"
11888
+ status: "warning"
12176
11889
  };
12177
11890
  }
12178
11891
  const readJsonFiles = async (dir, readFileImpl) => {
@@ -12357,7 +12070,6 @@ async function doctorProjectProfile(input = {}) {
12357
12070
  const cwd = input.cwd ?? process.cwd();
12358
12071
  const userConfig = resolveDoctorUserConfig(env, input.userConfig);
12359
12072
  const [hqSpoolCheck, hqRetroReadbackCheck] = await Promise.all([hqSpoolDoctorCheck({
12360
- cwd,
12361
12073
  env,
12362
12074
  repository: {
12363
12075
  owner: profile.repository.owner,
@@ -12524,7 +12236,7 @@ function repositoryUrlMatches(origin, profile) {
12524
12236
  //#endregion
12525
12237
  //#region src/commands/doctor.ts
12526
12238
  function createDoctorCommand(output) {
12527
- return new Command("doctor").description("Validate a project profile without mutating GitHub or local files").option("--base <ref>", "base branch or ref for --preflight", "origin/main").option("--cwd <path>", "working directory to validate", ".").option("--json", "print the doctor report as JSON").option("--preflight", "also list the pre-checkable admission requirements for the current candidate (read-only; never blocks); findings-file shape and wave membership have no read-only pre-check").option("--profile <path>", "path to the project profile JSON file").action(async (options) => {
12239
+ return markCwdOptionDefault(new Command("doctor").description("Validate a project profile without mutating GitHub or local files").option("--base <ref>", "base branch or ref for --preflight", "origin/main").option("--cwd <path>", "working directory to validate", collectCwdOption).option("--json", "print the doctor report as JSON").option("--preflight", "also list the pre-checkable admission requirements for the current candidate (read-only; never blocks); findings-file shape and wave membership have no read-only pre-check").option("--profile <path>", "path to the project profile JSON file").action(async (options) => {
12528
12240
  const report = await doctorProjectProfile({
12529
12241
  base: options.base,
12530
12242
  cwd: resolveCwdOption(options.cwd),
@@ -12534,7 +12246,7 @@ function createDoctorCommand(output) {
12534
12246
  if (options.json) output.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
12535
12247
  else output.stdout.write(formatHumanReport(report));
12536
12248
  if (!report.ok) process.exitCode = 1;
12537
- });
12249
+ }));
12538
12250
  }
12539
12251
  function formatHumanReport(report) {
12540
12252
  return [
@@ -12942,7 +12654,7 @@ function optionalString(key, flagValue, stdinValue) {
12942
12654
  return resolved ? { [key]: resolved } : {};
12943
12655
  }
12944
12656
  function createEvidenceEmitCommand(output, action = runEvidenceEmit, readInput = readStdin) {
12945
- return new Command("evidence:emit").description("Build, validate, and write an external-evidence envelope (ADR 0014)").option("--cwd <path>", "repository working directory", ".").option("--base <ref>", "base branch or ref for the three-way binding", "origin/main").option("--check <name>", "declared requiredChecks name this evidence satisfies").option("--outcome <pass|fail>", "the check outcome").option("--producer <id>", "the producing identity (recorded, not gated)").option("--policy-version <version>", "canonical prompt / Warden rules version").option("--session-id <id>", "producer session id (independence join key)").option("--request-id <id>", "producer request id (join key)").option("--findings-pointer <ref>", "pointer to findings (never embedded)").option("--profile <path>", "path to the project profile JSON file (accepted for a uniform consumer-wrapper flag set and loaded strictly when supplied)").option("--output <path>", "write the envelope to a specific path").option("--stdin", "merge JSON field values from stdin (flags win)").option("--json", "print the written envelope as JSON").action((options) => {
12657
+ return markCwdOptionDefault(new Command("evidence:emit").description("Build, validate, and write an external-evidence envelope (ADR 0014)").option("--cwd <path>", "repository working directory", collectCwdOption).option("--base <ref>", "base branch or ref for the three-way binding", "origin/main").option("--check <name>", "declared requiredChecks name this evidence satisfies").option("--outcome <pass|fail>", "the check outcome").option("--producer <id>", "the producing identity (recorded, not gated)").option("--policy-version <version>", "canonical prompt / Warden rules version").option("--session-id <id>", "producer session id (independence join key)").option("--request-id <id>", "producer request id (join key)").option("--findings-pointer <ref>", "pointer to findings (never embedded)").option("--profile <path>", "path to the project profile JSON file (accepted for a uniform consumer-wrapper flag set and loaded strictly when supplied)").option("--output <path>", "write the envelope to a specific path").option("--stdin", "merge JSON field values from stdin (flags win)").option("--json", "print the written envelope as JSON").action((options) => {
12946
12658
  const cwd = resolveCwdOption(options.cwd);
12947
12659
  const fromStdin = options.stdin ? readInput() : {};
12948
12660
  rejectRetiredStdinFields(fromStdin);
@@ -12969,7 +12681,7 @@ function createEvidenceEmitCommand(output, action = runEvidenceEmit, readInput =
12969
12681
  });
12970
12682
  output.stdout.write(`evidence:emit wrote envelope for "${result.envelope.check}" to ${result.path}\n`);
12971
12683
  if (options.json && "envelope" in result) output.stdout.write(`${JSON.stringify(result.envelope, null, 2)}\n`);
12972
- });
12684
+ }));
12973
12685
  }
12974
12686
  //#endregion
12975
12687
  //#region src/hq-flush.ts
@@ -13035,7 +12747,6 @@ async function runHqFlush(args, dependencies = {}) {
13035
12747
  };
13036
12748
  const explicitDirectories = args.dir && args.dir.length > 0 ? { explicitDirectories: args.dir } : {};
13037
12749
  const pending = await (dependencies.countSpool ?? countHqSpoolWork)({
13038
- cwd: args.cwd,
13039
12750
  ...explicitDirectories,
13040
12751
  repository
13041
12752
  }, { env });
@@ -13080,7 +12791,6 @@ async function runHqFlush(args, dependencies = {}) {
13080
12791
  ...await (dependencies.flush ?? flushHqSpool)({
13081
12792
  clientId: resolution.credentials.clientId,
13082
12793
  clientSecret: resolution.credentials.clientSecret,
13083
- cwd: args.cwd,
13084
12794
  endpoint: profile.hq.endpoint,
13085
12795
  ...explicitDirectories,
13086
12796
  repository
@@ -13107,7 +12817,7 @@ function renderHqFlush(result) {
13107
12817
  `hq:flush ${result.endpoint}`,
13108
12818
  ...result.spools.length === 0 ? ["no spool directory found"] : result.spools.map((spool) => `spool: ${spool}`),
13109
12819
  ...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}`,
12820
+ `delivered ${result.delivered}, duplicate ${result.duplicate}, rejected ${result.rejected}, undeliverable ${result.undeliverable}, unreachable ${result.unreachable}`,
13111
12821
  ...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
12822
  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
12823
  ...orphanLines(result.orphans)
@@ -13134,7 +12844,7 @@ const hqFlushExitCode = (result) => {
13134
12844
  //#endregion
13135
12845
  //#region src/commands/hq-flush.ts
13136
12846
  function createHqFlushCommand(output, action = runHqFlush) {
13137
- return new Command("hq:flush").description("Drain this repository's spooled HQ evidence and report every event; exits 1 when transport failed and 2 when the drain did not finish").option("--cwd <path>", "working directory to evaluate", ".").option("--dir <path>", "drain an explicit spool directory instead of the default locations; repeatable", (value, previous = []) => [...previous, value]).option("--json", "print the flush result as JSON").option("--profile <path>", "path to the project profile JSON file").action(async (options) => {
12847
+ return markCwdOptionDefault(new Command("hq:flush").description("Drain this repository's spooled HQ evidence and report every event; exits 1 when transport failed and 2 when the drain did not finish").option("--cwd <path>", "working directory to evaluate", collectCwdOption).option("--dir <path>", "drain an explicit spool directory instead of the default locations; repeatable", (value, previous = []) => [...previous, value]).option("--json", "print the flush result as JSON").option("--profile <path>", "path to the project profile JSON file").action(async (options) => {
13138
12848
  const result = await action({
13139
12849
  cwd: resolveCwdOption(options.cwd),
13140
12850
  ...options.dir ? { dir: options.dir } : {},
@@ -13144,7 +12854,7 @@ function createHqFlushCommand(output, action = runHqFlush) {
13144
12854
  output.stdout.write(options.json ? `${JSON.stringify(result, null, 2)}\n` : renderHqFlush(result));
13145
12855
  const exitCode = hqFlushExitCode(result);
13146
12856
  if (exitCode !== 0) process.exitCode = exitCode;
13147
- });
12857
+ }));
13148
12858
  }
13149
12859
  //#endregion
13150
12860
  //#region src/follow-up.ts
@@ -16007,7 +15717,7 @@ async function deliverSpooledEvidence({ awaitPending, cwd, flush, profilePath })
16007
15717
  //#endregion
16008
15718
  //#region src/commands/pr-publish.ts
16009
15719
  function createPrPublishCommand(_output, action = runPrPublish) {
16010
- return new Command("pr:publish").description("Compose supplied proof into a ready-for-human handoff; never launches review").option("--pr <number>", "pull request number", positiveInteger("--pr")).option("--base <ref>", "explicit base branch or ref override").option("--authoring-session <id>", "known authoring session identity for verification retries").option("--cwd <path>", "working directory to evaluate", ".").option("--epic <number>", "epic issue containing the factory-boundary manifest; the composed readiness evaluation then applies the wave-demanded review rung (#351)", positiveInteger("--epic")).option("--findings <path>", "clean-session findings to validate when no current review proof is available").option("--json", "print the publish result as JSON").option("--output <path>", "write readiness proof JSON to a file").option("--profile <path>", "path to the project profile JSON file").option("--review-proof <path>", "pr:review proof JSON path").option("--verify-proof <path>", "pr:verify proof JSON path").action(async (options) => {
15720
+ return markCwdOptionDefault(new Command("pr:publish").description("Compose supplied proof into a ready-for-human handoff; never launches review").option("--pr <number>", "pull request number", positiveInteger("--pr")).option("--base <ref>", "explicit base branch or ref override").option("--authoring-session <id>", "known authoring session identity for verification retries").option("--cwd <path>", "working directory to evaluate", collectCwdOption).option("--epic <number>", "epic issue containing the factory-boundary manifest; the composed readiness evaluation then applies the wave-demanded review rung (#351)", positiveInteger("--epic")).option("--findings <path>", "clean-session findings to validate when no current review proof is available").option("--json", "print the publish result as JSON").option("--output <path>", "write readiness proof JSON to a file").option("--profile <path>", "path to the project profile JSON file").option("--review-proof <path>", "pr:review proof JSON path").option("--verify-proof <path>", "pr:verify proof JSON path").action(async (options) => {
16011
15721
  await action({
16012
15722
  authoringSessionIds: options.authoringSession ? [options.authoringSession] : void 0,
16013
15723
  base: options.base ?? "",
@@ -16021,12 +15731,12 @@ function createPrPublishCommand(_output, action = runPrPublish) {
16021
15731
  reviewProof: options.reviewProof,
16022
15732
  verifyProof: options.verifyProof
16023
15733
  });
16024
- });
15734
+ }));
16025
15735
  }
16026
15736
  //#endregion
16027
15737
  //#region src/commands/pr-ready.ts
16028
15738
  function createPrReadyCommand(output, action) {
16029
- return new Command("pr:ready").description("Evaluate typed proof and GitHub state for final readiness").requiredOption("--pr <number>", "pull request number", positiveInteger("--pr")).option("--base <ref>", "explicit base branch or ref override").option("--authoring-session <id...>", "recorded authoring session id(s) for review-type requiredCheck independence (ADR 0014 §5)").option("--cwd <path>", "working directory to evaluate", ".").option("--epic <number>", "epic issue containing the factory-boundary manifest; readiness then evaluates the wave-demanded review rung (#351)", positiveInteger("--epic")).option("--json", "print the readiness proof as JSON").option("--output <path>", "write proof JSON to a file").option("--profile <path>", "path to the project profile JSON file").option("--review-proof <path>", "pr:review proof JSON path").option("--verify-proof <path>", "pr:verify proof JSON path").action(withGateTiming({
15739
+ return markCwdOptionDefault(new Command("pr:ready").description("Evaluate typed proof and GitHub state for final readiness").requiredOption("--pr <number>", "pull request number", positiveInteger("--pr")).option("--base <ref>", "explicit base branch or ref override").option("--authoring-session <id...>", "recorded authoring session id(s) for review-type requiredCheck independence (ADR 0014 §5)").option("--cwd <path>", "working directory to evaluate", collectCwdOption).option("--epic <number>", "epic issue containing the factory-boundary manifest; readiness then evaluates the wave-demanded review rung (#351)", positiveInteger("--epic")).option("--json", "print the readiness proof as JSON").option("--output <path>", "write proof JSON to a file").option("--profile <path>", "path to the project profile JSON file").option("--review-proof <path>", "pr:review proof JSON path").option("--verify-proof <path>", "pr:verify proof JSON path").action(withGateTiming({
16030
15740
  gate: "pr:ready",
16031
15741
  resolveLedgerRoot: (options) => resolveCwdOption(options.cwd),
16032
15742
  stderr: output.stderr
@@ -16044,7 +15754,7 @@ function createPrReadyCommand(output, action) {
16044
15754
  verifyProof: options.verifyProof
16045
15755
  };
16046
15756
  await (action ? action(args) : runPrReady(args));
16047
- }));
15757
+ })));
16048
15758
  }
16049
15759
  //#endregion
16050
15760
  //#region src/gate-foreground-guard.ts
@@ -16073,7 +15783,7 @@ function assertForegroundGate({ gate, env = process.env }) {
16073
15783
  //#endregion
16074
15784
  //#region src/commands/pr-review.ts
16075
15785
  function createPrReviewCommand(output, action) {
16076
- return new Command("pr:review").description("Validate clean-session findings and write typed review proof").option("--base <ref>", "base branch or ref", "origin/main").option("--cwd <path>", "working directory to review", ".").option("--cycle <number>", "review cycle number", positiveInteger("--cycle"), 1).option("--mode <mode>", "correctness, security, or all", "all").option("--findings <path>", "typed clean-session findings JSON file").option("--dispositions <path>", "JSON file of ladder disposition declarations (waived / follow-up-filed / fixed-in-thread) for previously flagged findings").option("--issue <number>", "issue number recorded on the review ladder trace", positiveInteger("--issue")).option("--output <path>", "write proof JSON to a file").option("--profile <path>", "path to the project profile JSON file").option("--verify-proof <path>", "pr:verify proof JSON path").action(withGateTiming({
15786
+ return markCwdOptionDefault(new Command("pr:review").description("Validate clean-session findings and write typed review proof").option("--base <ref>", "base branch or ref", "origin/main").option("--cwd <path>", "working directory to review", collectCwdOption).option("--cycle <number>", "review cycle number", positiveInteger("--cycle"), 1).option("--mode <mode>", "correctness, security, or all", "all").option("--findings <path>", "typed clean-session findings JSON file").option("--dispositions <path>", "JSON file of ladder disposition declarations (waived / follow-up-filed / fixed-in-thread) for previously flagged findings").option("--issue <number>", "issue number recorded on the review ladder trace", positiveInteger("--issue")).option("--output <path>", "write proof JSON to a file").option("--profile <path>", "path to the project profile JSON file").option("--verify-proof <path>", "pr:verify proof JSON path").action(withGateTiming({
16077
15787
  gate: "pr:review",
16078
15788
  resolveCycle: (options) => options.cycle,
16079
15789
  resolveLedgerRoot: (options) => resolveCwdOption(options.cwd),
@@ -16098,12 +15808,12 @@ function createPrReviewCommand(output, action) {
16098
15808
  verifyProof: options.verifyProof
16099
15809
  };
16100
15810
  await (action ? action(args) : runPrReview(args, { publishCheckRun: createFactoryCheckPublisher(output) }));
16101
- }));
15811
+ })));
16102
15812
  }
16103
15813
  //#endregion
16104
15814
  //#region src/commands/pr-verify.ts
16105
15815
  function createPrVerifyCommand(output, action = runPrVerify) {
16106
- return new Command("pr:verify").description("Run project-profile verification commands and write typed proof").option("--base <ref>", "base branch or ref", "origin/main").option("--cwd <path>", "working directory to verify", ".").option("--docs-only", "run the docs/process verification gate").option("--trivial", "run the trivial verification gate").option("--full", "run the full verification gate").option("--authoring-session <id>", "explicit authoring session identity (required for review evidence)").option("--json", "print the proof as JSON after verification").option("--output <path>", "write proof JSON to a file").option("--profile <path>", "path to the project profile JSON file").option("--no-status", "skip posting the patronage-factory/pr-verify commit status").action(withGateTiming({
15816
+ return markCwdOptionDefault(new Command("pr:verify").description("Run project-profile verification commands and write typed proof").option("--base <ref>", "base branch or ref", "origin/main").option("--cwd <path>", "working directory to verify", collectCwdOption).option("--docs-only", "run the docs/process verification gate").option("--trivial", "run the trivial verification gate").option("--full", "run the full verification gate").option("--authoring-session <id>", "explicit authoring session identity (required for review evidence)").option("--json", "print the proof as JSON after verification").option("--output <path>", "write proof JSON to a file").option("--profile <path>", "path to the project profile JSON file").option("--no-status", "skip posting the patronage-factory/pr-verify commit status").action(withGateTiming({
16107
15817
  gate: "pr:verify",
16108
15818
  resolveLedgerRoot: (options) => resolveCwdOption(options.cwd),
16109
15819
  stderr: output.stderr
@@ -16125,7 +15835,7 @@ function createPrVerifyCommand(output, action = runPrVerify) {
16125
15835
  requireKnownAuthoringSession: true
16126
15836
  }, dependencies);
16127
15837
  if (options.json) output.stdout.write(`${JSON.stringify(proof, null, 2)}\n`);
16128
- }));
15838
+ })));
16129
15839
  }
16130
15840
  function modeFor(options) {
16131
15841
  if (options.docsOnly) return "docs-only";
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.7",
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.7"
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",