halfcycle 0.4.7 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcycle",
3
- "version": "0.4.7",
3
+ "version": "0.4.8",
4
4
  "description": "Halfcycle Method bundle — resolution-stub slash commands, remote method-delivery registration, and governance-hook wiring for a Halfcycle engagement repo. It ships NO worker role files: a project's roles are authored from that project's own recorded decisions at orchestration kickoff (FX-2, W3-F-27).",
5
5
  "commands": [
6
6
  {
@@ -14899,8 +14899,8 @@ function readConfig(env = process.env) {
14899
14899
  }
14900
14900
  };
14901
14901
  }
14902
- function readNeverConfiguredReason() {
14903
- 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";
14904
14904
  }
14905
14905
  function isHalfcycleOwnCi() {
14906
14906
  return process.env["HALFCYCLE_INTERNAL_CI"] === "1";
@@ -16086,6 +16086,110 @@ function scopeNoticeText(scope, options) {
16086
16086
  };
16087
16087
  }
16088
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
+
16089
16193
  // ../events/dist/credential-wire.js
16090
16194
  var engagementCredentialResponseSchema = external_exports.object({
16091
16195
  engagementId: external_exports.string(),
@@ -16224,8 +16328,8 @@ This is not a service outage: the guard service answered. The suspect is this re
16224
16328
  }
16225
16329
  var UNCONFIGURED_EXIT_TOOL_USE = 2;
16226
16330
  var UNCONFIGURED_EXIT_STOP_FAMILY = 1;
16227
- function emitNeverConfigured(exitCode, reason) {
16228
- writeSync(2, neverConfiguredReport(reason));
16331
+ function emitNeverConfigured(exitCode, reason, engagementId) {
16332
+ writeSync(2, neverConfiguredReport(reason, void 0, engagementId));
16229
16333
  process.exitCode = exitCode;
16230
16334
  }
16231
16335
  var SIGN_IN_COMMAND = '"npx halfcycle"';
@@ -16255,16 +16359,16 @@ ${NOT_A_SERVICE_OUTAGE} Nothing was sent: this job holds no credential and was n
16255
16359
  ${CI_OLDER_SETUP}
16256
16360
  `;
16257
16361
  }
16258
- function notThisAccountReport(label) {
16259
- 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.
16260
16364
  `;
16261
16365
  }
16262
- function neverConfiguredReport(reason, label = "[Halfcycle]") {
16366
+ function neverConfiguredReport(reason, label = "[Halfcycle]", engagementId) {
16263
16367
  switch (reason) {
16264
16368
  case "no-credential-yet":
16265
16369
  return neverConfiguredMessage(HOOK_REMEDY, label);
16266
16370
  case "not-this-account":
16267
- return notThisAccountReport(label);
16371
+ return notThisAccountReport(label, engagementId);
16268
16372
  }
16269
16373
  }
16270
16374
  var CREDENTIAL_REJECTED_STATUSES = /* @__PURE__ */ new Set([401, 403]);
@@ -16315,14 +16419,14 @@ function credentialRenewalFailedMessage(remedy, label = "[Halfcycle]") {
16315
16419
  ${remedy}
16316
16420
  `;
16317
16421
  }
16318
- function noCoverageStatement(reason, remedySaid = false) {
16422
+ function noCoverageStatement(reason, remedySaid = false, engagementId) {
16319
16423
  if (reason === "not-this-account") {
16320
16424
  return [
16321
16425
  "[halfcycle] This project belongs to another Halfcycle account, so nothing here is checked.",
16322
16426
  "[halfcycle] Your work carries on exactly as it would without Halfcycle \u2014 no edit is",
16323
16427
  "[halfcycle] evaluated, and nothing you do is reported as having passed.",
16324
16428
  "[halfcycle] Halfcycle is Solo today \u2014 one developer per project \u2014 with Team plans coming.",
16325
- "[halfcycle] Ask the project's owner for view access.",
16429
+ `[halfcycle] ${requestAccessSentence(engagementId)}`,
16326
16430
  "[halfcycle] If this account is wrong, sign in as the one that owns the project and run",
16327
16431
  '[halfcycle] "npx halfcycle" again.'
16328
16432
  ].join("\n");
@@ -18108,8 +18212,36 @@ function cleanRunSummary(verdict, scope) {
18108
18212
  return `[Halfcycle CI] ${verdict} ${files}, ${scope.signals} signal(s) checked.`;
18109
18213
  }
18110
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
+
18111
18243
  // dist/credential-store.js
18112
- 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";
18113
18245
  import { homedir as homedir2, platform } from "node:os";
18114
18246
  import { dirname as dirname3, join as join5 } from "node:path";
18115
18247
  var REFRESHED_TOKEN_KEYS = ["HALFCYCLE_TOKEN", "GUARD_SERVICE_TOKEN"];
@@ -18123,7 +18255,7 @@ function hasNotThisAccountMarker(engagementId, home) {
18123
18255
  function readAccountCredential(controlOrigin, home) {
18124
18256
  let raw;
18125
18257
  try {
18126
- 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");
18127
18259
  } catch {
18128
18260
  return void 0;
18129
18261
  }
@@ -18140,7 +18272,7 @@ function readStoredEngagementToken(envFilePath) {
18140
18272
  function readEngagementStore(envFilePath) {
18141
18273
  let raw;
18142
18274
  try {
18143
- raw = readFileSync5(envFilePath, "utf-8");
18275
+ raw = readFileSync6(envFilePath, "utf-8");
18144
18276
  } catch {
18145
18277
  return void 0;
18146
18278
  }
@@ -18170,7 +18302,7 @@ function writeRefreshedToken(opts) {
18170
18302
  }
18171
18303
  let existing;
18172
18304
  try {
18173
- existing = readFileSync5(declaredReal, "utf-8");
18305
+ existing = readFileSync6(declaredReal, "utf-8");
18174
18306
  } catch {
18175
18307
  existing = null;
18176
18308
  }
@@ -18313,7 +18445,7 @@ async function evaluateWithRefresh(opts) {
18313
18445
 
18314
18446
  // dist/hooks/marker.js
18315
18447
  import { createHash } from "node:crypto";
18316
- 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";
18317
18449
  import { tmpdir } from "node:os";
18318
18450
  import { join as join6 } from "node:path";
18319
18451
  var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -18345,7 +18477,7 @@ function readMarker(repoRoot, sessionId) {
18345
18477
  if (!existsSync4(path))
18346
18478
  return null;
18347
18479
  try {
18348
- return readFileSync6(path, "utf-8").trim() || null;
18480
+ return readFileSync7(path, "utf-8").trim() || null;
18349
18481
  } catch {
18350
18482
  return null;
18351
18483
  }
@@ -18391,7 +18523,7 @@ function readDedupe(repoRoot, sessionId) {
18391
18523
  if (!existsSync4(path))
18392
18524
  return null;
18393
18525
  try {
18394
- const parsed = JSON.parse(readFileSync6(path, "utf-8"));
18526
+ const parsed = JSON.parse(readFileSync7(path, "utf-8"));
18395
18527
  if (typeof parsed === "object" && parsed !== null && typeof parsed.base === "string" && typeof parsed.digest === "string") {
18396
18528
  return parsed;
18397
18529
  }
@@ -18436,7 +18568,7 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
18436
18568
  const full = join6(dir, name);
18437
18569
  let unusable = false;
18438
18570
  try {
18439
- const ref = readFileSync6(full, "utf-8").trim();
18571
+ const ref = readFileSync7(full, "utf-8").trim();
18440
18572
  if (!ref || !refKnown(ref))
18441
18573
  unusable = true;
18442
18574
  } catch {
@@ -18460,9 +18592,9 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
18460
18592
  }
18461
18593
 
18462
18594
  // dist/seat-configuration.js
18463
- 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";
18464
18596
  import { homedir as homedir3 } from "node:os";
18465
- 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";
18466
18598
  function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
18467
18599
  try {
18468
18600
  const facts = read(repoRoot);
@@ -18475,7 +18607,7 @@ function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
18475
18607
  }
18476
18608
  }
18477
18609
  function readSeatConfiguration(repoRoot) {
18478
- const given = resolve(process.env["CLAUDE_PROJECT_DIR"] || process.cwd());
18610
+ const given = resolve2(process.env["CLAUDE_PROJECT_DIR"] || process.cwd());
18479
18611
  const projectDir = realpathSync2.native(given);
18480
18612
  const home = homedir3();
18481
18613
  let realHome = home;
@@ -18497,7 +18629,7 @@ function isPlainObject3(value) {
18497
18629
  function readSettingsFile(projectDir, rel) {
18498
18630
  let raw;
18499
18631
  try {
18500
- raw = readFileSync7(join7(projectDir, rel), "utf-8");
18632
+ raw = readFileSync8(join7(projectDir, rel), "utf-8");
18501
18633
  } catch (err) {
18502
18634
  const code = err.code;
18503
18635
  const state = code === "ENOENT" || code === "ENOTDIR" ? "absent" : "unreadable";
@@ -18655,7 +18787,7 @@ async function evaluateEdit(reply, readSeat) {
18655
18787
  await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
18656
18788
  if (!firstNoticeThisSession("credential-missing", repoRoot, input.session_id))
18657
18789
  return;
18658
- emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason());
18790
+ emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()));
18659
18791
  return;
18660
18792
  }
18661
18793
  const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
@@ -18748,10 +18880,10 @@ ${guardLines}${notRunBlock}`);
18748
18880
  }
18749
18881
  }
