halfcycle 0.4.6 → 0.4.8

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.
@@ -2266,8 +2266,8 @@ var Doc = class {
2266
2266
  const lines = content.split("\n").filter((x) => x);
2267
2267
  const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length));
2268
2268
  const dedented = lines.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x);
2269
- for (const line of dedented) {
2270
- this.content.push(line);
2269
+ for (const line2 of dedented) {
2270
+ this.content.push(line2);
2271
2271
  }
2272
2272
  }
2273
2273
  compile() {
@@ -14572,14 +14572,14 @@ function unquote(value) {
14572
14572
  }
14573
14573
  function parseEnvText(raw) {
14574
14574
  const out = {};
14575
- for (const line of raw.split("\n")) {
14576
- const eq = line.indexOf("=");
14575
+ for (const line2 of raw.split("\n")) {
14576
+ const eq = line2.indexOf("=");
14577
14577
  if (eq === -1)
14578
14578
  continue;
14579
- const key = line.slice(0, eq).replace(/^\s*export\s+/, "").trim();
14579
+ const key = line2.slice(0, eq).replace(/^\s*export\s+/, "").trim();
14580
14580
  if (key === "" || key.startsWith("#"))
14581
14581
  continue;
14582
- out[key] = unquote(line.slice(eq + 1));
14582
+ out[key] = unquote(line2.slice(eq + 1));
14583
14583
  }
14584
14584
  return out;
14585
14585
  }
@@ -14588,13 +14588,13 @@ function reconcileEnvText(existing, values, keys) {
14588
14588
  const value = values[k];
14589
14589
  return [k, value === void 0 ? "" : value];
14590
14590
  }));
14591
- const line = (k) => `${k}=${shq(desired.get(k) ?? "")}`;
14591
+ const line2 = (k) => `${k}=${shq(desired.get(k) ?? "")}`;
14592
14592
  const written = keys.filter((k) => desired.get(k) !== null);
14593
14593
  if (existing === null) {
14594
14594
  if (written.length === 0)
14595
14595
  return "";
14596
14596
  return `${ENGAGEMENT_ENV_HEADER}
14597
- ` + written.map(line).join("\n") + "\n";
14597
+ ` + written.map(line2).join("\n") + "\n";
14598
14598
  }
14599
14599
  const seen = /* @__PURE__ */ new Set();
14600
14600
  const out = [];
@@ -14615,13 +14615,13 @@ function reconcileEnvText(existing, values, keys) {
14615
14615
  seen.add(key);
14616
14616
  if (desired.get(key) === null)
14617
14617
  continue;
14618
- out.push(`${prefix}${line(key)}`);
14618
+ out.push(`${prefix}${line2(key)}`);
14619
14619
  }
14620
14620
  const missing = written.filter((k) => !seen.has(k));
14621
14621
  if (missing.length > 0) {
14622
14622
  const header = existing.includes(ENGAGEMENT_ENV_HEADER) ? "" : `${ENGAGEMENT_ENV_HEADER}
14623
14623
  `;
14624
- const block = header + missing.map(line).join("\n");
14624
+ const block = header + missing.map(line2).join("\n");
14625
14625
  const trailingBlank = out.length > 0 && out[out.length - 1] === "";
14626
14626
  if (trailingBlank)
14627
14627
  out.splice(out.length - 1, 0, block);
@@ -14662,6 +14662,7 @@ var VENDORED_BIN_REL = ".halfcycle/bin/bin.bundle.mjs";
14662
14662
  var SESSION_START_MARKER_REL = ".claude/hooks/session-start-marker.sh";
14663
14663
  var GUARD_RUNNER_REL = ".claude/hooks/guard-runner.sh";
14664
14664
  var USER_PROMPT_REMINDER_REL = ".claude/hooks/user-prompt-reminder.sh";
14665
+ var USER_PROMPT_SCOPE_REL = ".claude/hooks/user-prompt-scope.sh";
14665
14666
  var SESSION_END_MARKER_REL = ".claude/hooks/session-end-marker.sh";
14666
14667
  var WRITE_CAPABLE_TOOLS = ["Edit", "MultiEdit", "NotebookEdit", "Write"];
14667
14668
  function hookCommand(script, verb) {
@@ -14690,6 +14691,13 @@ var HALFCYCLE_HOOK_WIRING = [
14690
14691
  command: hookCommand(USER_PROMPT_REMINDER_REL),
14691
14692
  script: USER_PROMPT_REMINDER_REL
14692
14693
  },
14694
+ {
14695
+ event: "UserPromptSubmit",
14696
+ matcher: null,
14697
+ timeout: 10,
14698
+ command: hookCommand(USER_PROMPT_SCOPE_REL),
14699
+ script: USER_PROMPT_SCOPE_REL
14700
+ },
14693
14701
  {
14694
14702
  event: "PostToolUse",
14695
14703
  matcher: WRITE_CAPABLE_TOOLS.join("|"),
@@ -14765,6 +14773,98 @@ function looksLikeSecret(text) {
14765
14773
  var MCP_REGISTRATION_REL = ".mcp.json";
14766
14774
  var MCP_SERVER_KEY = "halfcycle";
14767
14775
 
14776
+ // ../core/dist/shell-quote.js
14777
+ function quoteForShell(value) {
14778
+ return `'${value.replace(/'/g, `'\\''`)}'`;
14779
+ }
14780
+ var HALFCYCLE_COMMAND = "npx halfcycle@latest";
14781
+
14782
+ // ../core/dist/shown-plans.js
14783
+ var SHOWN_PLANS_FILENAME = "shown-plans.json";
14784
+ var SHOWN_PLANS_FORMAT = 1;
14785
+ var RECORD_KEYS = ["entries", "format"];
14786
+ var ENTRY_KEYS = ["number", "phase", "shownAt", "version"];
14787
+ function parseShownPlans(text) {
14788
+ try {
14789
+ if (typeof text !== "string")
14790
+ return [];
14791
+ const parsed = JSON.parse(text);
14792
+ if (!isPlainObject2(parsed) || !hasExactKeys(parsed, RECORD_KEYS))
14793
+ return [];
14794
+ if (parsed["format"] !== SHOWN_PLANS_FORMAT)
14795
+ return [];
14796
+ const rawEntries = parsed["entries"];
14797
+ if (!Array.isArray(rawEntries))
14798
+ return [];
14799
+ const entries = [];
14800
+ for (const raw of rawEntries) {
14801
+ const entry = readEntry(raw);
14802
+ if (entry === null)
14803
+ return [];
14804
+ entries.push(entry);
14805
+ }
14806
+ return hasDuplicates(entries) ? [] : entries;
14807
+ } catch {
14808
+ return [];
14809
+ }
14810
+ }
14811
+ function readEntry(raw) {
14812
+ if (!isPlainObject2(raw) || !hasExactKeys(raw, ENTRY_KEYS))
14813
+ return null;
14814
+ const phase = raw["phase"];
14815
+ const number4 = raw["number"];
14816
+ const version2 = raw["version"];
14817
+ const shownAt = raw["shownAt"];
14818
+ if (typeof phase !== "string" || phase === "")
14819
+ return null;
14820
+ if (!isPositiveInteger(number4) || !isPositiveInteger(version2))
14821
+ return null;
14822
+ if (!isIsoInstant(shownAt))
14823
+ return null;
14824
+ return { phase, number: number4, version: version2, shownAt };
14825
+ }
14826
+ function isPlainObject2(value) {
14827
+ if (typeof value !== "object" || value === null || Array.isArray(value))
14828
+ return false;
14829
+ const proto = Object.getPrototypeOf(value);
14830
+ return proto === Object.prototype || proto === null;
14831
+ }
14832
+ function hasExactKeys(value, keys) {
14833
+ const own = Object.keys(value).sort();
14834
+ return own.length === keys.length && own.every((key, i) => key === keys[i]);
14835
+ }
14836
+ function isPositiveInteger(value) {
14837
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
14838
+ }
14839
+ function isIsoInstant(value) {
14840
+ if (typeof value !== "string")
14841
+ return false;
14842
+ const ms = Date.parse(value);
14843
+ return Number.isFinite(ms) && new Date(ms).toISOString() === value;
14844
+ }
14845
+ function hasDuplicates(entries) {
14846
+ const phases = new Set(entries.map((entry) => entry.phase));
14847
+ const numbers = new Set(entries.map((entry) => entry.number));
14848
+ return phases.size !== entries.length || numbers.size !== entries.length;
14849
+ }
14850
+
14851
+ // ../core/dist/phase-reference.js
14852
+ var TYPED_NUMBER = /^(?:0|[1-9][0-9]*)$/;
14853
+ function resolvePhaseReference(typed, phases) {
14854
+ if (typeof typed !== "string" || typed === "")
14855
+ return null;
14856
+ const byIdentity = phases.filter((entry) => entry.phase === typed);
14857
+ if (byIdentity.length > 0)
14858
+ return byIdentity.length === 1 ? byIdentity[0] : null;
14859
+ if (!TYPED_NUMBER.test(typed))
14860
+ return null;
14861
+ const byNumber = phases.filter((entry) => Number.isInteger(entry.number) && String(entry.number) === typed);
14862
+ return byNumber.length === 1 ? byNumber[0] : null;
14863
+ }
14864
+
14865
+ // ../core/dist/release-version.js
14866
+ var RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
14867
+
14768
14868
  // dist/control-origin.js
14769
14869
  var DEFAULT_CONTROL_ORIGIN = "https://control.halfcycle.ai";
14770
14870
  function resolveControlOrigin(env) {
@@ -14799,8 +14899,8 @@ function readConfig(env = process.env) {
14799
14899
  }
14800
14900
  };
14801
14901
  }
14802
- function readNeverConfiguredReason() {
14803
- return process.env["HALFCYCLE_NOT_THIS_ACCOUNT"] === "1" ? "not-this-account" : "no-credential-yet";
14902
+ function readNeverConfiguredReason(env = process.env) {
14903
+ return env["HALFCYCLE_NOT_THIS_ACCOUNT"] === "1" ? "not-this-account" : "no-credential-yet";
14804
14904
  }
14805
14905
  function isHalfcycleOwnCi() {
14806
14906
  return process.env["HALFCYCLE_INTERNAL_CI"] === "1";
@@ -14830,6 +14930,9 @@ function readMcpServerUrl() {
14830
14930
  const raw = process.env["CLAUDE_CODE_MCP_SERVER_URL"];
14831
14931
  return raw ? raw : void 0;
14832
14932
  }
14933
+ function readSessionAttended(env = process.env) {
14934
+ return env["CLAUDE_CODE_SESSION_ATTENDED"] === "1";
14935
+ }
14833
14936
  function readExcerptConfig() {
14834
14937
  const raw = process.env["HALFCYCLE_SOURCE_EXCERPTS"];
14835
14938
  const enabled = (raw ?? "").trim().toLowerCase() !== "false";
@@ -15040,14 +15143,25 @@ var wireErrorSchema = external_exports.object({
15040
15143
  message: external_exports.string()
15041
15144
  }).strict();
15042
15145
 
15146
+ // ../events/dist/lone-surrogates.js
15147
+ var LONE_SURROGATE = /\p{Cs}/u;
15148
+ function hasLoneSurrogate(text) {
15149
+ return LONE_SURROGATE.test(text);
15150
+ }
15151
+ var LONE_SURROGATE_MESSAGE = "must be valid text: it holds half of a character pair with no other half";
15152
+
15043
15153
  // ../events/dist/state.js
15044
15154
  var utcIso8601 = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
15045
15155
  var STATE_RECORD_FORMAT = "halfcycle-state-record/v1";
15046
15156
  var stepIdSchema = external_exports.string().regex(/^(l[0-5](-l[0-5])?|xl)\.[a-z0-9-]+$/);
15047
- var methodVersionSchema = external_exports.string().regex(/^\d+\.\d+\.\d+$/);
15157
+ var methodVersionSchema = external_exports.string().regex(RELEASE_VERSION_PATTERN);
15048
15158
  var anchorPathRegex = /^(?!\/)(?!.*#)(?!method\/)(?!(.*\/)?docs\/method\/).+$/;
15049
15159
  var recordKindSchema = external_exports.enum(["artefact", "step-run", "gate", "intervention"]);
15050
15160
  var methodLayerSchema = external_exports.enum(["l0", "l1", "l2", "l3", "l3-l4", "l4", "l5", "xl"]);
15161
+ var PHASE_SCOPED_LAYERS = ["l4", "l5"];
15162
+ function isPhaseScopedLayer(layer) {
15163
+ return PHASE_SCOPED_LAYERS.includes(layer);
15164
+ }
15051
15165
  var evidenceKindSchema = external_exports.enum(["anchor", "diff", "repro", "question-answer", "no-op"]);
15052
15166
  var declarableEvidenceKindSchema = external_exports.enum(["anchor", "diff", "repro", "question-answer"]);
15053
15167
  var dispositionSchema = external_exports.enum([
@@ -15295,6 +15409,787 @@ function describeIdentity(identity) {
15295
15409
  return `(a ${typeof identity})`;
15296
15410
  }
15297
15411
 
15412
+ // ../events/dist/build-record.js
15413
+ var BUILD_RECORD_V2_FORMAT = "halfcycle-build-record/v2";
15414
+ var STATEMENT_MAX = 2e3;
15415
+ var DEVIATION_MAX = 500;
15416
+ var DEVIATIONS_MAX = 20;
15417
+ var ACTOR_MAX = 200;
15418
+ var FINDING_MAX = 500;
15419
+ var FINDINGS_SHOWN_MAX = 50;
15420
+ var PHASE_CLOSE_REASON_MAX = 1900;
15421
+ var closeCommitSchema = external_exports.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/, "must be a full commit hash: 40 or 64 lowercase hex characters");
15422
+ var walkVerdictSchema = external_exports.enum(["clean", "defects"]);
15423
+ var closeDecisionSchema = external_exports.enum(["done-when-met", "override"]);
15424
+ function plainText(max) {
15425
+ const base = max === void 0 ? external_exports.string() : external_exports.string().max(max);
15426
+ return base.refine((v) => !hasControlCharacter(v), {
15427
+ message: "must be one line with no control characters (no line breaks, tabs or NUL)"
15428
+ }).refine((v) => !hasLoneSurrogate(v), { message: LONE_SURROGATE_MESSAGE });
15429
+ }
15430
+ function statementText(max) {
15431
+ return plainText(max).refine((v) => !looksLikeSecret(v), {
15432
+ message: "looks like a password or key \u2014 rephrase it"
15433
+ });
15434
+ }
15435
+ var closeActorSchema = plainText(ACTOR_MAX);
15436
+ var PHASE_NAME_RULE = `must be a phase name: not empty, no slash or backslash, not starting with a dot, no control characters, at most ${PHASE_NAME_MAX_BYTES} bytes`;
15437
+ var phaseNameSchema = external_exports.string().refine((v) => isValidPhaseIdentity(v), {
15438
+ message: PHASE_NAME_RULE
15439
+ });
15440
+ var phaseIdentitySchema = external_exports.union([external_exports.string(), external_exports.number()], { error: "must be a phase name or a whole number" }).refine((v) => isValidPhaseIdentity(v), { message: PHASE_NAME_RULE });
15441
+ var closeStatementsSchema = external_exports.object({
15442
+ thesis: statementText(STATEMENT_MAX).nullable(),
15443
+ outcome: statementText(STATEMENT_MAX).nullable(),
15444
+ deviations: external_exports.array(statementText(DEVIATION_MAX)).max(DEVIATIONS_MAX),
15445
+ closeCommit: closeCommitSchema.nullable()
15446
+ }).strict();
15447
+ var closedBySchema = external_exports.discriminatedUnion("kind", [
15448
+ external_exports.object({ kind: external_exports.literal("account"), email: plainText().nullable() }).strict(),
15449
+ external_exports.object({ kind: external_exports.literal("no-account") }).strict(),
15450
+ external_exports.object({ kind: external_exports.literal("account-removed") }).strict(),
15451
+ external_exports.object({ kind: external_exports.literal("not-recorded") }).strict()
15452
+ ]);
15453
+ var STEP_NAME_MAX = 200;
15454
+ var stepNameSchema = plainText(STEP_NAME_MAX).refine((v) => v.trim().length > 0, {
15455
+ message: "must not be blank"
15456
+ });
15457
+ var unfinishedStepSchema = external_exports.object({
15458
+ stepId: plainText(),
15459
+ state: external_exports.enum(["parked", "failed"]),
15460
+ name: stepNameSchema.optional()
15461
+ }).strict();
15462
+ var crossedSchema = external_exports.discriminatedUnion("kind", [
15463
+ external_exports.object({ kind: external_exports.literal("none") }).strict(),
15464
+ external_exports.object({ kind: external_exports.literal("unfinished-steps"), steps: external_exports.array(unfinishedStepSchema).min(1) }).strict(),
15465
+ external_exports.object({ kind: external_exports.literal("clean-over-findings"), findings: external_exports.number().int().min(1) }).strict(),
15466
+ external_exports.object({ kind: external_exports.literal("not-recorded") }).strict()
15467
+ ]);
15468
+ var seatCoverageSchema = external_exports.object({
15469
+ enrolled: external_exports.number().int().nonnegative(),
15470
+ conformant: external_exports.number().int().nonnegative()
15471
+ }).strict().refine((c) => c.conformant <= c.enrolled, {
15472
+ message: "conformant cannot exceed enrolled",
15473
+ path: ["conformant"]
15474
+ });
15475
+ var observedCloseSchema = external_exports.object({
15476
+ sequence: external_exports.number().int().min(1),
15477
+ closedAt: utcIso8601,
15478
+ closedBy: closedBySchema,
15479
+ crossed: crossedSchema,
15480
+ seatCoverage: seatCoverageSchema.nullable()
15481
+ }).strict();
15482
+ var couldNotRunCheckSchema = external_exports.object({
15483
+ check: plainText(),
15484
+ description: plainText().nullable(),
15485
+ reason: plainText()
15486
+ }).strict();
15487
+ var firedCheckSchema = external_exports.object({
15488
+ check: plainText(),
15489
+ description: plainText().nullable(),
15490
+ severity: severitySchema,
15491
+ explanation: plainText().nullable(),
15492
+ occurrences: external_exports.number().int().min(1)
15493
+ }).strict();
15494
+ var phaseChecksSchema = external_exports.discriminatedUnion("state", [
15495
+ external_exports.object({ state: external_exports.literal("not-observed") }).strict(),
15496
+ external_exports.object({
15497
+ state: external_exports.literal("nothing-fired"),
15498
+ runs: external_exports.number().int().min(1),
15499
+ couldNotRun: external_exports.array(couldNotRunCheckSchema)
15500
+ }).strict(),
15501
+ external_exports.object({
15502
+ state: external_exports.literal("fired"),
15503
+ runs: external_exports.number().int().min(1),
15504
+ couldNotRun: external_exports.array(couldNotRunCheckSchema),
15505
+ fired: external_exports.array(firedCheckSchema).min(1)
15506
+ }).strict()
15507
+ ]);
15508
+ var observedSchema = external_exports.object({
15509
+ engagementId: plainText(),
15510
+ methodVersion: methodVersionSchema,
15511
+ closes: external_exports.array(observedCloseSchema).min(1),
15512
+ checks: phaseChecksSchema
15513
+ }).strict().superRefine((o, ctx) => {
15514
+ o.closes.forEach((close, i) => {
15515
+ if (close.sequence !== i + 1) {
15516
+ ctx.addIssue({
15517
+ code: "custom",
15518
+ message: "closes must be numbered 1, 2, 3 \u2026 in order with no gap",
15519
+ path: ["closes", i, "sequence"]
15520
+ });
15521
+ }
15522
+ });
15523
+ });
15524
+ var attestedWalkSchema = external_exports.object({
15525
+ verdict: walkVerdictSchema,
15526
+ findings: external_exports.array(plainText(FINDING_MAX)).max(FINDINGS_SHOWN_MAX),
15527
+ findingsNotShown: external_exports.number().int().nonnegative()
15528
+ }).strict();
15529
+ var attestedStatementsSchema = external_exports.object({
15530
+ thesis: plainText(STATEMENT_MAX).nullable(),
15531
+ outcome: plainText(STATEMENT_MAX).nullable(),
15532
+ deviations: external_exports.array(plainText(DEVIATION_MAX)).max(DEVIATIONS_MAX)
15533
+ }).strict();
15534
+ var attestedCloseSchema = external_exports.object({
15535
+ sequence: external_exports.number().int().min(1),
15536
+ decision: closeDecisionSchema,
15537
+ actor: plainText(ACTOR_MAX).nullable(),
15538
+ reason: plainText(PHASE_CLOSE_REASON_MAX).nullable(),
15539
+ walk: attestedWalkSchema.nullable(),
15540
+ closeCommit: closeCommitSchema.nullable(),
15541
+ statements: attestedStatementsSchema
15542
+ }).strict();
15543
+ var attestedSchema = external_exports.object({
15544
+ closes: external_exports.array(attestedCloseSchema)
15545
+ }).strict();
15546
+ var buildRecordV2Schema = external_exports.object({
15547
+ format: external_exports.literal(BUILD_RECORD_V2_FORMAT),
15548
+ phase: phaseNameSchema,
15549
+ observed: observedSchema,
15550
+ attested: attestedSchema
15551
+ }).strict().superRefine((record2, ctx) => {
15552
+ const observedBySequence = new Map(record2.observed.closes.map((c) => [c.sequence, c]));
15553
+ const attestedSequences = record2.attested.closes.map((c) => c.sequence);
15554
+ const sameSet = attestedSequences.length === observedBySequence.size && new Set(attestedSequences).size === attestedSequences.length && attestedSequences.every((s) => observedBySequence.has(s));
15555
+ if (!sameSet) {
15556
+ ctx.addIssue({
15557
+ code: "custom",
15558
+ message: "attested closes must match the observed closes one for one, by sequence",
15559
+ path: ["attested", "closes"]
15560
+ });
15561
+ }
15562
+ record2.attested.closes.forEach((close, i) => {
15563
+ if (close.decision === "override" !== (close.reason !== null)) {
15564
+ ctx.addIssue({
15565
+ code: "custom",
15566
+ message: "reason is given exactly when the decision is override",
15567
+ path: ["attested", "closes", i, "reason"]
15568
+ });
15569
+ }
15570
+ const observed = observedBySequence.get(close.sequence);
15571
+ if (observed !== void 0 && observed.closedBy.kind === "account-removed" !== (close.actor === null)) {
15572
+ ctx.addIssue({
15573
+ code: "custom",
15574
+ message: "actor is empty exactly when the closing account was removed",
15575
+ path: ["attested", "closes", i, "actor"]
15576
+ });
15577
+ }
15578
+ });
15579
+ });
15580
+
15581
+ // ../events/dist/human-agent.js
15582
+ var HUMAN_AGENT = "Human";
15583
+
15584
+ // ../events/dist/build-snapshot.js
15585
+ var buildTaskStateSchema = external_exports.enum([
15586
+ "pending",
15587
+ "in-flight",
15588
+ "completed",
15589
+ "failed",
15590
+ "blocked"
15591
+ ]);
15592
+ var BUILD_TASK_TITLE_MAX = 240;
15593
+ var BUILD_SNAPSHOT_TASK_LIMIT = 500;
15594
+ var buildTaskSchema = external_exports.object({
15595
+ // The task's own id, as the coordinator's graph names it.
15596
+ id: external_exports.string().min(1).max(64),
15597
+ // What the task is, in the coordinator's words. A description of the work,
15598
+ // bounded and scanned — never an excerpt of anybody's code.
15599
+ title: external_exports.string().min(1).max(BUILD_TASK_TITLE_MAX),
15600
+ // What the task is doing right now.
15601
+ state: buildTaskStateSchema,
15602
+ // Who holds it — a bare callsign, or the value naming a person when the work is
15603
+ // a person's. Absent when nothing has been dispatched to it yet.
15604
+ agent: external_exports.string().min(1).max(32).optional(),
15605
+ // The kind of work the holder does. Present exactly when an agent holds it.
15606
+ discipline: external_exports.string().min(1).max(64).optional(),
15607
+ // How much thinking the holder brings. Present exactly when an agent holds it,
15608
+ // and there is no field naming the supplier behind it.
15609
+ tier: external_exports.string().min(1).max(32).optional(),
15610
+ // The tasks this one cannot start before. Always present; empty means it waits
15611
+ // on nothing, which is different from not having been computed.
15612
+ dependsOn: external_exports.array(external_exports.string().min(1).max(64)).max(BUILD_SNAPSHOT_TASK_LIMIT),
15613
+ // The one task this task checks, when it is a check on other work. Absent on a
15614
+ // task that is doing the work rather than checking it.
15615
+ reviews: external_exports.string().min(1).max(64).optional(),
15616
+ // Which wave of the phase this task belongs to — the wave that writes the
15617
+ // specifications, or the wave that builds them. Absent when the graph does not
15618
+ // record it, which is different from the task belonging to neither.
15619
+ layer: methodLayerSchema.optional(),
15620
+ // When it was picked up, and when it settled. A reader computes how long it
15621
+ // took; neither this row nor this payload carries a duration.
15622
+ startedAt: utcIso8601.optional(),
15623
+ settledAt: utcIso8601.optional()
15624
+ }).strict().superRefine((task, ctx) => {
15625
+ if (task.reviews !== void 0 && task.reviews === task.id) {
15626
+ ctx.addIssue({
15627
+ code: external_exports.ZodIssueCode.custom,
15628
+ path: ["reviews"],
15629
+ message: `'${task.id}' reviews itself \u2014 a review names the OTHER task it checks.`
15630
+ });
15631
+ }
15632
+ if (task.layer !== void 0 && !isPhaseScopedLayer(task.layer)) {
15633
+ ctx.addIssue({
15634
+ code: external_exports.ZodIssueCode.custom,
15635
+ path: ["layer"],
15636
+ message: `'${task.layer}' is not a wave a phase runs \u2014 expected one of ${PHASE_SCOPED_LAYERS.join(", ")}.`
15637
+ });
15638
+ }
15639
+ const profiled = task.agent !== void 0 && task.agent !== HUMAN_AGENT;
15640
+ for (const field of ["discipline", "tier"]) {
15641
+ if (profiled && task[field] === void 0) {
15642
+ ctx.addIssue({
15643
+ code: external_exports.ZodIssueCode.custom,
15644
+ path: [field],
15645
+ message: `'${field}' is required on a task an agent holds.`
15646
+ });
15647
+ }
15648
+ if (!profiled && task[field] !== void 0) {
15649
+ ctx.addIssue({
15650
+ code: external_exports.ZodIssueCode.custom,
15651
+ path: [field],
15652
+ message: task.agent === void 0 ? `'${field}' must be absent on a task nobody holds.` : `'${field}' must be absent on work a person holds.`
15653
+ });
15654
+ }
15655
+ }
15656
+ });
15657
+ var buildPhaseSchema = external_exports.string().min(1).max(128);
15658
+ var BUILD_PHASE_TITLE_MAX = 160;
15659
+ var buildPhaseTitleSchema = external_exports.string().min(1).max(BUILD_PHASE_TITLE_MAX);
15660
+ var buildTaskListSchema = external_exports.array(buildTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT).superRefine((tasks, ctx) => {
15661
+ const seen = /* @__PURE__ */ new Set();
15662
+ for (const [index, task] of tasks.entries()) {
15663
+ if (seen.has(task.id)) {
15664
+ ctx.addIssue({
15665
+ code: external_exports.ZodIssueCode.custom,
15666
+ path: [index, "id"],
15667
+ message: `duplicate task id '${task.id}' \u2014 one graph names each task once.`
15668
+ });
15669
+ }
15670
+ seen.add(task.id);
15671
+ }
15672
+ });
15673
+ var buildSnapshotSchema = external_exports.object({
15674
+ // Which phase this graph belongs to.
15675
+ phase: buildPhaseSchema,
15676
+ // That phase's name in words, when the project gives it one.
15677
+ phaseTitle: buildPhaseTitleSchema.optional(),
15678
+ // Every task on it. Never a partial graph and never a change since last time.
15679
+ tasks: buildTaskListSchema,
15680
+ // When this graph was last reported.
15681
+ reportedAt: utcIso8601,
15682
+ // When work on this phase was first reported with no approval of its scope in force.
15683
+ builtWithoutApproval: utcIso8601.optional()
15684
+ }).strict();
15685
+ var buildPhaseEntrySchema = external_exports.object({
15686
+ // Which phase has a stored graph.
15687
+ phase: buildPhaseSchema,
15688
+ // That phase's name in words, when the project gave it one.
15689
+ phaseTitle: buildPhaseTitleSchema.optional(),
15690
+ // When that phase's graph was last reported.
15691
+ reportedAt: utcIso8601,
15692
+ // Present when work on this phase was reported with no approval of its scope in force.
15693
+ builtWithoutApproval: external_exports.literal(true).optional()
15694
+ }).strict();
15695
+ var buildSnapshotReportSchema = external_exports.object({
15696
+ // Which phase this graph belongs to.
15697
+ phase: buildPhaseSchema,
15698
+ // That phase's name in words, when the project gives it one.
15699
+ phaseTitle: buildPhaseTitleSchema.optional(),
15700
+ // The whole graph, every time. Reporting a change instead of the graph is the
15701
+ // one thing this route does not accept.
15702
+ tasks: buildTaskListSchema
15703
+ }).strict();
15704
+ var buildSnapshotReportResultSchema = external_exports.object({
15705
+ // What happened. One outcome today; a refusal travels as the shared wire error.
15706
+ outcome: external_exports.literal("recorded"),
15707
+ // Which phase the graph was recorded against.
15708
+ phase: buildPhaseSchema,
15709
+ // When it was recorded.
15710
+ reportedAt: utcIso8601
15711
+ }).strict();
15712
+
15713
+ // ../events/dist/system-description.js
15714
+ var SYSTEM_DESCRIPTION_FORMAT = 1;
15715
+ var SYSTEM_PARTS_MAX = 60;
15716
+ var SYSTEM_CONNECTIONS_MAX = 400;
15717
+ var SYSTEM_SLOT_MAX = 11;
15718
+ var SYSTEM_OWNS_MAX = 20;
15719
+ var SYSTEM_OWNED_PATTERN_MAX = 200;
15720
+ var SYSTEM_PART_NAME_MAX = 60;
15721
+ var SYSTEM_PART_DESCRIPTION_MAX = 160;
15722
+ var SYSTEM_CONNECTION_LABEL_MAX = 60;
15723
+ var SYSTEM_PART_ID_PATTERN = /^[a-z][a-z0-9-]{0,39}$/;
15724
+ var SYSTEM_LANES = ["clients", "edge", "services", "data", "external"];
15725
+ var systemLaneSchema = external_exports.enum(SYSTEM_LANES, {
15726
+ error: `must be one of ${SYSTEM_LANES.join(", ")}`
15727
+ });
15728
+ var SYSTEM_CONNECTION_KINDS = ["call", "event", "write", "external"];
15729
+ var systemConnectionKindSchema = external_exports.enum(SYSTEM_CONNECTION_KINDS, {
15730
+ error: `must be one of ${SYSTEM_CONNECTION_KINDS.join(", ")}`
15731
+ });
15732
+ var LINE_SEPARATOR = /[\u2028\u2029]/;
15733
+ function descriptionText(max) {
15734
+ return statementText(max).refine((v) => v.trim().length > 0, { message: "must not be empty or only spaces" }).refine((v) => !LINE_SEPARATOR.test(v), {
15735
+ message: "must be one line: it holds a line or paragraph separator"
15736
+ });
15737
+ }
15738
+ var systemPartIdSchema = external_exports.string().regex(SYSTEM_PART_ID_PATTERN, {
15739
+ message: "must be a short id: a lowercase letter, then lowercase letters, digits or hyphens, 40 characters at most"
15740
+ }).refine((v) => !looksLikeSecret(v), { message: "looks like a password or key; choose another id" });
15741
+ function hasEmptySegment(pattern) {
15742
+ return pattern.split("/").some((segment) => segment.length === 0);
15743
+ }
15744
+ function hasDotSegment(pattern) {
15745
+ return pattern.split("/").some((segment) => segment === "." || segment === "..");
15746
+ }
15747
+ function hasPartialDoubleStar(pattern) {
15748
+ return pattern.split("/").some((segment) => segment.includes("**") && segment !== "**");
15749
+ }
15750
+ var SEGMENT_CHARACTERS = /^[A-Za-z0-9._\-*]*$/;
15751
+ function hasForeignCharacter(pattern) {
15752
+ return pattern.split("/").some((segment) => !SEGMENT_CHARACTERS.test(segment));
15753
+ }
15754
+ var systemOwnedPatternSchema = external_exports.string().max(SYSTEM_OWNED_PATTERN_MAX, { message: `must be at most ${SYSTEM_OWNED_PATTERN_MAX} characters` }).refine((v) => !hasEmptySegment(v), {
15755
+ message: "must not start or end with a slash, or have two slashes together"
15756
+ }).refine((v) => !hasDotSegment(v), { message: 'must not have a "." or ".." folder in it' }).refine((v) => !hasPartialDoubleStar(v), { message: '"**" must stand alone between slashes' }).refine((v) => !hasForeignCharacter(v), {
15757
+ message: 'may use only letters, digits, ".", "_", "-" and "*" between slashes'
15758
+ }).refine((v) => !looksLikeSecret(v), {
15759
+ message: "this path looks like a credential; list a shorter folder above it"
15760
+ });
15761
+ var systemPartSchema = external_exports.object({
15762
+ id: systemPartIdSchema,
15763
+ name: descriptionText(SYSTEM_PART_NAME_MAX),
15764
+ description: descriptionText(SYSTEM_PART_DESCRIPTION_MAX),
15765
+ lane: systemLaneSchema,
15766
+ slot: external_exports.number({ error: `must be a whole number from 0 to ${SYSTEM_SLOT_MAX}` }).int({ error: `must be a whole number from 0 to ${SYSTEM_SLOT_MAX}` }).min(0, { error: `must be a whole number from 0 to ${SYSTEM_SLOT_MAX}` }).max(SYSTEM_SLOT_MAX, { error: `must be a whole number from 0 to ${SYSTEM_SLOT_MAX}` }),
15767
+ outside: external_exports.boolean(),
15768
+ owns: external_exports.array(systemOwnedPatternSchema).max(SYSTEM_OWNS_MAX, { message: `can list at most ${SYSTEM_OWNS_MAX} paths` })
15769
+ }).strict();
15770
+ var systemConnectionSchema = external_exports.object({
15771
+ from: systemPartIdSchema,
15772
+ to: systemPartIdSchema,
15773
+ kind: systemConnectionKindSchema,
15774
+ label: descriptionText(SYSTEM_CONNECTION_LABEL_MAX)
15775
+ }).strict();
15776
+ var systemDescriptionSchema = external_exports.object({
15777
+ format: external_exports.literal(SYSTEM_DESCRIPTION_FORMAT, { error: `must be ${SYSTEM_DESCRIPTION_FORMAT}` }),
15778
+ parts: external_exports.array(systemPartSchema).min(1, { message: "must list at least one part" }).max(SYSTEM_PARTS_MAX, { message: `can hold at most ${SYSTEM_PARTS_MAX} parts (five lanes of twelve slots)` }),
15779
+ connections: external_exports.array(systemConnectionSchema).max(SYSTEM_CONNECTIONS_MAX, { message: `can hold at most ${SYSTEM_CONNECTIONS_MAX} connections` })
15780
+ }).strict();
15781
+ var systemDescriptionProblemSchema = external_exports.object({
15782
+ path: external_exports.string(),
15783
+ message: external_exports.string()
15784
+ }).strict();
15785
+ var systemReportRecordedSchema = external_exports.object({
15786
+ outcome: external_exports.literal("recorded"),
15787
+ version: external_exports.number().int().min(1),
15788
+ reportedAt: utcIso8601
15789
+ }).strict();
15790
+ var systemReportRefusedSchema = external_exports.object({
15791
+ outcome: external_exports.literal("refused"),
15792
+ problems: external_exports.array(systemDescriptionProblemSchema).min(1)
15793
+ }).strict();
15794
+ var systemReportUnavailableSchema = external_exports.object({
15795
+ outcome: external_exports.literal("unavailable")
15796
+ }).strict();
15797
+ var systemReportResultSchema = external_exports.discriminatedUnion("outcome", [
15798
+ systemReportRecordedSchema,
15799
+ systemReportRefusedSchema,
15800
+ systemReportUnavailableSchema
15801
+ ]);
15802
+ var SYSTEM_DRAW_SVG_MAX_BYTES = 2 * 1024 * 1024;
15803
+ var SYSTEM_DRAW_MERMAID_MAX_BYTES = 256 * 1024;
15804
+ var SYSTEM_DRAW_PNG_MAX_BYTES = 8 * 1024 * 1024;
15805
+ var systemDrawArchitectureRequestSchema = external_exports.object({
15806
+ picture: external_exports.literal("architecture"),
15807
+ version: external_exports.number().int().min(1)
15808
+ }).strict();
15809
+ var systemDrawPhaseRequestSchema = external_exports.object({
15810
+ picture: external_exports.literal("phase"),
15811
+ phase: phaseIdentitySchema,
15812
+ planVersion: external_exports.number().int().min(1)
15813
+ }).strict();
15814
+ var systemDrawRequestSchema = external_exports.discriminatedUnion("picture", [
15815
+ systemDrawArchitectureRequestSchema,
15816
+ systemDrawPhaseRequestSchema
15817
+ ]);
15818
+ function utf8ByteLength2(text) {
15819
+ let bytes = 0;
15820
+ for (let i = 0; i < text.length; i++) {
15821
+ const unit = text.charCodeAt(i);
15822
+ if (unit < 128)
15823
+ bytes += 1;
15824
+ else if (unit < 2048)
15825
+ bytes += 2;
15826
+ else if (unit >= 55296 && unit <= 56319 && i + 1 < text.length) {
15827
+ const next = text.charCodeAt(i + 1);
15828
+ if (next >= 56320 && next <= 57343) {
15829
+ bytes += 4;
15830
+ i++;
15831
+ } else {
15832
+ bytes += 3;
15833
+ }
15834
+ } else
15835
+ bytes += 3;
15836
+ }
15837
+ return bytes;
15838
+ }
15839
+ var BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
15840
+ function base64DecodedLength(text) {
15841
+ const padding = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
15842
+ return text.length / 4 * 3 - padding;
15843
+ }
15844
+ var systemPictureSchema = external_exports.object({
15845
+ svg: external_exports.string().min(1).refine((v) => utf8ByteLength2(v) <= SYSTEM_DRAW_SVG_MAX_BYTES, { message: "the picture is larger than 2 MB" }),
15846
+ png: external_exports.string().min(1).refine((v) => v.length % 4 === 0 && BASE64.test(v), { message: "the image is not valid base64" }).refine((v) => base64DecodedLength(v) <= SYSTEM_DRAW_PNG_MAX_BYTES, { message: "the image is larger than 8 MB" })
15847
+ }).strict();
15848
+ var systemDrawRefusedSchema = external_exports.object({
15849
+ outcome: external_exports.literal("refused"),
15850
+ reason: external_exports.string().min(1)
15851
+ }).strict();
15852
+ var systemDrawUnavailableSchema = external_exports.object({
15853
+ outcome: external_exports.literal("unavailable")
15854
+ }).strict();
15855
+
15856
+ // ../events/dist/phase-plan.js
15857
+ var PHASE_PLAN_FORMAT = 1;
15858
+ var PHASE_PLAN_CHANGES_MAX = 60;
15859
+ var PHASE_PLAN_SUMMARY_MAX = 160;
15860
+ var SCOPE_NOTE_MAX = 500;
15861
+ var PHASE_PLAN_PHASE_MAX = buildPhaseSchema.maxLength ?? 0;
15862
+ var PHASE_PLAN_CHANGE_KINDS = ["add", "change", "remove"];
15863
+ var phasePlanChangeSchema = external_exports.enum(PHASE_PLAN_CHANGE_KINDS, {
15864
+ error: `must be one of ${PHASE_PLAN_CHANGE_KINDS.join(", ")}`
15865
+ });
15866
+ var phasePlanPhaseSchema = phaseIdentitySchema.refine((v) => !isValidPhaseIdentity(v) || buildPhaseSchema.safeParse(String(v)).success, { message: `must be at most ${PHASE_PLAN_PHASE_MAX} characters` });
15867
+ var phasePlanSummarySchema = descriptionText(PHASE_PLAN_SUMMARY_MAX);
15868
+ var phasePlanEntrySchema = external_exports.object({
15869
+ part: systemPartIdSchema,
15870
+ change: phasePlanChangeSchema,
15871
+ summary: phasePlanSummarySchema
15872
+ }).strict();
15873
+ var phasePlanSchema = external_exports.object({
15874
+ format: external_exports.literal(PHASE_PLAN_FORMAT, { error: `must be ${PHASE_PLAN_FORMAT}` }),
15875
+ phase: phasePlanPhaseSchema,
15876
+ changes: external_exports.array(phasePlanEntrySchema).min(1, { message: "must list at least one part" }).max(PHASE_PLAN_CHANGES_MAX, { message: `can list at most ${PHASE_PLAN_CHANGES_MAX} parts` })
15877
+ }).strict();
15878
+ var scopePhaseSchema = buildPhaseSchema.refine((v) => isValidPhaseIdentity(v), {
15879
+ message: "must be a phase name"
15880
+ });
15881
+ var SCOPE_STATE_NAMES = [
15882
+ "not-planned",
15883
+ "waiting",
15884
+ "sent-back",
15885
+ "approved",
15886
+ "partly-stale",
15887
+ "withdrawn"
15888
+ ];
15889
+ var scopeStateNameSchema = external_exports.enum(SCOPE_STATE_NAMES);
15890
+ var plannedScopeStateSchema = scopeStateNameSchema.exclude(["not-planned"]);
15891
+ var SCOPE_PART_STATUSES = ["approved", "waiting"];
15892
+ var scopePartStatusSchema = external_exports.enum(SCOPE_PART_STATUSES);
15893
+ var SCOPE_ACTS = ["approve", "send-back", "withdraw"];
15894
+ var scopeActSchema = external_exports.enum(SCOPE_ACTS);
15895
+ var SCOPE_GIVEN_IN = ["console", "chat"];
15896
+ var scopeGivenInSchema = external_exports.enum(SCOPE_GIVEN_IN);
15897
+ var scopeNoteSchema = descriptionText(SCOPE_NOTE_MAX);
15898
+ var scopeNoteWireSchema = external_exports.string().max(SCOPE_NOTE_MAX);
15899
+ var SCOPE_PART_VERDICTS = ["ok", "change"];
15900
+ var scopePartVerdictSchema = external_exports.enum(SCOPE_PART_VERDICTS);
15901
+ var scopeDecisionPartSchema = external_exports.object({
15902
+ part: systemPartIdSchema,
15903
+ verdict: scopePartVerdictSchema,
15904
+ note: scopeNoteWireSchema.optional()
15905
+ }).strict();
15906
+ var planVersionSchema = external_exports.number().int().min(1);
15907
+ var phasePlanRequestSchema = external_exports.object({
15908
+ descriptionVersion: external_exports.number().int().min(1),
15909
+ plan: external_exports.unknown()
15910
+ }).strict();
15911
+ var phasePlanRecordedSchema = external_exports.object({
15912
+ outcome: external_exports.literal("recorded"),
15913
+ version: planVersionSchema,
15914
+ phaseNumber: external_exports.number().int().min(1),
15915
+ state: plannedScopeStateSchema,
15916
+ changedParts: external_exports.array(systemPartIdSchema).max(PHASE_PLAN_CHANGES_MAX),
15917
+ typedApprovalOffered: external_exports.boolean(),
15918
+ reportedAt: utcIso8601
15919
+ }).strict();
15920
+ var phasePlanRefusedSchema = external_exports.object({
15921
+ outcome: external_exports.literal("refused"),
15922
+ problems: external_exports.array(systemDescriptionProblemSchema).min(1)
15923
+ }).strict();
15924
+ var phasePlanUnavailableSchema = external_exports.object({
15925
+ outcome: external_exports.literal("unavailable")
15926
+ }).strict();
15927
+ var phasePlanResultSchema = external_exports.discriminatedUnion("outcome", [
15928
+ phasePlanRecordedSchema,
15929
+ phasePlanRefusedSchema,
15930
+ phasePlanUnavailableSchema
15931
+ ]);
15932
+ var scopeApproveRequestSchema = external_exports.object({
15933
+ act: external_exports.literal("approve"),
15934
+ planVersion: planVersionSchema,
15935
+ parts: external_exports.array(scopeDecisionPartSchema).max(PHASE_PLAN_CHANGES_MAX)
15936
+ }).strict();
15937
+ var scopeSendBackRequestSchema = external_exports.object({
15938
+ act: external_exports.literal("send-back"),
15939
+ planVersion: planVersionSchema,
15940
+ parts: external_exports.array(scopeDecisionPartSchema).max(PHASE_PLAN_CHANGES_MAX),
15941
+ note: scopeNoteWireSchema.optional()
15942
+ }).strict();
15943
+ var scopeWithdrawRequestSchema = external_exports.object({
15944
+ act: external_exports.literal("withdraw"),
15945
+ planVersion: planVersionSchema
15946
+ }).strict();
15947
+ var scopeDecisionRequestSchema = external_exports.discriminatedUnion("act", [
15948
+ scopeApproveRequestSchema,
15949
+ scopeSendBackRequestSchema,
15950
+ scopeWithdrawRequestSchema
15951
+ ]);
15952
+ var scopeDecisionRecordedSchema = external_exports.object({
15953
+ outcome: external_exports.literal("recorded"),
15954
+ act: scopeActSchema,
15955
+ phase: scopePhaseSchema,
15956
+ planVersion: planVersionSchema,
15957
+ state: plannedScopeStateSchema,
15958
+ decidedAt: utcIso8601
15959
+ }).strict();
15960
+ var scopeDecisionAlreadyApprovedSchema = external_exports.object({
15961
+ outcome: external_exports.literal("already-approved"),
15962
+ replanNeeded: external_exports.boolean(),
15963
+ reason: external_exports.string().min(1)
15964
+ }).strict();
15965
+ var scopeDecisionRefusedSchema = external_exports.object({
15966
+ outcome: external_exports.literal("refused"),
15967
+ problems: external_exports.array(systemDescriptionProblemSchema).min(1)
15968
+ }).strict();
15969
+ var scopeDecisionUnavailableSchema = external_exports.object({
15970
+ outcome: external_exports.literal("unavailable")
15971
+ }).strict();
15972
+ function reasonOutcome(outcome) {
15973
+ return external_exports.object({ outcome: external_exports.literal(outcome), reason: external_exports.string().min(1) }).strict();
15974
+ }
15975
+ var scopeDecisionResultSchema = external_exports.discriminatedUnion("outcome", [
15976
+ scopeDecisionRecordedSchema,
15977
+ scopeDecisionAlreadyApprovedSchema,
15978
+ scopeDecisionRefusedSchema,
15979
+ scopeDecisionUnavailableSchema,
15980
+ reasonOutcome("not-owner"),
15981
+ reasonOutcome("no-owner"),
15982
+ reasonOutcome("plan-changed"),
15983
+ reasonOutcome("nothing-to-withdraw"),
15984
+ reasonOutcome("build-started")
15985
+ ]);
15986
+ var scopeReadPartSchema = external_exports.object({
15987
+ part: systemPartIdSchema,
15988
+ name: systemPartSchema.shape.name,
15989
+ change: phasePlanChangeSchema,
15990
+ summary: phasePlanSummarySchema,
15991
+ status: scopePartStatusSchema,
15992
+ changed: external_exports.boolean()
15993
+ }).strict();
15994
+ var scopeReadDecisionSchema = external_exports.object({
15995
+ act: scopeActSchema,
15996
+ planVersion: planVersionSchema,
15997
+ givenIn: scopeGivenInSchema,
15998
+ at: utcIso8601,
15999
+ parts: external_exports.array(scopeDecisionPartSchema).max(PHASE_PLAN_CHANGES_MAX),
16000
+ note: scopeNoteWireSchema.optional()
16001
+ }).strict();
16002
+ var scopeReadNotPlannedSchema = external_exports.object({
16003
+ phase: scopePhaseSchema,
16004
+ state: external_exports.literal("not-planned"),
16005
+ canDecide: external_exports.boolean()
16006
+ }).strict();
16007
+ var scopeReadPlannedSchema = external_exports.object({
16008
+ phase: scopePhaseSchema,
16009
+ state: plannedScopeStateSchema,
16010
+ phaseNumber: external_exports.number().int().min(1),
16011
+ planVersion: planVersionSchema,
16012
+ parts: external_exports.array(scopeReadPartSchema).min(1).max(PHASE_PLAN_CHANGES_MAX),
16013
+ decisions: external_exports.array(scopeReadDecisionSchema),
16014
+ canDecide: external_exports.boolean(),
16015
+ replanLine: external_exports.string().min(1).optional()
16016
+ }).strict();
16017
+ var scopeReadSchema = external_exports.union([scopeReadNotPlannedSchema, scopeReadPlannedSchema]);
16018
+
16019
+ // ../events/dist/client-version.js
16020
+ var CLIENT_VERSION_HEADER = "x-halfcycle-client";
16021
+ var TYPED_APPROVAL_OFF_SENTENCE = "Approving by typing is switched off for now.";
16022
+ var CLIENT_UPDATE_SENTENCE = `To approve by typing in a session, Halfcycle needs updating: run \`${HALFCYCLE_COMMAND}\` in this project's folder, then reopen your session and ask again.`;
16023
+
16024
+ // ../events/dist/scope-notices.js
16025
+ var scopePartNameSchema = external_exports.string().min(1).max(SYSTEM_PART_NAME_MAX);
16026
+ var scopeNoteEntrySchema = external_exports.object({
16027
+ part: scopePartNameSchema.optional(),
16028
+ note: external_exports.string().min(1).max(SCOPE_NOTE_MAX)
16029
+ }).strict();
16030
+ var scopeSentBackSchema = external_exports.object({
16031
+ phase: scopePhaseSchema,
16032
+ number: external_exports.number().int().min(1),
16033
+ notes: external_exports.array(scopeNoteEntrySchema).min(1).max(PHASE_PLAN_CHANGES_MAX + 1)
16034
+ }).strict().superRefine((entry, ctx) => {
16035
+ entry.notes.forEach((n, i) => {
16036
+ if (n.part === void 0 && i !== entry.notes.length - 1) {
16037
+ ctx.addIssue({
16038
+ code: external_exports.ZodIssueCode.custom,
16039
+ path: ["notes", i],
16040
+ message: "the note on the whole phase comes after every note on a part"
16041
+ });
16042
+ }
16043
+ });
16044
+ });
16045
+ var scopeWaitingSchema = external_exports.object({
16046
+ phase: scopePhaseSchema,
16047
+ number: external_exports.number().int().min(1),
16048
+ changedParts: external_exports.array(scopePartNameSchema).max(PHASE_PLAN_CHANGES_MAX)
16049
+ }).strict();
16050
+ var scopeNoticesSchema = external_exports.object({
16051
+ sentBack: external_exports.array(scopeSentBackSchema),
16052
+ waiting: external_exports.array(scopeWaitingSchema),
16053
+ typedApprovalOffered: external_exports.boolean()
16054
+ }).strict().refine((v) => v.sentBack.length > 0 || v.waiting.length > 0, {
16055
+ message: "carried only when a phase was sent back or waits for the owner"
16056
+ });
16057
+ function quotedParts(parts) {
16058
+ return parts.map((p) => `"${p}"`).join(", ");
16059
+ }
16060
+ function approvalSentence(phase, offerTyping) {
16061
+ if (offerTyping === "off")
16062
+ return TYPED_APPROVAL_OFF_SENTENCE;
16063
+ if (offerTyping === "update")
16064
+ return CLIENT_UPDATE_SENTENCE;
16065
+ return `In their own Claude Code session, they ask to see the plan, check each part, and then type: approve ${phase}`;
16066
+ }
16067
+ function sentBackNotice(entry) {
16068
+ return [
16069
+ `Before anything else: the project's owner sent the scope of phase ${entry.number}, "${entry.phase}", back with these notes.`,
16070
+ ...entry.notes.map((n) => n.part === void 0 ? `- ${n.note}` : `- "${n.part}": ${n.note}`),
16071
+ `Change the plan to answer every note, then send it again with \`${HALFCYCLE_COMMAND} plan ${quoteForShell(entry.phase)}\`.`
16072
+ ].join("\n");
16073
+ }
16074
+ function waitingNotice(entry, offerTyping) {
16075
+ return [
16076
+ ...entry.changedParts.length > 0 ? [`These parts changed since the owner last decided on this phase, and wait for their approval: ${quotedParts(entry.changedParts)}.`] : [],
16077
+ `The scope of phase ${entry.number}, "${entry.phase}", is waiting for the project's owner to approve it.`,
16078
+ approvalSentence(entry.phase, offerTyping)
16079
+ ].join(" ");
16080
+ }
16081
+ function scopeNoticeText(scope, options) {
16082
+ const omit2 = options.omitActivePhase === true ? options.activePhase : void 0;
16083
+ return {
16084
+ sentBack: scope.sentBack.map(sentBackNotice),
16085
+ waiting: scope.waiting.filter((w) => omit2 === void 0 || w.phase !== omit2).map((w) => waitingNotice(w, options.offerTyping))
16086
+ };
16087
+ }
16088
+
16089
+ // ../events/dist/device-auth.js
16090
+ var utcIso86012 = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
16091
+ var DEVICE_AUTH_STATUS = {
16092
+ /** The poll: minted, nobody has acted yet. Keep polling. 200. */
16093
+ PENDING: "pending",
16094
+ /** The poll: approved, and the credential is in this body. Once only. 200. */
16095
+ APPROVED: "approved",
16096
+ /** Confirm: the decision was recorded (approve or decline). 200. */
16097
+ RECORDED: "recorded",
16098
+ /** The human refused at the confirmation page. 400 on the poll. */
16099
+ DECLINED: "declined",
16100
+ /** The code's absolute expiry has passed. 400. */
16101
+ EXPIRED: "expired",
16102
+ /** Never minted, or already spent. 400. */
16103
+ UNKNOWN: "unknown",
16104
+ /** Confirm: neither `approve` nor `decline` was named. Nothing was bound. 400. */
16105
+ INVALID_DECISION: "invalid-decision",
16106
+ /** Confirm: an approval arrived with no signed-in identity. 400. */
16107
+ IDENTITY_REQUIRED: "identity-required",
16108
+ /** This deployment has no `SIGNIN_BASE_URL` / `DEVICE_AUTH_CONFIRM_SECRET`. 503. */
16109
+ NOT_CONFIGURED: "not-configured",
16110
+ /** Confirm: the shared confirmation secret was missing or wrong. 401. */
16111
+ UNAUTHORIZED: "unauthorized",
16112
+ /** Confirm: the code was already approved, declined or claimed. 409. */
16113
+ ALREADY_DECIDED: "already-decided",
16114
+ /**
16115
+ * Confirm: an approval named a signed-in identity whose account has not accepted
16116
+ * the terms of service and privacy policy currently in force. 400.
16117
+ *
16118
+ * NOT BOUND TO THE DEVICE CODE. Unlike every other refusal in this const, this one
16119
+ * says nothing about the code itself — the code is still `pending` after this
16120
+ * response, exactly as it was before the request, so the SAME userCode can be
16121
+ * confirmed again once the caller has recorded acceptance. `deviceConfirmRequestSchema`'s
16122
+ * `acceptTerms` is how a second confirm says it did.
16123
+ */
16124
+ TERMS_REQUIRED: "terms-required"
16125
+ };
16126
+ var DEVICE_POLL_REFUSALS = [
16127
+ DEVICE_AUTH_STATUS.DECLINED,
16128
+ DEVICE_AUTH_STATUS.EXPIRED,
16129
+ DEVICE_AUTH_STATUS.UNKNOWN
16130
+ ];
16131
+ var deviceAuthStartResponseSchema = external_exports.object({
16132
+ deviceCode: external_exports.string().min(1),
16133
+ userCode: external_exports.string().min(1),
16134
+ verificationUrl: external_exports.string().min(1),
16135
+ expiresAt: utcIso86012,
16136
+ pollIntervalMs: external_exports.number().int().positive()
16137
+ }).strict();
16138
+ var devicePollPendingSchema = external_exports.object({
16139
+ status: external_exports.literal(DEVICE_AUTH_STATUS.PENDING),
16140
+ pollIntervalMs: external_exports.number().int().positive()
16141
+ }).strict();
16142
+ var devicePollApprovedSchema = external_exports.object({
16143
+ status: external_exports.literal(DEVICE_AUTH_STATUS.APPROVED),
16144
+ accountId: external_exports.string().min(1),
16145
+ credential: external_exports.string().min(1),
16146
+ expiresAt: utcIso86012
16147
+ }).strict();
16148
+ var deviceAuthRefusalSchema = external_exports.object({
16149
+ status: external_exports.enum([
16150
+ DEVICE_AUTH_STATUS.DECLINED,
16151
+ DEVICE_AUTH_STATUS.EXPIRED,
16152
+ DEVICE_AUTH_STATUS.UNKNOWN,
16153
+ DEVICE_AUTH_STATUS.INVALID_DECISION,
16154
+ DEVICE_AUTH_STATUS.IDENTITY_REQUIRED,
16155
+ DEVICE_AUTH_STATUS.NOT_CONFIGURED,
16156
+ DEVICE_AUTH_STATUS.UNAUTHORIZED,
16157
+ DEVICE_AUTH_STATUS.ALREADY_DECIDED,
16158
+ DEVICE_AUTH_STATUS.TERMS_REQUIRED
16159
+ ]),
16160
+ message: external_exports.string().min(1)
16161
+ }).strict();
16162
+ var devicePollResponseSchema = external_exports.union([
16163
+ devicePollPendingSchema,
16164
+ devicePollApprovedSchema,
16165
+ deviceAuthRefusalSchema
16166
+ ]);
16167
+ var deviceConfirmRequestSchema = external_exports.object({
16168
+ userCode: external_exports.string().min(1),
16169
+ decision: external_exports.enum(["approve", "decline"]),
16170
+ externalAuthId: external_exports.string().min(1).optional(),
16171
+ email: external_exports.string().optional(),
16172
+ acceptTerms: external_exports.boolean().optional()
16173
+ }).strict();
16174
+ var deviceConfirmRecordedSchema = external_exports.object({
16175
+ status: external_exports.literal(DEVICE_AUTH_STATUS.RECORDED),
16176
+ accountId: external_exports.string().min(1).optional()
16177
+ }).strict();
16178
+ var deviceConfirmResponseSchema = external_exports.union([
16179
+ deviceConfirmRecordedSchema,
16180
+ deviceAuthRefusalSchema
16181
+ ]);
16182
+ var DASHBOARD_URL = "https://app.halfcycle.ai";
16183
+
16184
+ // ../events/dist/project-page.js
16185
+ function projectPageUrl(engagementId) {
16186
+ return isEngagementId(engagementId) ? `${DASHBOARD_URL}/${encodeURIComponent(engagementId)}` : void 0;
16187
+ }
16188
+ function requestAccessSentence(engagementId) {
16189
+ const page = projectPageUrl(engagementId) ?? "this project's page on Halfcycle";
16190
+ return `To ask for access, open ${page} signed in as this account and choose "Request access". Then let the project's owner know, so they can approve it.`;
16191
+ }
16192
+
15298
16193
  // ../events/dist/credential-wire.js
15299
16194
  var engagementCredentialResponseSchema = external_exports.object({
15300
16195
  engagementId: external_exports.string(),
@@ -15324,12 +16219,60 @@ var NOT_THIS_ACCOUNT_SENTENCE = `Halfcycle is not connected: this project belong
15324
16219
  var UPDATE_CLAUDE_CODE_SENTENCE = "Halfcycle is not connected: this Claude Code version does not name the server it connects to, so no credential was sent. Update Claude Code, then reopen.";
15325
16220
  var CANNOT_REACH_SENTENCE = "Halfcycle is not connected: its server could not be reached, so its tools are unavailable in this session. Check your network, then reopen the project.";
15326
16221
  var SERVICE_PROBLEM_SENTENCE = "Halfcycle is not connected: its service had a problem just now, so nothing in this session is being checked. Reopen the project to try again.";
16222
+ var SCOPE_UNCHECKED_SENTENCE = "If this project needs approval before building, that cannot be checked either, so work done now shows as built without approval unless already approved.";
15327
16223
  var PLACEHOLDER_SENTENCES = Object.freeze({
15328
16224
  [NO_CREDENTIAL_PLACEHOLDER]: NO_CREDENTIAL_SENTENCE,
15329
16225
  [NOT_THIS_ACCOUNT_PLACEHOLDER]: NOT_THIS_ACCOUNT_SENTENCE,
15330
16226
  [UPDATE_CLAUDE_CODE_PLACEHOLDER]: UPDATE_CLAUDE_CODE_SENTENCE
15331
16227
  });
15332
16228
 
16229
+ // ../events/dist/scope-chat.js
16230
+ var chatScopeApproveRequestSchema = external_exports.object({
16231
+ act: external_exports.literal("approve"),
16232
+ reference: phaseIdentitySchema,
16233
+ planVersion: planVersionSchema
16234
+ }).strict();
16235
+ var chatScopeSendBackRequestSchema = external_exports.object({
16236
+ act: external_exports.literal("send-back"),
16237
+ reference: phaseIdentitySchema,
16238
+ planVersion: planVersionSchema,
16239
+ note: scopeNoteWireSchema.optional()
16240
+ }).strict();
16241
+ var chatScopeRequestSchema = external_exports.discriminatedUnion("act", [
16242
+ chatScopeApproveRequestSchema,
16243
+ chatScopeSendBackRequestSchema
16244
+ ]);
16245
+ var CHAT_SCOPE_ACTS = ["approve", "send-back"];
16246
+ var chatScopeRecordedSchema = external_exports.object({
16247
+ outcome: external_exports.literal("recorded"),
16248
+ act: external_exports.enum(CHAT_SCOPE_ACTS),
16249
+ phase: scopePhaseSchema,
16250
+ planVersion: planVersionSchema
16251
+ }).strict();
16252
+ var chatScopeAlreadyApprovedSchema = external_exports.object({
16253
+ outcome: external_exports.literal("already-approved"),
16254
+ replanNeeded: external_exports.boolean()
16255
+ }).strict();
16256
+ var chatScopeRefusedSchema = external_exports.object({
16257
+ outcome: external_exports.literal("refused"),
16258
+ problems: external_exports.array(systemDescriptionProblemSchema).min(1)
16259
+ }).strict();
16260
+ function bareOutcome(outcome) {
16261
+ return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
16262
+ }
16263
+ var chatScopeResultSchema = external_exports.discriminatedUnion("outcome", [
16264
+ chatScopeRecordedSchema,
16265
+ chatScopeAlreadyApprovedSchema,
16266
+ chatScopeRefusedSchema,
16267
+ bareOutcome("show-first"),
16268
+ bareOutcome("plan-changed"),
16269
+ bareOutcome("build-started"),
16270
+ bareOutcome("not-owner"),
16271
+ bareOutcome("no-owner"),
16272
+ bareOutcome("not-offered"),
16273
+ bareOutcome("unavailable")
16274
+ ]);
16275
+
15333
16276
  // dist/hook-protocol.js
15334
16277
  import { writeSync } from "node:fs";
15335
16278
  function emitBlock(reason) {
@@ -15385,8 +16328,8 @@ This is not a service outage: the guard service answered. The suspect is this re
15385
16328
  }
15386
16329
  var UNCONFIGURED_EXIT_TOOL_USE = 2;
15387
16330
  var UNCONFIGURED_EXIT_STOP_FAMILY = 1;
15388
- function emitNeverConfigured(exitCode, reason) {
15389
- writeSync(2, neverConfiguredReport(reason));
16331
+ function emitNeverConfigured(exitCode, reason, engagementId) {
16332
+ writeSync(2, neverConfiguredReport(reason, void 0, engagementId));
15390
16333
  process.exitCode = exitCode;
15391
16334
  }
15392
16335
  var SIGN_IN_COMMAND = '"npx halfcycle"';
@@ -15416,16 +16359,16 @@ ${NOT_A_SERVICE_OUTAGE} Nothing was sent: this job holds no credential and was n
15416
16359
  ${CI_OLDER_SETUP}
15417
16360
  `;
15418
16361
  }
15419
- function notThisAccountReport(label) {
15420
- return `${label} This project belongs to another Halfcycle account. Halfcycle is Solo today \u2014 one person works on a project at a time \u2014 so no credential exists for this machine and none will be issued. Nothing here needs fixing; this copy simply is not checked. Ask the project's owner for view access, or \u2014 if this account is wrong \u2014 sign in as the one that owns the project and run "npx halfcycle" again.
16362
+ function notThisAccountReport(label, engagementId) {
16363
+ return `${label} This project belongs to another Halfcycle account. Halfcycle is Solo today \u2014 one person works on a project at a time \u2014 so no credential exists for this machine and none will be issued. Nothing here needs fixing; this copy simply is not checked. ${requestAccessSentence(engagementId)} If this account is wrong, sign in as the one that owns the project and run "npx halfcycle" again.
15421
16364
  `;
15422
16365
  }
15423
- function neverConfiguredReport(reason, label = "[Halfcycle]") {
16366
+ function neverConfiguredReport(reason, label = "[Halfcycle]", engagementId) {
15424
16367
  switch (reason) {
15425
16368
  case "no-credential-yet":
15426
16369
  return neverConfiguredMessage(HOOK_REMEDY, label);
15427
16370
  case "not-this-account":
15428
- return notThisAccountReport(label);
16371
+ return notThisAccountReport(label, engagementId);
15429
16372
  }
15430
16373
  }
15431
16374
  var CREDENTIAL_REJECTED_STATUSES = /* @__PURE__ */ new Set([401, 403]);
@@ -15476,14 +16419,14 @@ function credentialRenewalFailedMessage(remedy, label = "[Halfcycle]") {
15476
16419
  ${remedy}
15477
16420
  `;
15478
16421
  }
15479
- function noCoverageStatement(reason, remedySaid = false) {
16422
+ function noCoverageStatement(reason, remedySaid = false, engagementId) {
15480
16423
  if (reason === "not-this-account") {
15481
16424
  return [
15482
16425
  "[halfcycle] This project belongs to another Halfcycle account, so nothing here is checked.",
15483
16426
  "[halfcycle] Your work carries on exactly as it would without Halfcycle \u2014 no edit is",
15484
16427
  "[halfcycle] evaluated, and nothing you do is reported as having passed.",
15485
16428
  "[halfcycle] Halfcycle is Solo today \u2014 one developer per project \u2014 with Team plans coming.",
15486
- "[halfcycle] Ask the project's owner for view access.",
16429
+ `[halfcycle] ${requestAccessSentence(engagementId)}`,
15487
16430
  "[halfcycle] If this account is wrong, sign in as the one that owns the project and run",
15488
16431
  '[halfcycle] "npx halfcycle" again.'
15489
16432
  ].join("\n");
@@ -15915,16 +16858,16 @@ function detectEnvReads(diffContent, jsComments) {
15915
16858
  if (raw.startsWith("-") && !raw.startsWith("---"))
15916
16859
  continue;
15917
16860
  const isSource = raw.startsWith("+") && !raw.startsWith("+++") || raw.startsWith(" ");
15918
- const line = jsComments && isSource ? raw.slice(1) : raw;
15919
- const comment = jsComments && isSource ? commentedOut(line) : void 0;
16861
+ const line2 = jsComments && isSource ? raw.slice(1) : raw;
16862
+ const comment = jsComments && isSource ? commentedOut(line2) : void 0;
15920
16863
  const live = (match) => comment === void 0 || !comment(match.index, match.index + match[0].length);
15921
- for (const match of line.matchAll(dotPattern))
16864
+ for (const match of line2.matchAll(dotPattern))
15922
16865
  if (live(match))
15923
16866
  found.add(match[1]);
15924
- for (const match of line.matchAll(bracketPattern))
16867
+ for (const match of line2.matchAll(bracketPattern))
15925
16868
  if (live(match))
15926
16869
  found.add(match[1]);
15927
- for (const destructure of line.matchAll(destructurePattern)) {
16870
+ for (const destructure of line2.matchAll(destructurePattern)) {
15928
16871
  if (!live(destructure))
15929
16872
  continue;
15930
16873
  for (const part of destructure[1].split(",")) {
@@ -15937,25 +16880,25 @@ function detectEnvReads(diffContent, jsComments) {
15937
16880
  return found;
15938
16881
  }
15939
16882
  var LINE_TERMINATOR = /[\r\u2028\u2029]/g;
15940
- function commentedOut(line) {
15941
- const lead = line.length - line.trimStart().length;
16883
+ function commentedOut(line2) {
16884
+ const lead = line2.length - line2.trimStart().length;
15942
16885
  let body;
15943
16886
  let close;
15944
16887
  let expression;
15945
- if (line.startsWith("//", lead)) {
16888
+ if (line2.startsWith("//", lead)) {
15946
16889
  body = lead + 2;
15947
16890
  LINE_TERMINATOR.lastIndex = body;
15948
- close = LINE_TERMINATOR.exec(line)?.index ?? line.length;
16891
+ close = LINE_TERMINATOR.exec(line2)?.index ?? line2.length;
15949
16892
  expression = "{";
15950
- } else if (line.startsWith("/*", lead) || line.startsWith("{/*", lead)) {
15951
- body = lead + (line[lead] === "{" ? 3 : 2);
15952
- const found = line.indexOf("*/", body);
15953
- close = found === -1 ? line.length : found;
15954
- expression = line[lead] === "{" ? "${" : "{";
16893
+ } else if (line2.startsWith("/*", lead) || line2.startsWith("{/*", lead)) {
16894
+ body = lead + (line2[lead] === "{" ? 3 : 2);
16895
+ const found = line2.indexOf("*/", body);
16896
+ close = found === -1 ? line2.length : found;
16897
+ expression = line2[lead] === "{" ? "${" : "{";
15955
16898
  } else {
15956
16899
  return void 0;
15957
16900
  }
15958
- const opened = line.indexOf(expression, body);
16901
+ const opened = line2.indexOf(expression, body);
15959
16902
  return (start, end) => start >= body && end <= close && (opened === -1 || opened >= start || opened >= close);
15960
16903
  }
15961
16904
  function collectDeclarationFiles(context) {
@@ -16034,8 +16977,8 @@ function flyTomlSectionHas(name, content, sectionHeader) {
16034
16977
  const lines = content.split("\n");
16035
16978
  let inSection = false;
16036
16979
  const keyPattern = new RegExp(`^${name}\\s*=`);
16037
- for (const line of lines) {
16038
- const trimmed = line.trim();
16980
+ for (const line2 of lines) {
16981
+ const trimmed = line2.trim();
16039
16982
  if (trimmed === sectionHeader) {
16040
16983
  inSection = true;
16041
16984
  continue;
@@ -16086,10 +17029,10 @@ function extractSqlWrites(diffContent) {
16086
17029
  seen.add(key);
16087
17030
  writes.push(write);
16088
17031
  };
16089
- for (const line of diffContent.split("\n")) {
16090
- if (isRemovedLine(line) || isContextLine(line) || isDiffHeader(line))
17032
+ for (const line2 of diffContent.split("\n")) {
17033
+ if (isRemovedLine(line2) || isContextLine(line2) || isDiffHeader(line2))
16091
17034
  continue;
16092
- const code = stripComments(stripDiffPrefix(line));
17035
+ const code = stripComments(stripDiffPrefix(line2));
16093
17036
  push(parseRawSql(code));
16094
17037
  push(parseQueryBuilder(code));
16095
17038
  push(parseOrmCall(code));
@@ -16099,23 +17042,23 @@ function extractSqlWrites(diffContent) {
16099
17042
  }
16100
17043
  return writes;
16101
17044
  }
16102
- function isRemovedLine(line) {
16103
- return line.startsWith("-") && !line.startsWith("---");
17045
+ function isRemovedLine(line2) {
17046
+ return line2.startsWith("-") && !line2.startsWith("---");
16104
17047
  }
16105
- function isContextLine(line) {
16106
- return line.startsWith(" ");
17048
+ function isContextLine(line2) {
17049
+ return line2.startsWith(" ");
16107
17050
  }
16108
- function isDiffHeader(line) {
16109
- return line.startsWith("+++") || line.startsWith("---");
17051
+ function isDiffHeader(line2) {
17052
+ return line2.startsWith("+++") || line2.startsWith("---");
16110
17053
  }
16111
- function stripDiffPrefix(line) {
16112
- if (line.startsWith("+") && !line.startsWith("+++"))
16113
- return line.slice(1);
16114
- if (line.startsWith("-") && !line.startsWith("---"))
16115
- return line.slice(1);
16116
- if (line.startsWith(" "))
16117
- return line.slice(1);
16118
- return line;
17054
+ function stripDiffPrefix(line2) {
17055
+ if (line2.startsWith("+") && !line2.startsWith("+++"))
17056
+ return line2.slice(1);
17057
+ if (line2.startsWith("-") && !line2.startsWith("---"))
17058
+ return line2.slice(1);
17059
+ if (line2.startsWith(" "))
17060
+ return line2.slice(1);
17061
+ return line2;
16119
17062
  }
16120
17063
  function assembleStatements(diffContent) {
16121
17064
  const statements = [];
@@ -16128,16 +17071,16 @@ function assembleStatements(diffContent) {
16128
17071
  buffer = [];
16129
17072
  hasAddedLine = false;
16130
17073
  };
16131
- for (const line of diffContent.split("\n")) {
16132
- if (line.startsWith("@@") || isDiffHeader(line)) {
17074
+ for (const line2 of diffContent.split("\n")) {
17075
+ if (line2.startsWith("@@") || isDiffHeader(line2)) {
16133
17076
  flush(true);
16134
17077
  continue;
16135
17078
  }
16136
- if (isRemovedLine(line)) {
17079
+ if (isRemovedLine(line2)) {
16137
17080
  flush(false);
16138
17081
  continue;
16139
17082
  }
16140
- const rawContent = stripDiffPrefix(line);
17083
+ const rawContent = stripDiffPrefix(line2);
16141
17084
  if (rawContent.trim() === "") {
16142
17085
  flush(true);
16143
17086
  continue;
@@ -16145,7 +17088,7 @@ function assembleStatements(diffContent) {
16145
17088
  const content = stripComments(rawContent);
16146
17089
  if (content.trim() === "")
16147
17090
  continue;
16148
- if (line.startsWith("+"))
17091
+ if (line2.startsWith("+"))
16149
17092
  hasAddedLine = true;
16150
17093
  buffer.push(content.trim());
16151
17094
  if (content.includes(";"))
@@ -16162,44 +17105,44 @@ function normaliseTable(raw) {
16162
17105
  return name.toLowerCase();
16163
17106
  }
16164
17107
  var TABLE_IDENT = String.raw`((?:"[^"]+"|[a-zA-Z_][a-zA-Z0-9_]*)(?:\.(?:"[^"]+"|[a-zA-Z_][a-zA-Z0-9_]*))?)`;
16165
- function parseRawSql(line) {
16166
- if (/\bON\s+CONFLICT\b/i.test(line)) {
16167
- const m = line.match(new RegExp(String.raw`INSERT\s+INTO\s+${TABLE_IDENT}`, "i"));
17108
+ function parseRawSql(line2) {
17109
+ if (/\bON\s+CONFLICT\b/i.test(line2)) {
17110
+ const m = line2.match(new RegExp(String.raw`INSERT\s+INTO\s+${TABLE_IDENT}`, "i"));
16168
17111
  if (m)
16169
17112
  return { kind: "upsert", table: normaliseTable(m[1]), whereColumns: [] };
16170
17113
  }
16171
- const upsertM = line.match(new RegExp(String.raw`UPSERT\s+INTO\s+${TABLE_IDENT}`, "i"));
17114
+ const upsertM = line2.match(new RegExp(String.raw`UPSERT\s+INTO\s+${TABLE_IDENT}`, "i"));
16172
17115
  if (upsertM)
16173
17116
  return { kind: "upsert", table: normaliseTable(upsertM[1]), whereColumns: [] };
16174
- const insertM = line.match(new RegExp(String.raw`INSERT\s+INTO\s+${TABLE_IDENT}`, "i"));
17117
+ const insertM = line2.match(new RegExp(String.raw`INSERT\s+INTO\s+${TABLE_IDENT}`, "i"));
16175
17118
  if (insertM)
16176
17119
  return { kind: "insert", table: normaliseTable(insertM[1]), whereColumns: [] };
16177
- const updateM = line.match(new RegExp(String.raw`\bUPDATE\s+${TABLE_IDENT}\s+[\s\S]*?\bSET\b`, "i"));
17120
+ const updateM = line2.match(new RegExp(String.raw`\bUPDATE\s+${TABLE_IDENT}\s+[\s\S]*?\bSET\b`, "i"));
16178
17121
  if (updateM) {
16179
17122
  return {
16180
17123
  kind: "update",
16181
17124
  table: normaliseTable(updateM[1]),
16182
- whereColumns: extractWhereColumns(line)
17125
+ whereColumns: extractWhereColumns(line2)
16183
17126
  };
16184
17127
  }
16185
- const deleteM = line.match(new RegExp(String.raw`DELETE\s+FROM\s+${TABLE_IDENT}`, "i"));
17128
+ const deleteM = line2.match(new RegExp(String.raw`DELETE\s+FROM\s+${TABLE_IDENT}`, "i"));
16186
17129
  if (deleteM) {
16187
17130
  return {
16188
17131
  kind: "delete",
16189
17132
  table: normaliseTable(deleteM[1]),
16190
- whereColumns: extractWhereColumns(line)
17133
+ whereColumns: extractWhereColumns(line2)
16191
17134
  };
16192
17135
  }
16193
17136
  return null;
16194
17137
  }
16195
17138
  function parseQueryBuilder(rawLine) {
16196
- const line = stripDiffPrefix(rawLine);
16197
- const m = line.match(/(?<!\bcreate(?:Hash|Hmac|Sign|Verify|Cipheriv|Decipheriv)\s*\([^)]*)['"]([\w]+)['"]\s*\)\s*\.(insert|update|upsert|delete)\s*\(/i);
17139
+ const line2 = stripDiffPrefix(rawLine);
17140
+ const m = line2.match(/(?<!\bcreate(?:Hash|Hmac|Sign|Verify|Cipheriv|Decipheriv)\s*\([^)]*)['"]([\w]+)['"]\s*\)\s*\.(insert|update|upsert|delete)\s*\(/i);
16198
17141
  if (m) {
16199
17142
  const kind = m[2].toLowerCase();
16200
17143
  return { kind, table: m[1].toLowerCase(), whereColumns: [] };
16201
17144
  }
16202
- const fromM = line.match(/\.(?:from|into|table)\s*\(\s*['"]([\w]+)['"]\s*\)\s*\.(insert|update|upsert|delete)\s*\(/i);
17145
+ const fromM = line2.match(/\.(?:from|into|table)\s*\(\s*['"]([\w]+)['"]\s*\)\s*\.(insert|update|upsert|delete)\s*\(/i);
16203
17146
  if (fromM) {
16204
17147
  const kind = fromM[2].toLowerCase();
16205
17148
  return { kind, table: fromM[1].toLowerCase(), whereColumns: [] };
@@ -16223,11 +17166,11 @@ var TYPEORM_KIND = {
16223
17166
  delete: "delete"
16224
17167
  };
16225
17168
  function parseOrmCall(rawLine) {
16226
- const line = stripDiffPrefix(rawLine);
16227
- return parsePrisma(line) ?? parseTypeorm(line) ?? parseDrizzle(line);
17169
+ const line2 = stripDiffPrefix(rawLine);
17170
+ return parsePrisma(line2) ?? parseTypeorm(line2) ?? parseDrizzle(line2);
16228
17171
  }
16229
- function parsePrisma(line) {
16230
- const m = line.match(/\b[\w$]+\.([a-zA-Z_$][\w$]*)\.(create|createMany|update|updateMany|upsert|delete|deleteMany)\s*\(/);
17172
+ function parsePrisma(line2) {
17173
+ const m = line2.match(/\b[\w$]+\.([a-zA-Z_$][\w$]*)\.(create|createMany|update|updateMany|upsert|delete|deleteMany)\s*\(/);
16231
17174
  if (!m)
16232
17175
  return null;
16233
17176
  const kind = PRISMA_KIND[m[2].toLowerCase()];
@@ -16235,29 +17178,29 @@ function parsePrisma(line) {
16235
17178
  return null;
16236
17179
  return { kind, table: m[1].toLowerCase(), whereColumns: [] };
16237
17180
  }
16238
- function parseDrizzle(line) {
16239
- const upd = line.match(/\b[\w$]+\.update\s*\(\s*['"]?([\w$]+)['"]?\s*\)\s*\.set\s*\(/);
17181
+ function parseDrizzle(line2) {
17182
+ const upd = line2.match(/\b[\w$]+\.update\s*\(\s*['"]?([\w$]+)['"]?\s*\)\s*\.set\s*\(/);
16240
17183
  if (upd)
16241
17184
  return { kind: "update", table: upd[1].toLowerCase(), whereColumns: [] };
16242
- const ins = line.match(/\b[\w$]+\.insert\s*\(\s*['"]?([\w$]+)['"]?\s*\)/);
17185
+ const ins = line2.match(/\b[\w$]+\.insert\s*\(\s*['"]?([\w$]+)['"]?\s*\)/);
16243
17186
  if (ins)
16244
17187
  return { kind: "insert", table: ins[1].toLowerCase(), whereColumns: [] };
16245
- const del = line.match(/\b[\w$]+\.delete\s*\(\s*['"]?([\w$]+)['"]?\s*\)/);
17188
+ const del = line2.match(/\b[\w$]+\.delete\s*\(\s*['"]?([\w$]+)['"]?\s*\)/);
16246
17189
  if (del)
16247
17190
  return { kind: "delete", table: del[1].toLowerCase(), whereColumns: [] };
16248
17191
  return null;
16249
17192
  }
16250
- function parseTypeorm(line) {
16251
- const qb = line.match(/\.createQueryBuilder\s*\([^)]*\)[\s\S]*?\.update\s*\(\s*([a-zA-Z_$][\w$]*)/);
17193
+ function parseTypeorm(line2) {
17194
+ const qb = line2.match(/\.createQueryBuilder\s*\([^)]*\)[\s\S]*?\.update\s*\(\s*([a-zA-Z_$][\w$]*)/);
16252
17195
  if (qb)
16253
17196
  return { kind: "update", table: qb[1].toLowerCase(), whereColumns: [] };
16254
- const saveUpsert = line.match(/(?:^|[^.\w$])([a-zA-Z_$][\w$]*)\.(save|upsert)\s*\(/);
17197
+ const saveUpsert = line2.match(/(?:^|[^.\w$])([a-zA-Z_$][\w$]*)\.(save|upsert)\s*\(/);
16255
17198
  if (saveUpsert) {
16256
17199
  const kind = TYPEORM_KIND[saveUpsert[2].toLowerCase()];
16257
17200
  if (kind)
16258
17201
  return { kind, table: saveUpsert[1].toLowerCase(), whereColumns: [] };
16259
17202
  }
16260
- const repo = line.match(/(?:^|[^.\w$])([a-zA-Z_$][\w$]*(?:repo|repository))\.(insert|update|delete)\s*\(/i);
17203
+ const repo = line2.match(/(?:^|[^.\w$])([a-zA-Z_$][\w$]*(?:repo|repository))\.(insert|update|delete)\s*\(/i);
16261
17204
  if (repo) {
16262
17205
  const kind = TYPEORM_KIND[repo[2].toLowerCase()];
16263
17206
  if (kind)
@@ -16265,8 +17208,8 @@ function parseTypeorm(line) {
16265
17208
  }
16266
17209
  return null;
16267
17210
  }
16268
- function extractWhereColumns(line) {
16269
- const whereM = line.match(/\bWHERE\s+(.+)/i);
17211
+ function extractWhereColumns(line2) {
17212
+ const whereM = line2.match(/\bWHERE\s+(.+)/i);
16270
17213
  if (!whereM)
16271
17214
  return [];
16272
17215
  const fragment = whereM[1].replace(/\s+(?:ORDER|LIMIT|GROUP|HAVING)\b.*/i, "");
@@ -16282,17 +17225,17 @@ function extractSchemaConstraints(diffContent) {
16282
17225
  const results = [];
16283
17226
  const seen = /* @__PURE__ */ new Set();
16284
17227
  let currentTable;
16285
- for (const line of diffContent.split("\n")) {
16286
- if (line.startsWith("-") && !line.startsWith("---")) {
17228
+ for (const line2 of diffContent.split("\n")) {
17229
+ if (line2.startsWith("-") && !line2.startsWith("---")) {
16287
17230
  currentTable = void 0;
16288
17231
  continue;
16289
17232
  }
16290
- if (line.startsWith("@@")) {
17233
+ if (line2.startsWith("@@")) {
16291
17234
  currentTable = void 0;
16292
17235
  continue;
16293
17236
  }
16294
- const isAdded = line.startsWith("+") && !line.startsWith("+++");
16295
- const raw = stripDiffPrefix2(line);
17237
+ const isAdded = line2.startsWith("+") && !line2.startsWith("+++");
17238
+ const raw = stripDiffPrefix2(line2);
16296
17239
  if (raw.trim() === "") {
16297
17240
  currentTable = void 0;
16298
17241
  continue;
@@ -16316,22 +17259,22 @@ function extractSchemaConstraints(diffContent) {
16316
17259
  }
16317
17260
  return results;
16318
17261
  }
16319
- function stripDiffPrefix2(line) {
16320
- if (line.startsWith("+") && !line.startsWith("+++"))
16321
- return line.slice(1);
16322
- if (line.startsWith(" "))
16323
- return line.slice(1);
16324
- return line;
17262
+ function stripDiffPrefix2(line2) {
17263
+ if (line2.startsWith("+") && !line2.startsWith("+++"))
17264
+ return line2.slice(1);
17265
+ if (line2.startsWith(" "))
17266
+ return line2.slice(1);
17267
+ return line2;
16325
17268
  }
16326
- function parseConstraintLine(line, trackedTable) {
16327
- const alterTable = extractAlterTableName(line) ?? trackedTable ?? "unknown";
16328
- const createUniqueIndex = line.match(new RegExp(String.raw`CREATE\s+UNIQUE\s+INDEX\s+(?:CONCURRENTLY\s+)?(?:(?:IF\s+NOT\s+EXISTS\s+)?(\w+)\s+)?ON\s+${TABLE_IDENT2}`, "i"));
17269
+ function parseConstraintLine(line2, trackedTable) {
17270
+ const alterTable = extractAlterTableName(line2) ?? trackedTable ?? "unknown";
17271
+ const createUniqueIndex = line2.match(new RegExp(String.raw`CREATE\s+UNIQUE\s+INDEX\s+(?:CONCURRENTLY\s+)?(?:(?:IF\s+NOT\s+EXISTS\s+)?(\w+)\s+)?ON\s+${TABLE_IDENT2}`, "i"));
16329
17272
  if (createUniqueIndex) {
16330
17273
  const constraintName = createUniqueIndex[1] ?? void 0;
16331
17274
  const table = normaliseTable2(createUniqueIndex[2]);
16332
17275
  return { table, kind: "unique", ...constraintName ? { constraintName } : {} };
16333
17276
  }
16334
- const addConstraint = line.match(/ADD\s+CONSTRAINT\s+(\w+)\s+(CHECK|UNIQUE|FOREIGN\s+KEY)\b/i);
17277
+ const addConstraint = line2.match(/ADD\s+CONSTRAINT\s+(\w+)\s+(CHECK|UNIQUE|FOREIGN\s+KEY)\b/i);
16335
17278
  if (addConstraint) {
16336
17279
  const constraintName = addConstraint[1];
16337
17280
  const kindRaw = addConstraint[2].toUpperCase();
@@ -16345,22 +17288,22 @@ function parseConstraintLine(line, trackedTable) {
16345
17288
  }
16346
17289
  return { table: alterTable, kind, constraintName };
16347
17290
  }
16348
- const addCheck = line.match(/ADD\s+CHECK\s*\(/i);
17291
+ const addCheck = line2.match(/ADD\s+CHECK\s*\(/i);
16349
17292
  if (addCheck) {
16350
17293
  return { table: alterTable, kind: "check" };
16351
17294
  }
16352
- const addUnique = line.match(/ADD\s+UNIQUE\s*\(/i);
17295
+ const addUnique = line2.match(/ADD\s+UNIQUE\s*\(/i);
16353
17296
  if (addUnique) {
16354
17297
  return { table: alterTable, kind: "unique" };
16355
17298
  }
16356
- const addForeignKey = line.match(/ADD\s+FOREIGN\s+KEY\s*\(/i);
17299
+ const addForeignKey = line2.match(/ADD\s+FOREIGN\s+KEY\s*\(/i);
16357
17300
  if (addForeignKey) {
16358
17301
  return { table: alterTable, kind: "foreignKey" };
16359
17302
  }
16360
17303
  return null;
16361
17304
  }
16362
- function extractAlterTableName(line) {
16363
- const m = line.match(new RegExp(String.raw`ALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?${TABLE_IDENT2}`, "i"));
17305
+ function extractAlterTableName(line2) {
17306
+ const m = line2.match(new RegExp(String.raw`ALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?${TABLE_IDENT2}`, "i"));
16364
17307
  return m ? normaliseTable2(m[1]) : void 0;
16365
17308
  }
16366
17309
  var TABLE_IDENT2 = String.raw`((?:"[^"]+"|[a-zA-Z_][a-zA-Z0-9_]*)(?:\.(?:"[^"]+"|[a-zA-Z_][a-zA-Z0-9_]*))?)`;
@@ -16381,10 +17324,10 @@ function detectBlastRadiusMarkers(filePath, diffContent) {
16381
17324
  markerKind: "patch-every-field-path"
16382
17325
  });
16383
17326
  }
16384
- for (const line of diffContent.split("\n")) {
16385
- if (line.startsWith("-") && !line.startsWith("---"))
17327
+ for (const line2 of diffContent.split("\n")) {
17328
+ if (line2.startsWith("-") && !line2.startsWith("---"))
16386
17329
  continue;
16387
- const symbolMatch = line.match(/\bUPDATABLE_FIELDS(?:_FOR_(\w+))?\b/);
17330
+ const symbolMatch = line2.match(/\bUPDATABLE_FIELDS(?:_FOR_(\w+))?\b/);
16388
17331
  if (symbolMatch) {
16389
17332
  const table = symbolMatch[1] ? symbolMatch[1].toLowerCase() : "unknown";
16390
17333
  markers.push({ table, markerKind: "updatable-fields-symbol" });
@@ -16398,10 +17341,10 @@ import { readFileSync as readFileSync2, readdirSync } from "node:fs";
16398
17341
  import { join } from "node:path";
16399
17342
  function extractNumericColumns(diffContent, context = {}) {
16400
17343
  const addedLines = [];
16401
- for (const line of diffContent.split("\n")) {
16402
- if (line.startsWith("-") && !line.startsWith("---"))
17344
+ for (const line2 of diffContent.split("\n")) {
17345
+ if (line2.startsWith("-") && !line2.startsWith("---"))
16403
17346
  continue;
16404
- addedLines.push(stripDiffPrefix3(line));
17347
+ addedLines.push(stripDiffPrefix3(line2));
16405
17348
  }
16406
17349
  const columnDecls = extractColumnDeclarations(addedLines);
16407
17350
  if (columnDecls.length === 0)
@@ -16480,19 +17423,19 @@ function walkForRegistrations(dir, repoRoot, acc) {
16480
17423
  acc.add(m[1]);
16481
17424
  }
16482
17425
  }
16483
- function stripDiffPrefix3(line) {
16484
- if (line.startsWith("+") && !line.startsWith("+++"))
16485
- return line.slice(1);
16486
- if (line.startsWith(" "))
16487
- return line.slice(1);
16488
- return line;
17426
+ function stripDiffPrefix3(line2) {
17427
+ if (line2.startsWith("+") && !line2.startsWith("+++"))
17428
+ return line2.slice(1);
17429
+ if (line2.startsWith(" "))
17430
+ return line2.slice(1);
17431
+ return line2;
16489
17432
  }
16490
17433
  function extractColumnDeclarations(lines) {
16491
17434
  const decls = [];
16492
17435
  let currentTable = null;
16493
17436
  let insideCreateTable = false;
16494
- for (const line of lines) {
16495
- const trimmed = line.trim();
17437
+ for (const line2 of lines) {
17438
+ const trimmed = line2.trim();
16496
17439
  const alterAdd = trimmed.match(/ALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?(\w+)\s+ADD\s+(?:COLUMN\s+)?(?:IF\s+NOT\s+EXISTS\s+)?(\w+)\s+(NUMERIC|BIGINT|DECIMAL)(?:\s*\([^)]*\))?/i);
16497
17440
  if (alterAdd) {
16498
17441
  const table = alterAdd[1].toLowerCase();
@@ -16519,8 +17462,8 @@ function extractColumnDeclarations(lines) {
16519
17462
  }
16520
17463
  return decls;
16521
17464
  }
16522
- function checkInlineColumn(line, table, decls) {
16523
- const colMatch = line.match(/^\s*(\w+)\s+(NUMERIC|BIGINT|DECIMAL)(?:\s*\([^)]*\))?(?:\s|,|$)/i);
17465
+ function checkInlineColumn(line2, table, decls) {
17466
+ const colMatch = line2.match(/^\s*(\w+)\s+(NUMERIC|BIGINT|DECIMAL)(?:\s*\([^)]*\))?(?:\s|,|$)/i);
16524
17467
  if (colMatch) {
16525
17468
  const name = colMatch[1].toUpperCase();
16526
17469
  if (SQL_KEYWORDS.has(name))
@@ -16559,8 +17502,8 @@ function resolveWireFields(lines, decls) {
16559
17502
  existing.push(decl);
16560
17503
  colToDecls.set(decl.column, existing);
16561
17504
  }
16562
- for (const line of lines) {
16563
- const trimmed = line.trim();
17505
+ for (const line2 of lines) {
17506
+ const trimmed = line2.trim();
16564
17507
  const assignment = trimmed.match(/^(\w+)\s*:\s*(?:\w+\.)?(\w+)\s*[,;]?\s*(?:\/\/.*)?$/);
16565
17508
  if (assignment) {
16566
17509
  const wireField = assignment[1];
@@ -16678,10 +17621,10 @@ function extractThirdPartyEndpoints(diffContent, context = {}) {
16678
17621
  }
16679
17622
  function detectEndpointMarkers(diffContent) {
16680
17623
  const detected = [];
16681
- for (const line of diffContent.split("\n")) {
16682
- if (line.startsWith("-") && !line.startsWith("---"))
17624
+ for (const line2 of diffContent.split("\n")) {
17625
+ if (line2.startsWith("-") && !line2.startsWith("---"))
16683
17626
  continue;
16684
- const stripped = stripDiffPrefix4(line).trim();
17627
+ const stripped = stripDiffPrefix4(line2).trim();
16685
17628
  const decoratorSimple = stripped.match(/@ThirdParty(?:Endpoint)?\s*\(\s*['"]([^'"]+)['"]\s*\)/);
16686
17629
  if (decoratorSimple) {
16687
17630
  detected.push({ signature: decoratorSimple[1] });
@@ -16728,12 +17671,12 @@ function detectEndpointMarkers(diffContent) {
16728
17671
  }
16729
17672
  return detected;
16730
17673
  }
16731
- function stripDiffPrefix4(line) {
16732
- if (line.startsWith("+") && !line.startsWith("+++"))
16733
- return line.slice(1);
16734
- if (line.startsWith(" "))
16735
- return line.slice(1);
16736
- return line;
17674
+ function stripDiffPrefix4(line2) {
17675
+ if (line2.startsWith("+") && !line2.startsWith("+++"))
17676
+ return line2.slice(1);
17677
+ if (line2.startsWith(" "))
17678
+ return line2.slice(1);
17679
+ return line2;
16737
17680
  }
16738
17681
 
16739
17682
  // dist/changeset.js
@@ -17269,8 +18212,36 @@ function cleanRunSummary(verdict, scope) {
17269
18212
  return `[Halfcycle CI] ${verdict} ${files}, ${scope.signals} signal(s) checked.`;
17270
18213
  }
17271
18214
 
18215
+ // dist/pinned-engagement.js
18216
+ import { readFileSync as readFileSync5 } from "node:fs";
18217
+ import { resolve } from "node:path";
18218
+ function readPinnedId(root) {
18219
+ let raw;
18220
+ try {
18221
+ raw = readFileSync5(resolve(root, BUNDLE_PIN_REL), "utf-8");
18222
+ } catch {
18223
+ return { problem: "no-pin" };
18224
+ }
18225
+ let pin;
18226
+ try {
18227
+ pin = JSON.parse(raw);
18228
+ } catch {
18229
+ return { problem: "no-id" };
18230
+ }
18231
+ const id = typeof pin === "object" && pin !== null ? pin[PIN_ENGAGEMENT_ID_FIELD] : void 0;
18232
+ return typeof id === "string" && id !== "" ? { id } : { problem: "no-id" };
18233
+ }
18234
+ function pinnedEngagementId(root) {
18235
+ const pinned = readPinnedId(root);
18236
+ return "id" in pinned && isEngagementId(pinned.id) ? pinned.id : void 0;
18237
+ }
18238
+ function projectFolder(env = process.env) {
18239
+ const dir = env["CLAUDE_PROJECT_DIR"];
18240
+ return dir !== void 0 && dir !== "" ? dir : process.cwd();
18241
+ }
18242
+
17272
18243
  // dist/credential-store.js
17273
- import { chmodSync, existsSync as existsSync3, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2, realpathSync } from "node:fs";
18244
+ import { chmodSync, existsSync as existsSync3, readFileSync as readFileSync6, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2, realpathSync } from "node:fs";
17274
18245
  import { homedir as homedir2, platform } from "node:os";
17275
18246
  import { dirname as dirname3, join as join5 } from "node:path";
17276
18247
  var REFRESHED_TOKEN_KEYS = ["HALFCYCLE_TOKEN", "GUARD_SERVICE_TOKEN"];
@@ -17284,7 +18255,7 @@ function hasNotThisAccountMarker(engagementId, home) {
17284
18255
  function readAccountCredential(controlOrigin, home) {
17285
18256
  let raw;
17286
18257
  try {
17287
- raw = readFileSync5(join5(home ?? homedir2(), HALFCYCLE_DIR_NAME, ACCOUNT_STORE_FILENAME), "utf-8");
18258
+ raw = readFileSync6(join5(home ?? homedir2(), HALFCYCLE_DIR_NAME, ACCOUNT_STORE_FILENAME), "utf-8");
17288
18259
  } catch {
17289
18260
  return void 0;
17290
18261
  }
@@ -17301,7 +18272,7 @@ function readStoredEngagementToken(envFilePath) {
17301
18272
  function readEngagementStore(envFilePath) {
17302
18273
  let raw;
17303
18274
  try {
17304
- raw = readFileSync5(envFilePath, "utf-8");
18275
+ raw = readFileSync6(envFilePath, "utf-8");
17305
18276
  } catch {
17306
18277
  return void 0;
17307
18278
  }
@@ -17331,7 +18302,7 @@ function writeRefreshedToken(opts) {
17331
18302
  }
17332
18303
  let existing;
17333
18304
  try {
17334
- existing = readFileSync5(declaredReal, "utf-8");
18305
+ existing = readFileSync6(declaredReal, "utf-8");
17335
18306
  } catch {
17336
18307
  existing = null;
17337
18308
  }
@@ -17358,6 +18329,9 @@ function writeRefreshedToken(opts) {
17358
18329
  // dist/credential-refresh.js
17359
18330
  var CONTROL_TIMEOUT_MS = 5e3;
17360
18331
  var CONNECTION_CHECK_DEADLINE_MS = 3e3;
18332
+ var SCOPE_NOTICE_DEADLINE_MS = 1500;
18333
+ var SESSION_START_HOOK_TIMEOUT_MS = 1e4;
18334
+ var SESSION_START_MARGIN_MS = 1500;
17361
18335
  var UNAUTHORIZED = 401;
17362
18336
  async function probeAccount(controlOrigin, credential, fetchFn = globalThis.fetch) {
17363
18337
  const controller = new AbortController();
@@ -17471,7 +18445,7 @@ async function evaluateWithRefresh(opts) {
17471
18445
 
17472
18446
  // dist/hooks/marker.js
17473
18447
  import { createHash } from "node:crypto";
17474
- import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync } from "node:fs";
18448
+ import { existsSync as existsSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync } from "node:fs";
17475
18449
  import { tmpdir } from "node:os";
17476
18450
  import { join as join6 } from "node:path";
17477
18451
  var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -17503,7 +18477,7 @@ function readMarker(repoRoot, sessionId) {
17503
18477
  if (!existsSync4(path))
17504
18478
  return null;
17505
18479
  try {
17506
- return readFileSync6(path, "utf-8").trim() || null;
18480
+ return readFileSync7(path, "utf-8").trim() || null;
17507
18481
  } catch {
17508
18482
  return null;
17509
18483
  }
@@ -17549,7 +18523,7 @@ function readDedupe(repoRoot, sessionId) {
17549
18523
  if (!existsSync4(path))
17550
18524
  return null;
17551
18525
  try {
17552
- const parsed = JSON.parse(readFileSync6(path, "utf-8"));
18526
+ const parsed = JSON.parse(readFileSync7(path, "utf-8"));
17553
18527
  if (typeof parsed === "object" && parsed !== null && typeof parsed.base === "string" && typeof parsed.digest === "string") {
17554
18528
  return parsed;
17555
18529
  }
@@ -17594,7 +18568,7 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
17594
18568
  const full = join6(dir, name);
17595
18569
  let unusable = false;
17596
18570
  try {
17597
- const ref = readFileSync6(full, "utf-8").trim();
18571
+ const ref = readFileSync7(full, "utf-8").trim();
17598
18572
  if (!ref || !refKnown(ref))
17599
18573
  unusable = true;
17600
18574
  } catch {
@@ -17618,9 +18592,9 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
17618
18592
  }
17619
18593
 
17620
18594
  // dist/seat-configuration.js
17621
- import { existsSync as existsSync5, readFileSync as readFileSync7, realpathSync as realpathSync2 } from "node:fs";
18595
+ import { existsSync as existsSync5, readFileSync as readFileSync8, realpathSync as realpathSync2 } from "node:fs";
17622
18596
  import { homedir as homedir3 } from "node:os";
17623
- import { join as join7, relative as relative3, resolve } from "node:path";
18597
+ import { join as join7, relative as relative3, resolve as resolve2 } from "node:path";
17624
18598
  function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
17625
18599
  try {
17626
18600
  const facts = read(repoRoot);
@@ -17633,7 +18607,7 @@ function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
17633
18607
  }
17634
18608
  }
17635
18609
  function readSeatConfiguration(repoRoot) {
17636
- const given = resolve(process.env["CLAUDE_PROJECT_DIR"] || process.cwd());
18610
+ const given = resolve2(process.env["CLAUDE_PROJECT_DIR"] || process.cwd());
17637
18611
  const projectDir = realpathSync2.native(given);
17638
18612
  const home = homedir3();
17639
18613
  let realHome = home;
@@ -17649,13 +18623,13 @@ function readSeatConfiguration(repoRoot) {
17649
18623
  paths: readPathStates(projectDir, repoRoot)
17650
18624
  };
17651
18625
  }
17652
- function isPlainObject2(value) {
18626
+ function isPlainObject3(value) {
17653
18627
  return typeof value === "object" && value !== null && !Array.isArray(value);
17654
18628
  }
17655
18629
  function readSettingsFile(projectDir, rel) {
17656
18630
  let raw;
17657
18631
  try {
17658
- raw = readFileSync7(join7(projectDir, rel), "utf-8");
18632
+ raw = readFileSync8(join7(projectDir, rel), "utf-8");
17659
18633
  } catch (err) {
17660
18634
  const code = err.code;
17661
18635
  const state = code === "ENOENT" || code === "ENOTDIR" ? "absent" : "unreadable";
@@ -17667,12 +18641,12 @@ function readSettingsFile(projectDir, rel) {
17667
18641
  } catch {
17668
18642
  return { path: rel, state: "unreadable", hooks: [] };
17669
18643
  }
17670
- if (!isPlainObject2(parsed))
18644
+ if (!isPlainObject3(parsed))
17671
18645
  return { path: rel, state: "unreadable", hooks: [] };
17672
18646
  return { path: rel, state: "read", hooks: ourHooks(parsed["hooks"]) };
17673
18647
  }
17674
18648
  function ourHooks(hooks) {
17675
- if (!isPlainObject2(hooks))
18649
+ if (!isPlainObject3(hooks))
17676
18650
  return [];
17677
18651
  const rows = [];
17678
18652
  const seen = /* @__PURE__ */ new Set();
@@ -17680,7 +18654,7 @@ function ourHooks(hooks) {
17680
18654
  if (!Array.isArray(entries))
17681
18655
  continue;
17682
18656
  for (const entry of entries) {
17683
- if (!isPlainObject2(entry))
18657
+ if (!isPlainObject3(entry))
17684
18658
  continue;
17685
18659
  let matcher = null;
17686
18660
  if (Object.hasOwn(entry, "matcher")) {
@@ -17692,7 +18666,7 @@ function ourHooks(hooks) {
17692
18666
  if (!Array.isArray(commands))
17693
18667
  continue;
17694
18668
  for (const hook of commands) {
17695
- if (!isPlainObject2(hook))
18669
+ if (!isPlainObject3(hook))
17696
18670
  continue;
17697
18671
  const command = hook["command"];
17698
18672
  if (typeof command !== "string" || !HALFCYCLE_HOOK_COMMANDS.has(command))
@@ -17813,7 +18787,7 @@ async function evaluateEdit(reply, readSeat) {
17813
18787
  await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
17814
18788
  if (!firstNoticeThisSession("credential-missing", repoRoot, input.session_id))
17815
18789
  return;
17816
- emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason());
18790
+ emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()));
17817
18791
  return;
17818
18792
  }
17819
18793
  const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
@@ -17906,10 +18880,10 @@ ${guardLines}${notRunBlock}`);
17906
18880
  }
17907
18881
  }
17908
18882
  function readStdin() {
17909
- return new Promise((resolve4, reject) => {
18883
+ return new Promise((resolve5, reject) => {
17910
18884
  const chunks = [];
17911
18885
  process.stdin.on("data", (chunk) => chunks.push(chunk));
17912
- process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
18886
+ process.stdin.on("end", () => resolve5(Buffer.concat(chunks).toString("utf-8")));
17913
18887
  process.stdin.on("error", reject);
17914
18888
  });
17915
18889
  }
@@ -17994,7 +18968,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
17994
18968
  await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet, diffBase });
17995
18969
  if (!firstNoticeThisSession("credential-missing", repoRoot, sessionId))
17996
18970
  return;
17997
- emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason());
18971
+ emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()));
17998
18972
  return;
17999
18973
  }
18000
18974
  const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
@@ -18105,9 +19079,9 @@ async function readHookStdin() {
18105
19079
  }
18106
19080
  }
18107
19081
  function readStdin2() {
18108
- return new Promise((resolve4) => {
19082
+ return new Promise((resolve5) => {
18109
19083
  if (process.stdin.isTTY) {
18110
- resolve4("");
19084
+ resolve5("");
18111
19085
  return;
18112
19086
  }
18113
19087
  const chunks = [];
@@ -18117,7 +19091,7 @@ function readStdin2() {
18117
19091
  return;
18118
19092
  settled = true;
18119
19093
  clearTimeout(timer);
18120
- resolve4(Buffer.concat(chunks).toString("utf-8"));
19094
+ resolve5(Buffer.concat(chunks).toString("utf-8"));
18121
19095
  };
18122
19096
  const timer = setTimeout(done, HOOK_STDIN_GRACE_MS);
18123
19097
  process.stdin.on("data", (chunk) => chunks.push(chunk));
@@ -18138,18 +19112,237 @@ async function runStop() {
18138
19112
  await runSessionDiff(input, "stop");
18139
19113
  }
18140
19114
 
18141
- // dist/resolve-engagement.js
18142
- import { existsSync as existsSync6, readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
18143
- import { join as join8, sep as sep2 } from "node:path";
19115
+ // dist/hooks/user-prompt-submit.js
19116
+ import { readFileSync as readFileSync9 } from "node:fs";
18144
19117
  import { homedir as homedir4 } from "node:os";
19118
+ import { join as join8 } from "node:path";
19119
+
19120
+ // dist/hooks/scope-phrase.js
19121
+ var APPROVE_PREFIX = /^approve /i;
19122
+ var SEND_BACK_PREFIX = /^send back /i;
19123
+ var LINE_BREAK = /[\n\r\u000b\u000c\u0085\u2028\u2029]/;
19124
+ function scopePhraseShape(prompt) {
19125
+ if (typeof prompt !== "string")
19126
+ return null;
19127
+ const line2 = prompt.trim();
19128
+ if (LINE_BREAK.test(line2))
19129
+ return null;
19130
+ const approve = APPROVE_PREFIX.exec(line2);
19131
+ if (approve)
19132
+ return { act: "approve", rest: line2.slice(approve[0].length) };
19133
+ const sendBack = SEND_BACK_PREFIX.exec(line2);
19134
+ if (sendBack)
19135
+ return { act: "send-back", rest: line2.slice(sendBack[0].length) };
19136
+ return null;
19137
+ }
19138
+ function matchScopePhraseShape(shape, shown) {
19139
+ if (shape.act === "approve") {
19140
+ const entry = resolvePhaseReference(shape.rest, shown);
19141
+ return entry === null ? { kind: "show-first", act: "approve" } : { kind: "approve", entry, reference: shape.rest };
19142
+ }
19143
+ for (let colon = shape.rest.lastIndexOf(":"); colon > 0; colon = shape.rest.lastIndexOf(":", colon - 1)) {
19144
+ const reference = shape.rest.slice(0, colon);
19145
+ const entry = resolvePhaseReference(reference, shown);
19146
+ if (entry !== null) {
19147
+ return { kind: "send-back", entry, reference, note: shape.rest.slice(colon + 1).trim() };
19148
+ }
19149
+ }
19150
+ return { kind: "show-first", act: "send-back" };
19151
+ }
19152
+
19153
+ // dist/hooks/user-prompt-submit-lines.js
19154
+ var TELL_THE_PERSON = "Tell the person.";
19155
+ function line(text) {
19156
+ return `${text} ${TELL_THE_PERSON}`;
19157
+ }
19158
+ function nothingDone(act) {
19159
+ return act === "approve" ? "Nothing was approved" : "Nothing was sent back";
19160
+ }
19161
+ function showCommand(phase) {
19162
+ return `\`${HALFCYCLE_COMMAND} plan ${quoteForShell(phase)} --show\``;
19163
+ }
19164
+ function recordedLine(act, phase, planVersion) {
19165
+ return act === "approve" ? line(`Approved ${phase}, plan version ${planVersion}, in your session.`) : line(`Sent ${phase} back with your note, plan version ${planVersion}, in your session. The note is given first to the next session that asks what to do.`);
19166
+ }
19167
+ function notAttendedLine(act) {
19168
+ return line(`${nothingDone(act)}: approving or sending back by typing counts only when a person types it into an open session.`);
19169
+ }
19170
+ function notSignedInLine(act) {
19171
+ return line(`${nothingDone(act)}: this project is not signed in on this computer. ${HOOK_REMEDY}`);
19172
+ }
19173
+ function notOwnerLine(act) {
19174
+ return line(`${nothingDone(act)}: only the project's owner can approve or send back this scope.`);
19175
+ }
19176
+ function showFirstLine(act) {
19177
+ return line(`${nothingDone(act)}: that phase has not been listed here. List its parts first with \`${HALFCYCLE_COMMAND} plan <phase> --show\`, then type the line again.`);
19178
+ }
19179
+ function tooOldLine(act, phase) {
19180
+ return line(`${nothingDone(act)}: ${phase} was listed an hour or more ago. List it again with ${showCommand(phase)}, then type the line again within the hour.`);
19181
+ }
19182
+ function noteNeededLine() {
19183
+ return line("Nothing was sent back: a send-back needs a note after the colon saying what should change.");
19184
+ }
19185
+ function noteLooksSecretLine() {
19186
+ return line("Nothing was sent back: the note looks like it holds a password or key, so it never left this computer. Type the send-back again without it.");
19187
+ }
19188
+ function noteTooLongLine() {
19189
+ return line("Nothing was sent back: the note is too long. Shorten it and type the send-back again.");
19190
+ }
19191
+ function noteRefusedLine() {
19192
+ return line("Nothing was sent back: the note was not accepted. Reword it in plain words and type the send-back again.");
19193
+ }
19194
+ function approveRefusedLine() {
19195
+ return line(`${nothingDone("approve")}: Halfcycle did not accept the request. ${CLIENT_UPDATE_SENTENCE}`);
19196
+ }
19197
+ function cannotReachLine(act) {
19198
+ return line(`${nothingDone(act)}: Halfcycle could not be reached just now. Type the line again in a minute.`);
19199
+ }
19200
+ function lineForAnswer(act, phase, answer) {
19201
+ switch (answer.outcome) {
19202
+ case "recorded":
19203
+ return recordedLine(answer.act, answer.phase, answer.planVersion);
19204
+ case "show-first":
19205
+ return showFirstLine(act);
19206
+ case "plan-changed":
19207
+ return line(`${nothingDone(act)}: the plan for ${phase} changed after it was listed here. List it again with ${showCommand(phase)}, check the parts, then type the line again.`);
19208
+ case "already-approved":
19209
+ return answer.replanNeeded ? line(`${phase} is already approved, so nothing changed. Some parts wait because the description of the system changed since the plan was written: write the plan for ${phase} again, run ${showCommand(phase)}, then approve the new version.`) : line(`${phase} is already approved, so nothing changed.`);
19210
+ case "build-started":
19211
+ return line(`${nothingDone(act)}: tasks in ${phase} have started, so its scope can no longer be sent back. A built part goes back at the phase's review.`);
19212
+ case "not-owner":
19213
+ return notOwnerLine(act);
19214
+ case "no-owner":
19215
+ return line(`${nothingDone(act)}: this project has no owner, so nobody can approve or send back its scope.`);
19216
+ case "not-offered":
19217
+ return line(`${nothingDone(act)}. ${TYPED_APPROVAL_OFF_SENTENCE}`);
19218
+ case "refused":
19219
+ return act === "approve" ? approveRefusedLine() : noteRefusedLine();
19220
+ case "unavailable":
19221
+ return cannotReachLine(act);
19222
+ default: {
19223
+ const unreachable = answer;
19224
+ return unreachable;
19225
+ }
19226
+ }
19227
+ }
19228
+
19229
+ // dist/hooks/user-prompt-submit.js
19230
+ var CHAT_REQUEST_TIMEOUT_MS = 6e3;
19231
+ var SHOWN_ENTRY_MAX_AGE_MS = 60 * 60 * 1e3;
19232
+ async function runUserPromptSubmit(opts) {
19233
+ let act = null;
19234
+ try {
19235
+ const prompt = promptOf(opts.input);
19236
+ const shape = scopePhraseShape(prompt);
19237
+ if (shape === null)
19238
+ return "";
19239
+ act = shape.act;
19240
+ return `${await decide(shape, opts)}
19241
+ `;
19242
+ } catch {
19243
+ return act === null ? "" : `${cannotReachLine(act)}
19244
+ `;
19245
+ }
19246
+ }
19247
+ async function decide(shape, opts) {
19248
+ const { act } = shape;
19249
+ const env = opts.env ?? process.env;
19250
+ if (!readSessionAttended(env))
19251
+ return notAttendedLine(act);
19252
+ const read = readConfig(env);
19253
+ if (read.config === null) {
19254
+ return readNeverConfiguredReason(env) === "not-this-account" ? notOwnerLine(act) : notSignedInLine(act);
19255
+ }
19256
+ const { guardServiceToken, guardEngagementId } = read.config;
19257
+ const shown = readShownPlans(guardEngagementId, opts.home);
19258
+ const match = matchScopePhraseShape(shape, shown);
19259
+ if (match.kind === "show-first")
19260
+ return showFirstLine(act);
19261
+ const { entry } = match;
19262
+ if (!shownRecently(entry, (opts.now ?? Date.now)()))
19263
+ return tooOldLine(act, entry.phase);
19264
+ let candidate;
19265
+ if (match.kind === "send-back") {
19266
+ if (match.note === "")
19267
+ return noteNeededLine();
19268
+ if (!scopeNoteWireSchema.safeParse(match.note).success)
19269
+ return noteTooLongLine();
19270
+ if (looksLikeSecret(match.note))
19271
+ return noteLooksSecretLine();
19272
+ candidate = { act: "send-back", reference: match.reference, planVersion: entry.version, note: match.note };
19273
+ } else {
19274
+ candidate = { act: "approve", reference: match.reference, planVersion: entry.version };
19275
+ }
19276
+ const request = chatScopeRequestSchema.safeParse(candidate);
19277
+ if (!request.success)
19278
+ return showFirstLine(act);
19279
+ return post(act, entry, request.data, guardEngagementId, guardServiceToken, env, opts.fetchFn);
19280
+ }
19281
+ function promptOf(input) {
19282
+ try {
19283
+ const parsed = JSON.parse(input);
19284
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
19285
+ return void 0;
19286
+ return parsed["prompt"];
19287
+ } catch {
19288
+ return void 0;
19289
+ }
19290
+ }
19291
+ function readShownPlans(engagementId, home) {
19292
+ if (!isEngagementId(engagementId))
19293
+ return [];
19294
+ let text;
19295
+ try {
19296
+ text = readFileSync9(join8(home ?? homedir4(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SHOWN_PLANS_FILENAME), "utf-8");
19297
+ } catch {
19298
+ return [];
19299
+ }
19300
+ return parseShownPlans(text);
19301
+ }
19302
+ function shownRecently(entry, now) {
19303
+ const age = now - Date.parse(entry.shownAt);
19304
+ return Number.isFinite(age) && age >= 0 && age < SHOWN_ENTRY_MAX_AGE_MS;
19305
+ }
19306
+ async function post(act, entry, request, engagementId, credential, env, fetchFn = globalThis.fetch) {
19307
+ const url2 = `${readControlOrigin(env).origin}/engagements/${encodeURIComponent(engagementId)}/phases/${encodeURIComponent(entry.phase)}/scope/chat`;
19308
+ const controller = new AbortController();
19309
+ const timer = setTimeout(() => controller.abort(), CHAT_REQUEST_TIMEOUT_MS);
19310
+ try {
19311
+ const response = await fetchFn(url2, {
19312
+ method: "POST",
19313
+ headers: { Authorization: `Bearer ${credential}`, "Content-Type": "application/json" },
19314
+ body: JSON.stringify(request),
19315
+ signal: controller.signal
19316
+ });
19317
+ if (response.status === 401)
19318
+ return notSignedInLine(act);
19319
+ if (response.status === 429 || response.status >= 500)
19320
+ return cannotReachLine(act);
19321
+ const parsed = chatScopeResultSchema.safeParse(await response.json());
19322
+ if (!parsed.success)
19323
+ return cannotReachLine(act);
19324
+ if (parsed.data.outcome === "recorded" && response.status !== 200)
19325
+ return cannotReachLine(act);
19326
+ return lineForAnswer(act, entry.phase, parsed.data);
19327
+ } catch {
19328
+ return cannotReachLine(act);
19329
+ } finally {
19330
+ clearTimeout(timer);
19331
+ }
19332
+ }
19333
+
19334
+ // dist/resolve-engagement.js
19335
+ import { existsSync as existsSync6, readFileSync as readFileSync10, realpathSync as realpathSync3 } from "node:fs";
19336
+ import { join as join9, sep as sep2 } from "node:path";
19337
+ import { homedir as homedir5 } from "node:os";
18145
19338
  function defaultRegistryPath() {
18146
- return join8(homedir4(), ".halfcycle", "registry.json");
19339
+ return join9(homedir5(), ".halfcycle", "registry.json");
18147
19340
  }
18148
19341
  function readRegistry(registryPath = defaultRegistryPath()) {
18149
19342
  if (!existsSync6(registryPath))
18150
19343
  return {};
18151
19344
  try {
18152
- const parsed = JSON.parse(readFileSync8(registryPath, "utf-8"));
19345
+ const parsed = JSON.parse(readFileSync10(registryPath, "utf-8"));
18153
19346
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
18154
19347
  return {};
18155
19348
  const out = {};
@@ -18197,39 +19390,42 @@ function runResolveEngagement(cwd, registryPath) {
18197
19390
  }
18198
19391
 
18199
19392
  // dist/refresh-credential.js
18200
- import { readFileSync as readFileSync10 } from "node:fs";
18201
- import { resolve as resolve3 } from "node:path";
19393
+ import { readFileSync as readFileSync12 } from "node:fs";
19394
+ import { resolve as resolve4 } from "node:path";
18202
19395
 
18203
19396
  // dist/mcp-headers.js
18204
- import { readFileSync as readFileSync9 } from "node:fs";
18205
- import { homedir as homedir5 } from "node:os";
18206
- import { dirname as dirname4, resolve as resolve2 } from "node:path";
19397
+ import { readFileSync as readFileSync11 } from "node:fs";
19398
+ import { homedir as homedir6 } from "node:os";
19399
+ import { dirname as dirname4, resolve as resolve3 } from "node:path";
18207
19400
  function defaultWriteErr(text) {
18208
19401
  process.stderr.write(text);
18209
19402
  }
18210
- function headerLine(bearer) {
18211
- return `${JSON.stringify({ Authorization: `Bearer ${bearer}` })}
19403
+ function headerLine(bearer, clientVersion) {
19404
+ const header = { Authorization: `Bearer ${bearer}` };
19405
+ if (clientVersion !== void 0)
19406
+ header[CLIENT_VERSION_HEADER] = clientVersion;
19407
+ return `${JSON.stringify(header)}
18212
19408
  `;
18213
19409
  }
18214
19410
  function projectRootFromBinary(binaryPath) {
18215
19411
  const depth = VENDORED_BIN_REL.split("/").length - 1;
18216
- return resolve2(dirname4(binaryPath), ...Array(depth).fill(".."));
19412
+ return resolve3(dirname4(binaryPath), ...Array(depth).fill(".."));
18217
19413
  }
18218
- function readPinnedId(root) {
19414
+ function readPinnedVersion(root) {
18219
19415
  let raw;
18220
19416
  try {
18221
- raw = readFileSync9(resolve2(root, BUNDLE_PIN_REL), "utf-8");
19417
+ raw = readFileSync11(resolve3(root, BUNDLE_PIN_REL), "utf-8");
18222
19418
  } catch {
18223
- return { problem: "no-pin" };
19419
+ return void 0;
18224
19420
  }
18225
19421
  let pin;
18226
19422
  try {
18227
19423
  pin = JSON.parse(raw);
18228
19424
  } catch {
18229
- return { problem: "no-id" };
19425
+ return void 0;
18230
19426
  }
18231
- const id = typeof pin === "object" && pin !== null ? pin[PIN_ENGAGEMENT_ID_FIELD] : void 0;
18232
- return typeof id === "string" && id !== "" ? { id } : { problem: "no-id" };
19427
+ const version2 = typeof pin === "object" && pin !== null ? pin["version"] : void 0;
19428
+ return typeof version2 === "string" && version2 !== "" ? version2 : void 0;
18233
19429
  }
18234
19430
  function resolveStore(root, home) {
18235
19431
  const pinned = readPinnedId(root);
@@ -18255,12 +19451,12 @@ function shownUrl(url2) {
18255
19451
  return url2.replace(/[^\x21-\x7e]/g, "").slice(0, 200);
18256
19452
  }
18257
19453
  function sentences(lines) {
18258
- return lines.map((line) => `halfcycle: ${line}
19454
+ return lines.map((line2) => `halfcycle: ${line2}
18259
19455
  `).join("");
18260
19456
  }
18261
19457
  function badIdSentence(root) {
18262
19458
  return sentences([
18263
- `${resolve2(root, BUNDLE_PIN_REL)} names an engagement id that is not a Halfcycle project id,`,
19459
+ `${resolve3(root, BUNDLE_PIN_REL)} names an engagement id that is not a Halfcycle project id,`,
18264
19460
  'so no credential was read. Delete that file and run "npx halfcycle" again: this repository',
18265
19461
  "is then set up as a new Halfcycle project."
18266
19462
  ]);
@@ -18274,7 +19470,7 @@ function storeProblemSentence(problem, root, home, engagementId) {
18274
19470
  ]);
18275
19471
  case "no-id":
18276
19472
  return sentences([
18277
- `${resolve2(root, BUNDLE_PIN_REL)} names no engagement id.`,
19473
+ `${resolve3(root, BUNDLE_PIN_REL)} names no engagement id.`,
18278
19474
  'run "npx halfcycle" here to rewrite it.'
18279
19475
  ]);
18280
19476
  case "bad-id":
@@ -18283,13 +19479,15 @@ function storeProblemSentence(problem, root, home, engagementId) {
18283
19479
  return sentences([
18284
19480
  "this project belongs to another Halfcycle account, so this machine has no",
18285
19481
  "credential for it and the Halfcycle tools are not available here. Nothing is",
18286
- "broken \u2014 ask the project's owner for view access, or sign in as the account",
18287
- 'that owns it and run "npx halfcycle" again.'
19482
+ "broken.",
19483
+ requestAccessSentence(engagementId),
19484
+ "If this account is wrong, sign in as the account that owns it and run",
19485
+ '"npx halfcycle" again.'
18288
19486
  ]);
18289
19487
  case "no-credential":
18290
19488
  return sentences([
18291
19489
  `this machine holds no credential for engagement ${engagementId ?? ""}.`,
18292
- `run "npx halfcycle" in this repository \u2014 it will sign you in through your browser if needed, and write the credential to ${resolve2(home, HALFCYCLE_DIR_NAME)}, outside this repository.`
19490
+ `run "npx halfcycle" in this repository \u2014 it will sign you in through your browser if needed, and write the credential to ${resolve3(home, HALFCYCLE_DIR_NAME)}, outside this repository.`
18293
19491
  ]);
18294
19492
  }
18295
19493
  }
@@ -18312,7 +19510,7 @@ async function refreshWithinDeadline(source, opts, writeErr) {
18312
19510
  }
18313
19511
  async function chooseBearer(opts) {
18314
19512
  const writeErr = opts.writeErr ?? defaultWriteErr;
18315
- const home = opts.home ?? homedir5();
19513
+ const home = opts.home ?? homedir6();
18316
19514
  const root = projectRootFromBinary(opts.binaryPath);
18317
19515
  const environmentToken = opts.environmentTokenFirst ? readEnvironmentToken() : void 0;
18318
19516
  const store = resolveStore(root, home);
@@ -18376,6 +19574,7 @@ async function chooseBearer(opts) {
18376
19574
  }
18377
19575
  async function mcpHeaders(opts) {
18378
19576
  const writeErr = opts.writeErr ?? defaultWriteErr;
19577
+ const root = projectRootFromBinary(opts.binaryPath);
18379
19578
  try {
18380
19579
  return headerLine(await chooseBearer({
18381
19580
  ...opts,
@@ -18383,7 +19582,7 @@ async function mcpHeaders(opts) {
18383
19582
  requestedUrl: readMcpServerUrl(),
18384
19583
  renewal: "renew-here",
18385
19584
  environmentTokenFirst: true
18386
- }));
19585
+ }), readPinnedVersion(root));
18387
19586
  } catch (err) {
18388
19587
  try {
18389
19588
  writeErr(sentences([
@@ -18391,9 +19590,9 @@ async function mcpHeaders(opts) {
18391
19590
  `run "npx halfcycle" in this repository to rewrite this machine's credential.`
18392
19591
  ]));
18393
19592
  } catch {
18394
- return headerLine(NO_CREDENTIAL_PLACEHOLDER);
19593
+ return headerLine(NO_CREDENTIAL_PLACEHOLDER, readPinnedVersion(root));
18395
19594
  }
18396
- return headerLine(NO_CREDENTIAL_PLACEHOLDER);
19595
+ return headerLine(NO_CREDENTIAL_PLACEHOLDER, readPinnedVersion(root));
18397
19596
  }
18398
19597
  }
18399
19598
 
@@ -18441,7 +19640,7 @@ var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initializ
18441
19640
  function registeredMcpUrl(projectRoot) {
18442
19641
  let parsed;
18443
19642
  try {
18444
- parsed = JSON.parse(readFileSync10(resolve3(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
19643
+ parsed = JSON.parse(readFileSync12(resolve4(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
18445
19644
  } catch {
18446
19645
  return void 0;
18447
19646
  }
@@ -18535,16 +19734,83 @@ async function checkConnection(renewedToken, opts) {
18535
19734
  environmentTokenFirst: false
18536
19735
  });
18537
19736
  if (CREDENTIAL_PLACEHOLDERS.includes(bearer)) {
18538
- return PLACEHOLDER_SENTENCES[bearer];
19737
+ return { sentence: PLACEHOLDER_SENTENCES[bearer], url: url2, bearer };
18539
19738
  }
18540
19739
  const outcome = await probeDelivery(url2, bearer, opts.fetchFn ?? globalThis.fetch, opts.checkDeadlineMs ?? CONNECTION_CHECK_DEADLINE_MS);
18541
- return outcome ?? CONNECTED_SENTENCE;
19740
+ return { sentence: outcome ?? CONNECTED_SENTENCE, url: url2, bearer };
19741
+ }
19742
+ function positionRequestBody(engagementId) {
19743
+ return JSON.stringify({
19744
+ jsonrpc: "2.0",
19745
+ id: 1,
19746
+ method: "tools/call",
19747
+ params: { name: "halfcycle_position", arguments: { engagementId } }
19748
+ });
19749
+ }
19750
+ async function fetchScopeNotices(url2, bearer, engagementId, fetchFn, deadlineMs) {
19751
+ const controller = new AbortController();
19752
+ const timer = setTimeout(() => controller.abort(), deadlineMs);
19753
+ try {
19754
+ let res;
19755
+ try {
19756
+ res = await fetchFn(url2, {
19757
+ method: "POST",
19758
+ headers: { "content-type": "application/json", authorization: `Bearer ${bearer}` },
19759
+ body: positionRequestBody(engagementId),
19760
+ redirect: "manual",
19761
+ signal: controller.signal
19762
+ });
19763
+ } catch {
19764
+ return void 0;
19765
+ }
19766
+ if (res.status < 200 || res.status > 299) {
19767
+ discardBody(res);
19768
+ return void 0;
19769
+ }
19770
+ let text;
19771
+ try {
19772
+ text = await res.text();
19773
+ } catch {
19774
+ return void 0;
19775
+ }
19776
+ let rpc;
19777
+ try {
19778
+ rpc = JSON.parse(text);
19779
+ } catch {
19780
+ return void 0;
19781
+ }
19782
+ const result = typeof rpc === "object" && rpc !== null ? rpc["result"] : void 0;
19783
+ if (typeof result !== "object" || result === null || result["isError"] === true) {
19784
+ return void 0;
19785
+ }
19786
+ const content = result["content"];
19787
+ const first = Array.isArray(content) ? content[0] : void 0;
19788
+ const bodyText = typeof first === "object" && first !== null ? first["text"] : void 0;
19789
+ if (typeof bodyText !== "string")
19790
+ return void 0;
19791
+ let body;
19792
+ try {
19793
+ body = JSON.parse(bodyText);
19794
+ } catch {
19795
+ return void 0;
19796
+ }
19797
+ const scope = typeof body === "object" && body !== null ? body["scope"] : void 0;
19798
+ const parsed = scopeNoticesSchema.safeParse(scope);
19799
+ return parsed.success ? parsed.data : void 0;
19800
+ } finally {
19801
+ clearTimeout(timer);
19802
+ }
19803
+ }
19804
+ function scopeNoticeLines(scope) {
19805
+ const text = scopeNoticeText(scope, { offerTyping: scope.typedApprovalOffered ? "yes" : "off" });
19806
+ return [...text.sentBack, ...text.waiting];
18542
19807
  }
18543
19808
  function defaultWriteErr2(text) {
18544
19809
  process.stderr.write(text);
18545
19810
  }
18546
19811
  async function runRefreshCredential(opts) {
18547
19812
  const writeErr = opts.writeErr ?? defaultWriteErr2;
19813
+ const startedAt = Date.now();
18548
19814
  try {
18549
19815
  const renewalSaid = [];
18550
19816
  let renewalOpen = true;
@@ -18558,13 +19824,28 @@ async function runRefreshCredential(opts) {
18558
19824
  renewalOpen = false;
18559
19825
  const parts = [];
18560
19826
  if (CHECKED_SOURCES.has(input["source"])) {
18561
- const outcome = await checkConnection(renewedToken, { ...opts, writeErr });
18562
- if (outcome !== void 0)
18563
- parts.push(outcome);
19827
+ const connection = await checkConnection(renewedToken, { ...opts, writeErr });
19828
+ if (connection !== void 0) {
19829
+ parts.push(connection.sentence);
19830
+ if (connection.sentence === CONNECTED_SENTENCE) {
19831
+ const engagementId = readConfig().config?.guardEngagementId;
19832
+ if (engagementId !== void 0) {
19833
+ const remainingForMargin = SESSION_START_HOOK_TIMEOUT_MS - SESSION_START_MARGIN_MS - (Date.now() - startedAt);
19834
+ const positionDeadline = Math.min(opts.scopeNoticeDeadlineMs ?? SCOPE_NOTICE_DEADLINE_MS, remainingForMargin);
19835
+ if (positionDeadline > 0) {
19836
+ const scope = await fetchScopeNotices(connection.url, connection.bearer, engagementId, opts.fetchFn ?? globalThis.fetch, positionDeadline);
19837
+ if (scope !== void 0)
19838
+ parts.push(...scopeNoticeLines(scope));
19839
+ }
19840
+ }
19841
+ } else if (connection.sentence === CANNOT_REACH_SENTENCE || connection.sentence === SERVICE_PROBLEM_SENTENCE) {
19842
+ parts.push(SCOPE_UNCHECKED_SENTENCE);
19843
+ }
19844
+ }
18564
19845
  }
18565
19846
  if (readConfig().config === null) {
18566
19847
  const remedySaid = parts.some(namesTheSignInCommand);
18567
- parts.push(noCoverageStatement(readNeverConfiguredReason(), remedySaid));
19848
+ parts.push(noCoverageStatement(readNeverConfiguredReason(), remedySaid, pinnedEngagementId(projectRootFromBinary(opts.binaryPath))));
18568
19849
  }
18569
19850
  for (const said of renewalSaid) {
18570
19851
  if (namesTheSignInCommand(said) && parts.some(namesTheSignInCommand))
@@ -18588,7 +19869,7 @@ async function runRefreshCredential(opts) {
18588
19869
  import { fileURLToPath } from "node:url";
18589
19870
 
18590
19871
  // dist/capture.js
18591
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "node:fs";
19872
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "node:fs";
18592
19873
  import { dirname as dirname5 } from "node:path";
18593
19874
  function runCapture(provider, endpoint, opts = {}) {
18594
19875
  const repoRoot = opts.repoRoot ?? process.cwd();
@@ -18656,7 +19937,7 @@ function readInputFile(fromFile) {
18656
19937
  if (!fromFile)
18657
19938
  return void 0;
18658
19939
  try {
18659
- return readFileSync11(fromFile, "utf-8");
19940
+ return readFileSync13(fromFile, "utf-8");
18660
19941
  } catch {
18661
19942
  return void 0;
18662
19943
  }
@@ -18681,8 +19962,8 @@ async function main() {
18681
19962
  return;
18682
19963
  }
18683
19964
  if (cmd === "mcp-headers") {
18684
- const line = await mcpHeaders({ binaryPath: fileURLToPath(import.meta.url) });
18685
- process.stdout.write(line, () => process.exit(0));
19965
+ const line2 = await mcpHeaders({ binaryPath: fileURLToPath(import.meta.url) });
19966
+ process.stdout.write(line2, () => process.exit(0));
18686
19967
  return;
18687
19968
  }
18688
19969
  if (cmd === "resolve-engagement") {
@@ -18738,8 +20019,18 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
18738
20019
  process.exit(process.exitCode ?? 0);
18739
20020
  return;
18740
20021
  }
20022
+ if (sub === "user-prompt-submit") {
20023
+ let reply = "";
20024
+ try {
20025
+ reply = await runUserPromptSubmit({ input: await readStdin2() });
20026
+ } catch {
20027
+ reply = "";
20028
+ }
20029
+ process.stdout.write(reply, () => process.exit(0));
20030
+ return;
20031
+ }
18741
20032
  process.stderr.write(`halfcycle-runner: unknown hook "${sub ?? ""}"
18742
- Valid hooks: post-tool-use, stop, subagent-stop
20033
+ Valid hooks: post-tool-use, stop, subagent-stop, user-prompt-submit
18743
20034
  `);
18744
20035
  process.exit(1);
18745
20036
  return;
@@ -18749,6 +20040,7 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
18749
20040
  halfcycle-runner hook post-tool-use
18750
20041
  halfcycle-runner hook stop
18751
20042
  halfcycle-runner hook subagent-stop
20043
+ halfcycle-runner hook user-prompt-submit
18752
20044
  halfcycle-runner refresh-credential
18753
20045
  halfcycle-runner mcp-headers
18754
20046
  halfcycle-runner resolve-engagement <cwd>
@@ -18757,10 +20049,10 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
18757
20049
  process.exit(1);
18758
20050
  }
18759
20051
  function readStdin3() {
18760
- return new Promise((resolve4, reject) => {
20052
+ return new Promise((resolve5, reject) => {
18761
20053
  const chunks = [];
18762
20054
  process.stdin.on("data", (chunk) => chunks.push(chunk));
18763
- process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
20055
+ process.stdin.on("end", () => resolve5(Buffer.concat(chunks).toString("utf-8")));
18764
20056
  process.stdin.on("error", reject);
18765
20057
  });
18766
20058
  }