18750
18882
  function readStdin() {
18751
- return new Promise((resolve4, reject) => {
18883
+ return new Promise((resolve5, reject) => {
18752
18884
  const chunks = [];
18753
18885
  process.stdin.on("data", (chunk) => chunks.push(chunk));
18754
- process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
18886
+ process.stdin.on("end", () => resolve5(Buffer.concat(chunks).toString("utf-8")));
18755
18887
  process.stdin.on("error", reject);
18756
18888
  });
18757
18889
  }
@@ -18836,7 +18968,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
18836
18968
  await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet, diffBase });
18837
18969
  if (!firstNoticeThisSession("credential-missing", repoRoot, sessionId))
18838
18970
  return;
18839
- emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason());
18971
+ emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()));
18840
18972
  return;
18841
18973
  }
18842
18974
  const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
@@ -18947,9 +19079,9 @@ async function readHookStdin() {
18947
19079
  }
18948
19080
  }
18949
19081
  function readStdin2() {
18950
- return new Promise((resolve4) => {
19082
+ return new Promise((resolve5) => {
18951
19083
  if (process.stdin.isTTY) {
18952
- resolve4("");
19084
+ resolve5("");
18953
19085
  return;
18954
19086
  }
18955
19087
  const chunks = [];
@@ -18959,7 +19091,7 @@ function readStdin2() {
18959
19091
  return;
18960
19092
  settled = true;
18961
19093
  clearTimeout(timer);
18962
- resolve4(Buffer.concat(chunks).toString("utf-8"));
19094
+ resolve5(Buffer.concat(chunks).toString("utf-8"));
18963
19095
  };
18964
19096
  const timer = setTimeout(done, HOOK_STDIN_GRACE_MS);
18965
19097
  process.stdin.on("data", (chunk) => chunks.push(chunk));
@@ -18981,7 +19113,7 @@ async function runStop() {
18981
19113
  }
18982
19114
 
18983
19115
  // dist/hooks/user-prompt-submit.js
18984
- import { readFileSync as readFileSync8 } from "node:fs";
19116
+ import { readFileSync as readFileSync9 } from "node:fs";
18985
19117
  import { homedir as homedir4 } from "node:os";
18986
19118
  import { join as join8 } from "node:path";
18987
19119
 
@@ -19038,6 +19170,9 @@ function notAttendedLine(act) {
19038
19170
  function notSignedInLine(act) {
19039
19171
  return line(`${nothingDone(act)}: this project is not signed in on this computer. ${HOOK_REMEDY}`);
19040
19172
  }
19173
+ function notOwnerLine(act) {
19174
+ return line(`${nothingDone(act)}: only the project's owner can approve or send back this scope.`);
19175
+ }
19041
19176
  function showFirstLine(act) {
19042
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.`);
19043
19178
  }
@@ -19075,7 +19210,7 @@ function lineForAnswer(act, phase, answer) {
19075
19210
  case "build-started":
19076
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.`);
19077
19212
  case "not-owner":
19078
- return line(`${nothingDone(act)}: only the project's owner can approve or send back this scope.`);
19213
+ return notOwnerLine(act);
19079
19214
  case "no-owner":
19080
19215
  return line(`${nothingDone(act)}: this project has no owner, so nobody can approve or send back its scope.`);
19081
19216
  case "not-offered":
@@ -19115,8 +19250,9 @@ async function decide(shape, opts) {
19115
19250
  if (!readSessionAttended(env))
19116
19251
  return notAttendedLine(act);
19117
19252
  const read = readConfig(env);
19118
- if (read.config === null)
19119
- return notSignedInLine(act);
19253
+ if (read.config === null) {
19254
+ return readNeverConfiguredReason(env) === "not-this-account" ? notOwnerLine(act) : notSignedInLine(act);
19255
+ }
19120
19256
  const { guardServiceToken, guardEngagementId } = read.config;
19121
19257
  const shown = readShownPlans(guardEngagementId, opts.home);
19122
19258
  const match = matchScopePhraseShape(shape, shown);
@@ -19157,7 +19293,7 @@ function readShownPlans(engagementId, home) {
19157
19293
  return [];
19158
19294
  let text;
19159
19295
  try {
19160
- text = readFileSync8(join8(home ?? homedir4(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SHOWN_PLANS_FILENAME), "utf-8");
19296
+ text = readFileSync9(join8(home ?? homedir4(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SHOWN_PLANS_FILENAME), "utf-8");
19161
19297
  } catch {
19162
19298
  return [];
19163
19299
  }
@@ -19196,7 +19332,7 @@ async function post(act, entry, request, engagementId, credential, env, fetchFn
19196
19332
  }
19197
19333
 
19198
19334
  // dist/resolve-engagement.js
19199
- import { existsSync as existsSync6, readFileSync as readFileSync9, realpathSync as realpathSync3 } from "node:fs";
19335
+ import { existsSync as existsSync6, readFileSync as readFileSync10, realpathSync as realpathSync3 } from "node:fs";
19200
19336
  import { join as join9, sep as sep2 } from "node:path";
19201
19337
  import { homedir as homedir5 } from "node:os";
19202
19338
  function defaultRegistryPath() {
@@ -19206,7 +19342,7 @@ function readRegistry(registryPath = defaultRegistryPath()) {
19206
19342
  if (!existsSync6(registryPath))
19207
19343
  return {};
19208
19344
  try {
19209
- const parsed = JSON.parse(readFileSync9(registryPath, "utf-8"));
19345
+ const parsed = JSON.parse(readFileSync10(registryPath, "utf-8"));
19210
19346
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
19211
19347
  return {};
19212
19348
  const out = {};
@@ -19254,13 +19390,13 @@ function runResolveEngagement(cwd, registryPath) {
19254
19390
  }
19255
19391
 
19256
19392
  // dist/refresh-credential.js
19257
- import { readFileSync as readFileSync11 } from "node:fs";
19258
- import { resolve as resolve3 } from "node:path";
19393
+ import { readFileSync as readFileSync12 } from "node:fs";
19394
+ import { resolve as resolve4 } from "node:path";
19259
19395
 
19260
19396
  // dist/mcp-headers.js
19261
- import { readFileSync as readFileSync10 } from "node:fs";
19397
+ import { readFileSync as readFileSync11 } from "node:fs";
19262
19398
  import { homedir as homedir6 } from "node:os";
19263
- import { dirname as dirname4, resolve as resolve2 } from "node:path";
19399
+ import { dirname as dirname4, resolve as resolve3 } from "node:path";
19264
19400
  function defaultWriteErr(text) {
19265
19401
  process.stderr.write(text);
19266
19402
  }
@@ -19273,28 +19409,12 @@ function headerLine(bearer, clientVersion) {
19273
19409
  }
19274
19410
  function projectRootFromBinary(binaryPath) {
19275
19411
  const depth = VENDORED_BIN_REL.split("/").length - 1;
19276
- return resolve2(dirname4(binaryPath), ...Array(depth).fill(".."));
19277
- }
19278
- function readPinnedId(root) {
19279
- let raw;
19280
- try {
19281
- raw = readFileSync10(resolve2(root, BUNDLE_PIN_REL), "utf-8");
19282
- } catch {
19283
- return { problem: "no-pin" };
19284
- }
19285
- let pin;
19286
- try {
19287
- pin = JSON.parse(raw);
19288
- } catch {
19289
- return { problem: "no-id" };
19290
- }
19291
- const id = typeof pin === "object" && pin !== null ? pin[PIN_ENGAGEMENT_ID_FIELD] : void 0;
19292
- return typeof id === "string" && id !== "" ? { id } : { problem: "no-id" };
19412
+ return resolve3(dirname4(binaryPath), ...Array(depth).fill(".."));
19293
19413
  }
19294
19414
  function readPinnedVersion(root) {
19295
19415
  let raw;
19296
19416
  try {
19297
- raw = readFileSync10(resolve2(root, BUNDLE_PIN_REL), "utf-8");
19417
+ raw = readFileSync11(resolve3(root, BUNDLE_PIN_REL), "utf-8");
19298
19418
  } catch {
19299
19419
  return void 0;
19300
19420
  }
@@ -19336,7 +19456,7 @@ function sentences(lines) {
19336
19456
  }
19337
19457
  function badIdSentence(root) {
19338
19458
  return sentences([
19339
- `${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,`,
19340
19460
  'so no credential was read. Delete that file and run "npx halfcycle" again: this repository',
19341
19461
  "is then set up as a new Halfcycle project."
19342
19462
  ]);
@@ -19350,7 +19470,7 @@ function storeProblemSentence(problem, root, home, engagementId) {
19350
19470
  ]);
19351
19471
  case "no-id":
19352
19472
  return sentences([
19353
- `${resolve2(root, BUNDLE_PIN_REL)} names no engagement id.`,
19473
+ `${resolve3(root, BUNDLE_PIN_REL)} names no engagement id.`,
19354
19474
  'run "npx halfcycle" here to rewrite it.'
19355
19475
  ]);
19356
19476
  case "bad-id":
@@ -19359,13 +19479,15 @@ function storeProblemSentence(problem, root, home, engagementId) {
19359
19479
  return sentences([
19360
19480
  "this project belongs to another Halfcycle account, so this machine has no",
19361
19481
  "credential for it and the Halfcycle tools are not available here. Nothing is",
19362
- "broken \u2014 ask the project's owner for view access, or sign in as the account",
19363
- '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.'
19364
19486
  ]);
19365
19487
  case "no-credential":
19366
19488
  return sentences([
19367
19489
  `this machine holds no credential for engagement ${engagementId ?? ""}.`,
19368
- `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.`
19369
19491
  ]);
19370
19492
  }
19371
19493
  }
@@ -19518,7 +19640,7 @@ var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initializ
19518
19640
  function registeredMcpUrl(projectRoot) {
19519
19641
  let parsed;
19520
19642
  try {
19521
- parsed = JSON.parse(readFileSync11(resolve3(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
19643
+ parsed = JSON.parse(readFileSync12(resolve4(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
19522
19644
  } catch {
19523
19645
  return void 0;
19524
19646
  }
@@ -19723,7 +19845,7 @@ async function runRefreshCredential(opts) {
19723
19845
  }
19724
19846
  if (readConfig().config === null) {
19725
19847
  const remedySaid = parts.some(namesTheSignInCommand);
19726
- parts.push(noCoverageStatement(readNeverConfiguredReason(), remedySaid));
19848
+ parts.push(noCoverageStatement(readNeverConfiguredReason(), remedySaid, pinnedEngagementId(projectRootFromBinary(opts.binaryPath))));
19727
19849
  }
19728
19850
  for (const said of renewalSaid) {
19729
19851
  if (namesTheSignInCommand(said) && parts.some(namesTheSignInCommand))
@@ -19747,7 +19869,7 @@ async function runRefreshCredential(opts) {
19747
19869
  import { fileURLToPath } from "node:url";
19748
19870
 
19749
19871
  // dist/capture.js
19750
- import { readFileSync as readFileSync12, 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";
19751
19873
  import { dirname as dirname5 } from "node:path";
19752
19874
  function runCapture(provider, endpoint, opts = {}) {
19753
19875
  const repoRoot = opts.repoRoot ?? process.cwd();
@@ -19815,7 +19937,7 @@ function readInputFile(fromFile) {
19815
19937
  if (!fromFile)
19816
19938
  return void 0;
19817
19939
  try {
19818
- return readFileSync12(fromFile, "utf-8");
19940
+ return readFileSync13(fromFile, "utf-8");
19819
19941
  } catch {
19820
19942
  return void 0;
19821
19943
  }
@@ -19927,10 +20049,10 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
19927
20049
  process.exit(1);
19928
20050
  }
19929
20051
  function readStdin3() {
19930
- return new Promise((resolve4, reject) => {
20052
+ return new Promise((resolve5, reject) => {
19931
20053
  const chunks = [];
19932
20054
  process.stdin.on("data", (chunk) => chunks.push(chunk));
19933
- process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
20055
+ process.stdin.on("end", () => resolve5(Buffer.concat(chunks).toString("utf-8")));
19934
20056
  process.stdin.on("error", reject);
19935
20057
  });
19936
20058
  }
package/dist/cli.js CHANGED
@@ -2067,6 +2067,15 @@ function isNotThisAccountRefusal(body) {
2067
2067
  return typeof body === "object" && body !== null && body.reason === NOT_THIS_ACCOUNT_REASON;
2068
2068
  }
2069
2069
 
2070
+ // ../events/dist/project-page.js
2071
+ function projectPageUrl(engagementId) {
2072
+ return isEngagementId(engagementId) ? `${DASHBOARD_URL}/${encodeURIComponent(engagementId)}` : void 0;
2073
+ }
2074
+ function requestAccessSentence(engagementId) {
2075
+ const page = projectPageUrl(engagementId) ?? "this project's page on Halfcycle";
2076
+ 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.`;
2077
+ }
2078
+
2070
2079
  // ../events/dist/account-identity.js
2071
2080
  import { z as z12 } from "zod";
2072
2081
  var accountIdentitySchema = z12.object({
@@ -2788,11 +2797,6 @@ function nodeFloorProgram(guarded) {
2788
2797
  return `if(+process.versions.node.split('.')[0]<${NODE_FLOOR_MAJOR}){console.error('${NODE_FLOOR_SENTENCE_HEAD}'+process.versions.node);process.exitCode=1}else{${guarded}}`;
2789
2798
  }
2790
2799
 
2791
- // dist/project-page.js
2792
- function projectPageUrl(engagementId) {
2793
- return isEngagementId(engagementId) ? `${DASHBOARD_URL}/${encodeURIComponent(engagementId)}` : void 0;
2794
- }
2795
-
2796
2800
  // dist/merge-settings.js
2797
2801
  function isHalfcycleCommand(hook, halfcycleCommands) {
2798
2802
  if (typeof hook !== "object" || hook === null)
@@ -8886,7 +8890,7 @@ ${PROJECT_RESET_LINE}
8886
8890
  `);
8887
8891
  }
8888
8892
  if (credential === void 0 && notThisAccountMessage !== void 0) {
8889
- process.stdout.write(`[halfcycle] ${notThisAccountMessage} Your work carries on exactly as it would without Halfcycle: no edit is evaluated, and nothing you do here is reported as having passed. Ask the project's owner for view access. ${identitySwitchRemedy(void 0, isInteractiveRun())}
8893
+ process.stdout.write(`[halfcycle] ${notThisAccountMessage} Your work carries on exactly as it would without Halfcycle: no edit is evaluated, and nothing you do here is reported as having passed. ${requestAccessSentence(engagementId)} ${identitySwitchRemedy(void 0, isInteractiveRun())}
8890
8894
  `);
8891
8895
  }
8892
8896
  reportClaudeCodeVersion(verbose);