halfcycle 0.11.1 → 0.12.0-rc.2

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.
Files changed (79) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +1 -1
  3. package/bin/bin.bundle.mjs +964 -351
  4. package/bin/pre-tool-use-filter.sh +4 -0
  5. package/commands/agent-teams-orchestration.md +1 -1
  6. package/commands/consistency-check.md +1 -1
  7. package/commands/context-update.md +1 -1
  8. package/commands/halfcycle-design.md +1 -1
  9. package/commands/halfcycle-setup.md +1 -1
  10. package/commands/stack-assembly-worker.md +1 -1
  11. package/commands/task-execution.md +1 -1
  12. package/commands/task-graph.md +1 -1
  13. package/commands/write-spec.md +1 -1
  14. package/dist/banner-facts.d.ts +7 -2
  15. package/dist/banner-facts.d.ts.map +1 -1
  16. package/dist/build-record/obtain.d.ts.map +1 -1
  17. package/dist/change.d.ts.map +1 -1
  18. package/dist/check-criteria.d.ts +1 -1
  19. package/dist/check-criteria.d.ts.map +1 -1
  20. package/dist/check-report.d.ts +1 -1
  21. package/dist/check-report.d.ts.map +1 -1
  22. package/dist/check-tasks.d.ts +10 -1
  23. package/dist/check-tasks.d.ts.map +1 -1
  24. package/dist/cli-contract.d.ts +1 -1
  25. package/dist/cli-contract.d.ts.map +1 -1
  26. package/dist/cli.js +1412 -873
  27. package/dist/cli.js.map +3 -3
  28. package/dist/close-phase.d.ts +5 -3
  29. package/dist/close-phase.d.ts.map +1 -1
  30. package/dist/confirm-identity.d.ts +3 -0
  31. package/dist/confirm-identity.d.ts.map +1 -1
  32. package/dist/crew.d.ts +37 -0
  33. package/dist/crew.d.ts.map +1 -0
  34. package/dist/draw/index.d.ts +1 -1
  35. package/dist/draw/index.d.ts.map +1 -1
  36. package/dist/engagement-credential.d.ts +4 -0
  37. package/dist/engagement-credential.d.ts.map +1 -1
  38. package/dist/failure-line.d.ts +3 -2
  39. package/dist/failure-line.d.ts.map +1 -1
  40. package/dist/folder-refusals.d.ts.map +1 -1
  41. package/dist/index.js +330 -224
  42. package/dist/index.js.map +3 -3
  43. package/dist/install-manifest.d.ts +5 -1
  44. package/dist/install-manifest.d.ts.map +1 -1
  45. package/dist/install-plan.d.ts.map +1 -1
  46. package/dist/install.d.ts +45 -11
  47. package/dist/install.d.ts.map +1 -1
  48. package/dist/local-allow.d.ts +72 -3
  49. package/dist/local-allow.d.ts.map +1 -1
  50. package/dist/loopback-signin.d.ts +6 -5
  51. package/dist/loopback-signin.d.ts.map +1 -1
  52. package/dist/merge-settings.d.ts +16 -1
  53. package/dist/merge-settings.d.ts.map +1 -1
  54. package/dist/open-phase.d.ts +1 -1
  55. package/dist/open-phase.d.ts.map +1 -1
  56. package/dist/own-version.d.ts +42 -0
  57. package/dist/own-version.d.ts.map +1 -0
  58. package/dist/pace.d.ts +2 -2
  59. package/dist/pace.d.ts.map +1 -1
  60. package/dist/plan.d.ts +4 -3
  61. package/dist/plan.d.ts.map +1 -1
  62. package/dist/pre-create-checks.d.ts.map +1 -1
  63. package/dist/preview.d.ts +7 -2
  64. package/dist/preview.d.ts.map +1 -1
  65. package/dist/ready.d.ts +1 -1
  66. package/dist/ready.d.ts.map +1 -1
  67. package/dist/regression.d.ts +48 -0
  68. package/dist/regression.d.ts.map +1 -0
  69. package/dist/request-shapes-verb.d.ts +1 -1
  70. package/dist/request-shapes-verb.d.ts.map +1 -1
  71. package/dist/restore.d.ts +1 -1
  72. package/dist/restore.d.ts.map +1 -1
  73. package/dist/review.d.ts +2 -2
  74. package/dist/review.d.ts.map +1 -1
  75. package/dist/route.d.ts +1 -1
  76. package/dist/route.d.ts.map +1 -1
  77. package/dist/size.d.ts.map +1 -1
  78. package/dist/uninstall.d.ts.map +1 -1
  79. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,10 +1,9 @@
1
1
  // dist/install.js
2
2
  import { execFileSync as execFileSync2 } from "node:child_process";
3
3
  import { createHash as createHash2 } from "node:crypto";
4
- import { existsSync as existsSync4, lstatSync, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
4
+ import { existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
5
5
  import { homedir as homedir2 } from "node:os";
6
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6, posix as posix2, relative, resolve } from "node:path";
7
- import { fileURLToPath } from "node:url";
6
+ import { dirname as dirname4, isAbsolute as isAbsolute2, join as join7, posix as posix2, relative, resolve } from "node:path";
8
7
 
9
8
  // ../core/dist/guard-record.js
10
9
  import { z } from "zod";
@@ -268,14 +267,40 @@ function looksLikeSecret(text) {
268
267
  var MCP_REGISTRATION_REL = ".mcp.json";
269
268
  var MCP_SERVER_KEY = "halfcycle";
270
269
 
270
+ // ../core/dist/release-version.js
271
+ var RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
272
+
273
+ // ../core/dist/release-tag.js
274
+ var PRE_RELEASE_VERSION_PATTERN = /^(\d+\.\d+\.\d+)-rc\.\d+$/;
275
+ var PACKAGE_COMMAND = "npx halfcycle";
276
+ var COMMAND_FORMS = /npx halfcycle(?:@latest|(?![@\p{L}\p{N}-]))/gu;
277
+ function isPreReleaseVersion(value) {
278
+ return typeof value === "string" && PRE_RELEASE_VERSION_PATTERN.test(value);
279
+ }
280
+ function releaseTagOfVersion(version) {
281
+ return isPreReleaseVersion(version) ? "next" : "latest";
282
+ }
283
+ function halfcycleCommand(version) {
284
+ return `${PACKAGE_COMMAND}@${releaseTagOfVersion(version)}`;
285
+ }
286
+ function bareHalfcycleCommand(version) {
287
+ return releaseTagOfVersion(version) === "next" ? `${PACKAGE_COMMAND}@next` : PACKAGE_COMMAND;
288
+ }
289
+ function rewriteHalfcycleCommands(text, tag) {
290
+ if (tag === "latest")
291
+ return text;
292
+ return text.replace(COMMAND_FORMS, `${PACKAGE_COMMAND}@${tag}`);
293
+ }
294
+
271
295
  // ../core/dist/shell-quote.js
272
296
  function quoteForShell(value) {
273
297
  return `'${value.replace(/'/g, `'\\''`)}'`;
274
298
  }
275
- var HALFCYCLE_COMMAND = "npx halfcycle@latest";
276
-
277
- // ../core/dist/release-version.js
278
- var RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
299
+ var HALFCYCLE_COMMAND = halfcycleCommand();
300
+ function doctorLine(version) {
301
+ return `Run \`${bareHalfcycleCommand(version)} doctor\` to check set up, sign-in, connection, checks and version.`;
302
+ }
303
+ var DOCTOR_LINE = doctorLine();
279
304
 
280
305
  // ../core/dist/github-address.js
281
306
  var GITHUB_REPOSITORY_ADDRESS = /^https:\/\/github\.com\/[A-Za-z0-9][A-Za-z0-9-]{0,38}\/(?!\.{1,2}$)[A-Za-z0-9._-]{1,100}$/;
@@ -293,8 +318,14 @@ var REMOTE_FORMS = [
293
318
  ];
294
319
 
295
320
  // ../core/dist/sign-in.js
296
- var SIGN_IN_COMMAND = '"npx halfcycle"';
297
- var HOOK_REMEDY = `Run ${SIGN_IN_COMMAND} in the project folder to sign in.`;
321
+ function signInCommand(version) {
322
+ return `"${bareHalfcycleCommand(version)}"`;
323
+ }
324
+ var SIGN_IN_COMMAND = signInCommand();
325
+ function hookRemedy(version) {
326
+ return `Run ${signInCommand(version)} in the project folder to sign in.`;
327
+ }
328
+ var HOOK_REMEDY = hookRemedy();
298
329
 
299
330
  // ../core/dist/task-plan.js
300
331
  import { z as z2 } from "zod";
@@ -408,7 +439,7 @@ function codexHookCommand(script, args) {
408
439
  const invocation = `cd "$(git rev-parse --show-toplevel)" && bash ./${script}`;
409
440
  return args === void 0 ? invocation : `${invocation} ${args}`;
410
441
  }
411
- var CODEX_WINDOWS_NOT_SET_UP = "Halfcycle checks did not run because Halfcycle is not set up in this folder or any folder above it; run npx halfcycle in the project folder.";
442
+ var CODEX_WINDOWS_NOT_SET_UP = `Halfcycle checks did not run because Halfcycle is not set up in this folder or any folder above it; run ${bareHalfcycleCommand()} in the project folder.`;
412
443
  var CODEX_WINDOWS_NO_GIT_BASH = "Halfcycle checks did not run because Git for Windows was not found; install Git for Windows, then open a new session.";
413
444
  function codexHookCommandWindows(script, args) {
414
445
  const tick = (text) => "`" + text + "`";
@@ -3111,8 +3142,22 @@ var deviceConfirmResponseSchema = z19.union([
3111
3142
  var DASHBOARD_URL = "https://app.halfcycle.ai";
3112
3143
 
3113
3144
  // ../events/dist/project-page.js
3114
- function projectPageUrl(engagementId) {
3115
- return isEngagementId(engagementId) ? `${DASHBOARD_URL}/${encodeURIComponent(engagementId)}` : void 0;
3145
+ var SERVICE_HOST = /^(?:control|delivery)(-[a-z0-9]+)?\.halfcycle\.ai$/;
3146
+ function consoleOrigin(serviceAddress) {
3147
+ const trimmed = serviceAddress?.trim();
3148
+ if (!trimmed)
3149
+ return DASHBOARD_URL;
3150
+ let host;
3151
+ try {
3152
+ host = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`).hostname.toLowerCase();
3153
+ } catch {
3154
+ return DASHBOARD_URL;
3155
+ }
3156
+ const suffix = SERVICE_HOST.exec(host)?.[1];
3157
+ return suffix === void 0 ? DASHBOARD_URL : `https://app${suffix}.halfcycle.ai`;
3158
+ }
3159
+ function projectPageUrl(engagementId, serviceAddress) {
3160
+ return isEngagementId(engagementId) ? `${consoleOrigin(serviceAddress)}/${encodeURIComponent(engagementId)}` : void 0;
3116
3161
  }
3117
3162
 
3118
3163
  // ../events/dist/credential-wire.js
@@ -3135,8 +3180,14 @@ var NO_CREDENTIAL_PLACEHOLDER = "halfcycle:no-credential";
3135
3180
  var NOT_THIS_ACCOUNT_PLACEHOLDER = "halfcycle:not-this-account";
3136
3181
  var UPDATE_CLAUDE_CODE_PLACEHOLDER = "halfcycle:update-claude-code";
3137
3182
  var SWITCH_ACCOUNT_ANSWER = 'answer "s" when asked';
3138
- var NO_CREDENTIAL_SENTENCE = 'Halfcycle is not connected: this machine has no working Halfcycle credential for this project. Run "npx halfcycle" in the project folder to sign in again.';
3139
- var NOT_THIS_ACCOUNT_SENTENCE = `Halfcycle is not connected: this project belongs to another Halfcycle account. To switch, run "npx halfcycle" in this project and ${SWITCH_ACCOUNT_ANSWER}.`;
3183
+ function noCredentialSentence(version) {
3184
+ return `Halfcycle is not connected: this machine has no working Halfcycle credential for this project. Run "${bareHalfcycleCommand(version)}" in the project folder to sign in again.`;
3185
+ }
3186
+ var NO_CREDENTIAL_SENTENCE = noCredentialSentence();
3187
+ function notThisAccountSentence(version) {
3188
+ return `Halfcycle is not connected: this project belongs to another Halfcycle account. To switch, run "${bareHalfcycleCommand(version)}" in this project and ${SWITCH_ACCOUNT_ANSWER}.`;
3189
+ }
3190
+ var NOT_THIS_ACCOUNT_SENTENCE = notThisAccountSentence();
3140
3191
  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.";
3141
3192
  var NO_NODE_SENTENCE = "Halfcycle is not connected: Node is not installed, or not on the path this session uses, so nothing here is checked. Install Node, then reopen.";
3142
3193
  var PLACEHOLDER_SENTENCES = Object.freeze({
@@ -3572,9 +3623,9 @@ var phaseModelsFileSchema = z26.object({
3572
3623
  }).strict();
3573
3624
 
3574
3625
  // dist/engagement-credential.js
3575
- import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3626
+ import { chmodSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
3576
3627
  import { platform as platform2 } from "node:os";
3577
- import { join as join2 } from "node:path";
3628
+ import { join as join3 } from "node:path";
3578
3629
 
3579
3630
  // dist/account-credential.js
3580
3631
  import { homedir, platform } from "node:os";
@@ -3583,6 +3634,46 @@ function halfcycleHome(home) {
3583
3634
  return join(home ?? homedir(), HALFCYCLE_DIR_NAME);
3584
3635
  }
3585
3636
 
3637
+ // dist/own-version.js
3638
+ import { existsSync, readFileSync } from "node:fs";
3639
+ import { dirname as dirname2, join as join2 } from "node:path";
3640
+ import { fileURLToPath } from "node:url";
3641
+ var moduleDir = dirname2(fileURLToPath(import.meta.url));
3642
+ function findBundleRoot(startDir) {
3643
+ let candidate = startDir;
3644
+ for (let i = 0; i < 4; i++) {
3645
+ const manifest = join2(candidate, ".claude-plugin", "plugin.json");
3646
+ try {
3647
+ if (existsSync(manifest))
3648
+ return candidate;
3649
+ } catch {
3650
+ }
3651
+ candidate = dirname2(candidate);
3652
+ }
3653
+ return join2(startDir, "..");
3654
+ }
3655
+ var BUNDLE_ROOT = findBundleRoot(moduleDir);
3656
+ function bundleVersion() {
3657
+ try {
3658
+ const pkg = JSON.parse(readFileSync(join2(BUNDLE_ROOT, "package.json"), "utf-8"));
3659
+ return typeof pkg.version === "string" ? pkg.version : void 0;
3660
+ } catch {
3661
+ return void 0;
3662
+ }
3663
+ }
3664
+ var cachedVersion;
3665
+ function ownVersion() {
3666
+ if (cachedVersion === void 0)
3667
+ cachedVersion = { value: bundleVersion() };
3668
+ return cachedVersion.value;
3669
+ }
3670
+ function ownCommand() {
3671
+ return halfcycleCommand(ownVersion());
3672
+ }
3673
+ function ownBareCommand() {
3674
+ return bareHalfcycleCommand(ownVersion());
3675
+ }
3676
+
3586
3677
  // dist/engagement-credential.js
3587
3678
  var ENGAGEMENTS_DIR = ENGAGEMENTS_DIR_NAME;
3588
3679
  var ENV_FILENAME = ENGAGEMENT_ENV_FILENAME;
@@ -3590,7 +3681,7 @@ var HALFCYCLE_DIR = HALFCYCLE_DIR_NAME;
3590
3681
  var InvalidEngagementIdError = class extends Error {
3591
3682
  constructor(engagementId) {
3592
3683
  const shown = JSON.stringify(engagementId.length > 80 ? `${engagementId.slice(0, 80)}\u2026` : engagementId);
3593
- super(`${shown} is not a Halfcycle project id, so it cannot name a folder on this machine and nothing was written. A project id looks like 3f2a1c4e-8b7d-4e2f-9a61-0c5d7e8f9b10. If it is the "${PIN_ENGAGEMENT_ID_FIELD}" in .halfcycle/bundle.json, delete that file and run "npx halfcycle" again: this repository is then set up as a new Halfcycle project.`);
3684
+ super(`${shown} is not a Halfcycle project id, so it cannot name a folder on this machine and nothing was written. A project id looks like 3f2a1c4e-8b7d-4e2f-9a61-0c5d7e8f9b10. If it is the "${PIN_ENGAGEMENT_ID_FIELD}" in .halfcycle/bundle.json, delete that file and run "${ownBareCommand()}" again: this repository is then set up as a new Halfcycle project.`);
3594
3685
  this.name = "InvalidEngagementIdError";
3595
3686
  }
3596
3687
  };
@@ -3600,10 +3691,10 @@ function assertEngagementId(engagementId) {
3600
3691
  }
3601
3692
  function engagementStateDir(engagementId, home) {
3602
3693
  assertEngagementId(engagementId);
3603
- return join2(halfcycleHome(home), ENGAGEMENTS_DIR, engagementId);
3694
+ return join3(halfcycleHome(home), ENGAGEMENTS_DIR, engagementId);
3604
3695
  }
3605
3696
  function engagementEnvPath(engagementId, home) {
3606
- return join2(engagementStateDir(engagementId, home), ENV_FILENAME);
3697
+ return join3(engagementStateDir(engagementId, home), ENV_FILENAME);
3607
3698
  }
3608
3699
  var GUARD_ENV_KEYS = [
3609
3700
  "GUARD_SERVICE_URL",
@@ -3625,7 +3716,7 @@ function reconcileEnvText2(existing, values, keys = ENGAGEMENT_ENV_KEYS) {
3625
3716
  }
3626
3717
  function readEngagementEnv(engagementId, home) {
3627
3718
  try {
3628
- return parseEnvText(readFileSync(engagementEnvPath(engagementId, home), "utf-8"));
3719
+ return parseEnvText(readFileSync2(engagementEnvPath(engagementId, home), "utf-8"));
3629
3720
  } catch {
3630
3721
  return null;
3631
3722
  }
@@ -3635,7 +3726,7 @@ function writeEngagementEnv(engagementId, values, home, keys = ENGAGEMENT_ENV_KE
3635
3726
  const dir = engagementStateDir(engagementId, home);
3636
3727
  let existing;
3637
3728
  try {
3638
- existing = readFileSync(path, "utf-8");
3729
+ existing = readFileSync2(path, "utf-8");
3639
3730
  } catch {
3640
3731
  existing = null;
3641
3732
  }
@@ -3666,7 +3757,7 @@ function engagementResolutionShell() {
3666
3757
  return `# --- Halfcycle credential resolution (generated; do not edit) ----------------
3667
3758
  # THE CREDENTIAL IS NOT IN THIS REPOSITORY. It lives at
3668
3759
  # $HOME/${HALFCYCLE_DIR}/${ENGAGEMENTS_DIR}/<engagement-id>/${ENV_FILENAME}
3669
- # written owner-only by \`npx halfcycle\`. What this repository holds is the
3760
+ # written owner-only by \`${bareHalfcycleCommand()}\`. What this repository holds is the
3670
3761
  # engagement id, in the committed .halfcycle/bundle.json. So: read the id out of
3671
3762
  # the pin, then name the file. There is no jq here and node is not dependable on a
3672
3763
  # desktop session's PATH, so the id is cut out with sed \u2014 after flattening the
@@ -3720,8 +3811,8 @@ halfcycle_env_file() {
3720
3811
 
3721
3812
  // dist/identity.js
3722
3813
  import { execFileSync } from "node:child_process";
3723
- import { existsSync, linkSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
3724
- import { dirname as dirname2, isAbsolute, join as join3 } from "node:path";
3814
+ import { existsSync as existsSync2, linkSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
3815
+ import { dirname as dirname3, isAbsolute, join as join4 } from "node:path";
3725
3816
  import { randomUUID } from "node:crypto";
3726
3817
  var PROJECT_IDENTITY_NOTE = 'projectId is NOT the engagement id \u2014 it identifies this repository/worktree to Halfcycle, not an engagement. The engagement id lives in .halfcycle/bundle.json, in its "engagementId" field.';
3727
3818
  var PROJECT_IDENTITY_REL = ".halfcycle/project.json";
@@ -3834,13 +3925,13 @@ function readIgnoredPaths(targetRepoRoot, paths) {
3834
3925
  const toplevel = git(targetRepoRoot, ["rev-parse", "--show-toplevel"]);
3835
3926
  if (toplevel === null)
3836
3927
  return null;
3837
- return ignored.map((entry) => isAbsolute(entry.source) ? entry : { ...entry, source: join3(toplevel, entry.source) });
3928
+ return ignored.map((entry) => isAbsolute(entry.source) ? entry : { ...entry, source: join4(toplevel, entry.source) });
3838
3929
  }
3839
3930
  function mintOrReadIdentity(targetRepoRoot) {
3840
- const path = join3(targetRepoRoot, ".halfcycle", "project.json");
3841
- if (existsSync(path)) {
3931
+ const path = join4(targetRepoRoot, ".halfcycle", "project.json");
3932
+ if (existsSync2(path)) {
3842
3933
  repairRemoteCredential(targetRepoRoot);
3843
- const onDisk = JSON.parse(readFileSync2(path, "utf-8"));
3934
+ const onDisk = JSON.parse(readFileSync3(path, "utf-8"));
3844
3935
  const identity = {
3845
3936
  projectId: onDisk.projectId ?? "",
3846
3937
  rootCommit: onDisk.rootCommit ?? null,
@@ -3865,10 +3956,10 @@ function repairRemoteCredential(targetRepoRoot) {
3865
3956
  replaceProjectIdentityFile(targetRepoRoot, repaired);
3866
3957
  }
3867
3958
  function plannedRemoteRepair(targetRepoRoot) {
3868
- const path = join3(targetRepoRoot, PROJECT_IDENTITY_REL);
3959
+ const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
3869
3960
  let onDisk;
3870
3961
  try {
3871
- const parsed = JSON.parse(readFileSync2(path, "utf-8"));
3962
+ const parsed = JSON.parse(readFileSync3(path, "utf-8"));
3872
3963
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
3873
3964
  return null;
3874
3965
  onDisk = parsed;
@@ -3887,7 +3978,7 @@ function plannedRemoteRepair(targetRepoRoot) {
3887
3978
  });
3888
3979
  }
3889
3980
  function replaceProjectIdentityFile(targetRepoRoot, text) {
3890
- const path = join3(targetRepoRoot, PROJECT_IDENTITY_REL);
3981
+ const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
3891
3982
  const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
3892
3983
  try {
3893
3984
  writeFileSync2(temporary, text, { encoding: "utf-8", flag: "wx" });
@@ -3898,8 +3989,8 @@ function replaceProjectIdentityFile(targetRepoRoot, text) {
3898
3989
  }
3899
3990
  }
3900
3991
  function planProjectIdentity(targetRepoRoot) {
3901
- const path = join3(targetRepoRoot, PROJECT_IDENTITY_REL);
3902
- if (!existsSync(path)) {
3992
+ const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
3993
+ if (!existsSync2(path)) {
3903
3994
  return { action: "add", after: serializeProjectIdentity(mintIdentity(targetRepoRoot)) };
3904
3995
  }
3905
3996
  const repaired = plannedRemoteRepair(targetRepoRoot);
@@ -3907,7 +3998,7 @@ function planProjectIdentity(targetRepoRoot) {
3907
3998
  return { action: "change", after: repaired };
3908
3999
  let current;
3909
4000
  try {
3910
- current = readFileSync2(path, "utf-8");
4001
+ current = readFileSync3(path, "utf-8");
3911
4002
  } catch {
3912
4003
  current = null;
3913
4004
  }
@@ -3917,8 +4008,8 @@ function serializeProjectIdentity(identity) {
3917
4008
  return JSON.stringify(identity, null, 2) + "\n";
3918
4009
  }
3919
4010
  function createProjectIdentityFile(targetRepoRoot, text) {
3920
- const path = join3(targetRepoRoot, PROJECT_IDENTITY_REL);
3921
- mkdirSync2(dirname2(path), { recursive: true });
4011
+ const path = join4(targetRepoRoot, PROJECT_IDENTITY_REL);
4012
+ mkdirSync2(dirname3(path), { recursive: true });
3922
4013
  const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
3923
4014
  try {
3924
4015
  writeFileSync2(temporary, text ?? serializeProjectIdentity(mintIdentity(targetRepoRoot)), {
@@ -3937,8 +4028,8 @@ function createProjectIdentityFile(targetRepoRoot, text) {
3937
4028
  }
3938
4029
 
3939
4030
  // dist/install-plan.js
3940
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
3941
- import { join as join5, posix } from "node:path";
4031
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
4032
+ import { join as join6, posix } from "node:path";
3942
4033
 
3943
4034
  // dist/install-manifest.js
3944
4035
  import { Buffer as Buffer2 } from "node:buffer";
@@ -4076,11 +4167,15 @@ function parseManifest(text) {
4076
4167
  "hooksCreated",
4077
4168
  "permissionsCreated",
4078
4169
  "denyCreated",
4079
- "statusLineSha256"
4170
+ "statusLineSha256",
4171
+ "attributionSha256"
4080
4172
  ], '"settings"');
4081
4173
  if (s["statusLineSha256"] !== void 0 && (typeof s["statusLineSha256"] !== "string" || !HEX64.test(s["statusLineSha256"]))) {
4082
4174
  throw new UnreadableManifestError('"settings.statusLineSha256" is not a hash');
4083
4175
  }
4176
+ if (s["attributionSha256"] !== void 0 && (typeof s["attributionSha256"] !== "string" || !HEX64.test(s["attributionSha256"]))) {
4177
+ throw new UnreadableManifestError('"settings.attributionSha256" is not a hash');
4178
+ }
4084
4179
  const settings = {
4085
4180
  created: bool(s["created"], "settings.created"),
4086
4181
  adopted: bool(s["adopted"], "settings.adopted"),
@@ -4091,7 +4186,8 @@ function parseManifest(text) {
4091
4186
  hooksCreated: bool(s["hooksCreated"], "settings.hooksCreated"),
4092
4187
  permissionsCreated: bool(s["permissionsCreated"], "settings.permissionsCreated"),
4093
4188
  denyCreated: bool(s["denyCreated"], "settings.denyCreated"),
4094
- ...typeof s["statusLineSha256"] === "string" ? { statusLineSha256: s["statusLineSha256"] } : {}
4189
+ ...typeof s["statusLineSha256"] === "string" ? { statusLineSha256: s["statusLineSha256"] } : {},
4190
+ ...typeof s["attributionSha256"] === "string" ? { attributionSha256: s["attributionSha256"] } : {}
4095
4191
  };
4096
4192
  let mcp;
4097
4193
  if (raw["mcp"] !== void 0) {
@@ -4198,6 +4294,7 @@ function recordSettings(draft, observed) {
4198
4294
  return;
4199
4295
  }
4200
4296
  const statusLineSha256 = observed.statusLineSha256 ?? prev.statusLineSha256;
4297
+ const attributionSha256 = observed.attributionSha256 ?? prev.attributionSha256;
4201
4298
  draft.settings = {
4202
4299
  created: prev.created,
4203
4300
  adopted: prev.adopted,
@@ -4208,7 +4305,8 @@ function recordSettings(draft, observed) {
4208
4305
  hooksCreated: prev.hooksCreated,
4209
4306
  permissionsCreated: prev.permissionsCreated,
4210
4307
  denyCreated: prev.denyCreated,
4211
- ...statusLineSha256 !== void 0 ? { statusLineSha256 } : {}
4308
+ ...statusLineSha256 !== void 0 ? { statusLineSha256 } : {},
4309
+ ...attributionSha256 !== void 0 ? { attributionSha256 } : {}
4212
4310
  };
4213
4311
  }
4214
4312
  function recordDenyRetired(draft, hashes) {
@@ -4289,8 +4387,8 @@ function union(a, b) {
4289
4387
  }
4290
4388
 
4291
4389
  // dist/scan.js
4292
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
4293
- import { join as join4 } from "node:path";
4390
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
4391
+ import { join as join5 } from "node:path";
4294
4392
  var HALFCYCLE_STATE_FORMAT = "halfcycle-state-file/v1";
4295
4393
  var HALFCYCLE_STATE_NOTE = [
4296
4394
  "This file is a local cache written ONCE when Halfcycle was installed. It is not a",
@@ -4313,7 +4411,7 @@ var LAYER_ARTEFACT_PATHS = {
4313
4411
  function scanLayers(targetRepo, scannedAt = (/* @__PURE__ */ new Date()).toISOString()) {
4314
4412
  const layers = {};
4315
4413
  for (const [layer, paths] of Object.entries(LAYER_ARTEFACT_PATHS)) {
4316
- const found = paths.filter((p) => existsSync2(join4(targetRepo, p)));
4414
+ const found = paths.filter((p) => existsSync3(join5(targetRepo, p)));
4317
4415
  if (found.length > 0) {
4318
4416
  layers[layer] = {
4319
4417
  value: "asserted",
@@ -4333,17 +4431,17 @@ function runBootstrapScan(targetRepo) {
4333
4431
  return { state: planned.state, ran: planned.text !== null };
4334
4432
  }
4335
4433
  function planBootstrapScan(targetRepo) {
4336
- const statePath = join4(targetRepo, BOOTSTRAP_STATE_REL);
4337
- if (existsSync2(statePath)) {
4338
- const existing = JSON.parse(readFileSync3(statePath, "utf-8"));
4434
+ const statePath = join5(targetRepo, BOOTSTRAP_STATE_REL);
4435
+ if (existsSync3(statePath)) {
4436
+ const existing = JSON.parse(readFileSync4(statePath, "utf-8"));
4339
4437
  return { state: existing, text: null };
4340
4438
  }
4341
4439
  const state = scanLayers(targetRepo);
4342
4440
  return { state, text: JSON.stringify(state, null, 2) + "\n" };
4343
4441
  }
4344
4442
  function writeBootstrapState(targetRepo, text) {
4345
- mkdirSync3(join4(targetRepo, ".halfcycle"), { recursive: true });
4346
- writeFileSync3(join4(targetRepo, BOOTSTRAP_STATE_REL), text, "utf-8");
4443
+ mkdirSync3(join5(targetRepo, ".halfcycle"), { recursive: true });
4444
+ writeFileSync3(join5(targetRepo, BOOTSTRAP_STATE_REL), text, "utf-8");
4347
4445
  }
4348
4446
  function mergeWithRecords(state, layersWithRealRecords) {
4349
4447
  const merged = {};
@@ -4500,7 +4598,7 @@ function kindOf(rel) {
4500
4598
  }
4501
4599
  var STEPS = /* @__PURE__ */ new WeakMap();
4502
4600
  function planInstall(options) {
4503
- const { targetRepo, engagementId, engagementType, credential, home, env, createdByAccountId } = options;
4601
+ const { targetRepo, engagementId, engagementType, credential, home, env, createdByAccountId, serviceUrl } = options;
4504
4602
  const preview = engagementId === void 0;
4505
4603
  if (!preview) {
4506
4604
  assertEngagementId(engagementId);
@@ -4508,12 +4606,12 @@ function planInstall(options) {
4508
4606
  throw new Error("[halfcycle] a plan for a project needs its kind as well as its id.");
4509
4607
  }
4510
4608
  }
4511
- if (!existsSync3(targetRepo)) {
4609
+ if (!existsSync4(targetRepo)) {
4512
4610
  throw new Error(`[halfcycle] Target repo does not exist: ${targetRepo}`);
4513
4611
  }
4514
4612
  const writeSet = closedWriteSet();
4515
4613
  const previous = readPreviousManifest(targetRepo);
4516
- const dirsBefore = new Set([...writeSet.dirs].filter((dir) => existsSync3(join5(targetRepo, dir))));
4614
+ const dirsBefore = new Set([...writeSet.dirs].filter((dir) => existsSync4(join6(targetRepo, dir))));
4517
4615
  const { version, commands } = planCommandStubs(targetRepo, previous, writeSet);
4518
4616
  const codexSkills = planCodexSkills(targetRepo, previous, writeSet);
4519
4617
  const vendored = planVendoredBinary(targetRepo);
@@ -4535,12 +4633,12 @@ function planInstall(options) {
4535
4633
  const gitattributes = planGitattributes(targetRepo);
4536
4634
  const identity = planProjectIdentity(targetRepo);
4537
4635
  if (identity.after === null)
4538
- readFileSync4(join5(targetRepo, PROJECT_IDENTITY_REL));
4636
+ readFileSync5(join6(targetRepo, PROJECT_IDENTITY_REL));
4539
4637
  const credentialPlan = !preview && credential !== void 0 ? planEngagementCredential(credential, engagementId, home) : null;
4540
4638
  const credentialWrite = credentialPlan === null ? null : { outcome: credentialPlan.outcome, existed: credentialPlan.existed, path: engagementEnvPath(engagementId, home) };
4541
4639
  const storedAfter = credentialPlan?.storedAfter ?? (preview ? Object.fromEntries(ENGAGEMENT_ENV_KEYS.map((key) => [key, "supplied at sign-in"])) : readEngagementEnv(engagementId, home) ?? {});
4542
4640
  const legacy = migrateLegacyEnvLocal(targetRepo, storedAfter);
4543
- const pin = preview ? planBundlePinPreview(targetRepo, version, home) : planBundlePin(targetRepo, version, engagementId, engagementType, createdByAccountId, credential?.methodVersion);
4641
+ const pin = preview ? planBundlePinPreview(targetRepo, version, home) : planBundlePin(targetRepo, version, engagementId, engagementType, createdByAccountId, credential?.methodVersion, credential?.serviceUrl ?? serviceUrl);
4544
4642
  const crew = planCrewRoster(targetRepo);
4545
4643
  const crewProfiles = planCrewProfiles(targetRepo, previous, writeSet);
4546
4644
  const scan = { text: planBootstrapScan(targetRepo).text };
@@ -4668,7 +4766,7 @@ function entriesOf(steps, record2) {
4668
4766
  }
4669
4767
  };
4670
4768
  const pending = (rel) => {
4671
- const there = existsSync3(join5(targetRepo, rel));
4769
+ const there = existsSync4(join6(targetRepo, rel));
4672
4770
  entries.push({ path: rel, action: there ? "change" : "add", kind: kindOf(rel), bytes: PENDING });
4673
4771
  };
4674
4772
  steps.commands.forEach(fromFile);
@@ -4716,7 +4814,7 @@ function entriesOf(steps, record2) {
4716
4814
  action: "leave",
4717
4815
  kind: "codex-server",
4718
4816
  bytes: null,
4719
- ...existsSync3(join5(targetRepo, CODEX_CONFIG_REL2)) ? { yours: true } : {}
4817
+ ...existsSync4(join6(targetRepo, CODEX_CONFIG_REL2)) ? { yours: true } : {}
4720
4818
  });
4721
4819
  break;
4722
4820
  }
@@ -4795,10 +4893,10 @@ function applyInstallPlan(plan) {
4795
4893
  putPlannedCodexHooks(targetRepo, steps.codexHooks);
4796
4894
  putPlannedCodexConfig(targetRepo, steps.codexConfig);
4797
4895
  const gitignore = putPlannedGitignore(targetRepo, steps.gitignore);
4798
- const gitignorePath = join5(targetRepo, GITIGNORE_REL);
4799
- const gitignoreAfter = existsSync3(gitignorePath) ? readFileSync4(gitignorePath, "utf-8") : null;
4896
+ const gitignorePath = join6(targetRepo, GITIGNORE_REL);
4897
+ const gitignoreAfter = existsSync4(gitignorePath) ? readFileSync5(gitignorePath, "utf-8") : null;
4800
4898
  const gitattributes = putPlannedGitattributes(targetRepo, steps.gitattributes);
4801
- const gitattributesAfter = readTextOrNull(join5(targetRepo, GITATTRIBUTES_REL));
4899
+ const gitattributesAfter = readTextOrNull(join6(targetRepo, GITATTRIBUTES_REL));
4802
4900
  writeProjectIdentity(targetRepo, steps.identity);
4803
4901
  const credential = steps.credential;
4804
4902
  const credentialOutcome = steps.credentialWrite !== null && credential !== void 0 ? writeEngagementCredential(credential, engagementId, steps.home) : null;
@@ -4819,9 +4917,9 @@ function applyInstallPlan(plan) {
4819
4917
  gitattributesAfter,
4820
4918
  credential: credentialOutcome,
4821
4919
  legacy,
4822
- bytesAfter: (rel) => readFileSync4(join5(targetRepo, rel)),
4823
- dirExistsAfter: (dir) => existsSync3(join5(targetRepo, dir)),
4824
- stateExistsAfter: existsSync3(join5(targetRepo, BOOTSTRAP_STATE_REL))
4920
+ bytesAfter: (rel) => readFileSync5(join6(targetRepo, rel)),
4921
+ dirExistsAfter: (dir) => existsSync4(join6(targetRepo, dir)),
4922
+ stateExistsAfter: existsSync4(join6(targetRepo, BOOTSTRAP_STATE_REL))
4825
4923
  });
4826
4924
  const record2 = planInstallRecord(targetRepo, manifestText);
4827
4925
  putPlannedFile(targetRepo, record2);
@@ -4876,7 +4974,7 @@ function withoutHalfcycleCommands(entry, halfcycleCommands) {
4876
4974
  const remaining = entry.hooks.filter((h) => !isHalfcycleCommand(h, halfcycleCommands));
4877
4975
  return remaining.length > 0 ? { ...entry, hooks: remaining } : null;
4878
4976
  }
4879
- function mergeSettings(existing, generated, retiredOurs, statusLine) {
4977
+ function mergeSettings(existing, generated, retiredOurs, statusLine, attribution) {
4880
4978
  const merged = { ...existing };
4881
4979
  if (generated.$schema !== void 0) {
4882
4980
  merged.$schema = generated.$schema;
@@ -4902,6 +5000,9 @@ function mergeSettings(existing, generated, retiredOurs, statusLine) {
4902
5000
  if (statusLine !== void 0 && !("statusLine" in existing)) {
4903
5001
  merged.statusLine = { ...statusLine };
4904
5002
  }
5003
+ if (attribution !== void 0 && !("attribution" in existing) && !("includeCoAuthoredBy" in existing)) {
5004
+ merged.attribution = { ...attribution };
5005
+ }
4905
5006
  return merged;
4906
5007
  }
4907
5008
  function isPlainRecord(value) {
@@ -5172,8 +5273,8 @@ function crewProfileRel(agent, callsign) {
5172
5273
  }
5173
5274
  function crewProfileTarget(targetRepo, rel) {
5174
5275
  const folder = rel.startsWith(`${CLAUDE_AGENTS_DIR}/`) ? CLAUDE_AGENTS_DIR : CODEX_AGENTS_DIR;
5175
- const target = resolve(join6(targetRepo, rel));
5176
- if (dirname3(target) !== resolve(targetRepo, folder)) {
5276
+ const target = resolve(join7(targetRepo, rel));
5277
+ if (dirname4(target) !== resolve(targetRepo, folder)) {
5177
5278
  throw new Error(`[halfcycle] crew profile "${rel}" does not sit directly in ${folder}.`);
5178
5279
  }
5179
5280
  return target;
@@ -5256,41 +5357,25 @@ function renderCrewProfiles(roster = CREW_ROSTER) {
5256
5357
  ])
5257
5358
  ];
5258
5359
  }
5259
- var __filename = fileURLToPath(import.meta.url);
5260
- var __dirname = dirname3(__filename);
5261
- function findBundleRoot(startDir) {
5262
- let candidate = startDir;
5263
- for (let i = 0; i < 4; i++) {
5264
- const manifest = join6(candidate, ".claude-plugin", "plugin.json");
5265
- try {
5266
- if (existsSync4(manifest))
5267
- return candidate;
5268
- } catch {
5269
- }
5270
- candidate = dirname3(candidate);
5271
- }
5272
- return join6(startDir, "..");
5273
- }
5274
- var BUNDLE_ROOT = findBundleRoot(__dirname);
5275
5360
  function resolveVendoredBinary() {
5276
5361
  const candidates = [
5277
- join6(BUNDLE_ROOT, "bin", "bin.bundle.mjs"),
5278
- join6(BUNDLE_ROOT, "..", "runner", "dist", "bin.bundle.mjs")
5362
+ join7(BUNDLE_ROOT, "bin", "bin.bundle.mjs"),
5363
+ join7(BUNDLE_ROOT, "..", "runner", "dist", "bin.bundle.mjs")
5279
5364
  ];
5280
5365
  for (const candidate of candidates) {
5281
- if (existsSync4(candidate))
5366
+ if (existsSync5(candidate))
5282
5367
  return candidate;
5283
5368
  }
5284
5369
  throw new Error(`[halfcycle] Cannot find the self-contained guard binary bin.bundle.mjs. This copy of the halfcycle package is incomplete \u2014 reinstall it, or rebuild it if you are working on it. Looked in: ${candidates.join(", ")}.`);
5285
5370
  }
5286
5371
  function resolvePreFilterShell() {
5287
5372
  const candidates = [
5288
- join6(BUNDLE_ROOT, "bin", PRE_FILTER_SHELL_FILENAME),
5289
- join6(BUNDLE_ROOT, "..", "runner", "dist", PRE_FILTER_SHELL_FILENAME)
5373
+ join7(BUNDLE_ROOT, "bin", PRE_FILTER_SHELL_FILENAME),
5374
+ join7(BUNDLE_ROOT, "..", "runner", "dist", PRE_FILTER_SHELL_FILENAME)
5290
5375
  ];
5291
5376
  for (const candidate of candidates) {
5292
- if (existsSync4(candidate))
5293
- return readFileSync5(candidate, "utf-8");
5377
+ if (existsSync5(candidate))
5378
+ return readFileSync6(candidate, "utf-8");
5294
5379
  }
5295
5380
  throw new Error(`[halfcycle] Cannot find ${PRE_FILTER_SHELL_FILENAME}. This copy of the halfcycle package is incomplete \u2014 reinstall it, or rebuild it if you are working on it. Looked in: ${candidates.join(", ")}.`);
5296
5381
  }
@@ -5329,7 +5414,7 @@ function isAllowlisted(targetRelPath) {
5329
5414
  var CREW_ROSTER_REL = ".halfcycle/crew.json";
5330
5415
  var CAPTURED_INDEX_REL = "test/fixtures/captured/manifest.json";
5331
5416
  function capturedIndexScaffold() {
5332
- return join6(BUNDLE_ROOT, "scaffolding", "test", "fixtures", "captured", "manifest.json");
5417
+ return join7(BUNDLE_ROOT, "scaffolding", "test", "fixtures", "captured", "manifest.json");
5333
5418
  }
5334
5419
  var GITIGNORE_REL = ".gitignore";
5335
5420
  var GITATTRIBUTES_REL = ".gitattributes";
@@ -5398,20 +5483,22 @@ function closedWriteSet() {
5398
5483
  ...lfRules([...Object.keys(OWNED_GENERATED_HEADERS), ...RETIRED_WRITE_SET_FILES])
5399
5484
  ]),
5400
5485
  generatedSha256: sha256OfWhatThisVersionWrites(commands),
5401
- statusLineSha256: /* @__PURE__ */ new Set([canonicalSha256(HALFCYCLE_STATUS_LINE)])
5486
+ statusLineSha256: /* @__PURE__ */ new Set([canonicalSha256(HALFCYCLE_STATUS_LINE)]),
5487
+ attributionSha256: /* @__PURE__ */ new Set([canonicalSha256(HALFCYCLE_ATTRIBUTION)])
5402
5488
  };
5403
5489
  }
5404
5490
  function sha256OfWhatThisVersionWrites(commands) {
5405
5491
  const out = /* @__PURE__ */ new Map();
5406
5492
  const fromFile = (rel, src) => {
5407
- if (existsSync4(src))
5408
- out.set(rel, fileSha256(readFileSync5(src)));
5493
+ if (existsSync5(src))
5494
+ out.set(rel, fileSha256(readFileSync6(src)));
5409
5495
  };
5410
5496
  for (const cmd of commands) {
5411
- const srcFile = join6(BUNDLE_ROOT, ".claude-plugin", cmd.path);
5412
- fromFile(commandStubRel(cmd.name), srcFile);
5413
- if (existsSync4(srcFile)) {
5414
- out.set(skillRel(cmd.name), fileSha256(Buffer.from(codexSkillContent(cmd.name, readFileSync5(srcFile, "utf-8")), "utf-8")));
5497
+ const srcFile = join7(BUNDLE_ROOT, ".claude-plugin", cmd.path);
5498
+ if (existsSync5(srcFile)) {
5499
+ const text = commandFileText(readFileSync6(srcFile, "utf-8"), ownVersion());
5500
+ out.set(commandStubRel(cmd.name), fileSha256(Buffer.from(text, "utf-8")));
5501
+ out.set(skillRel(cmd.name), fileSha256(Buffer.from(codexSkillContent(cmd.name, text), "utf-8")));
5415
5502
  }
5416
5503
  }
5417
5504
  for (const [rel, content] of hookScriptsThisVersionWrites())
@@ -5428,7 +5515,7 @@ function bytesAreOurs(path, bytes, recorded, writeSet) {
5428
5515
  }
5429
5516
  function readPreviousManifest(targetRepo) {
5430
5517
  try {
5431
- return parseManifest(readFileSync5(join6(targetRepo, INSTALL_MANIFEST_REL), "utf-8"));
5518
+ return parseManifest(readFileSync6(join7(targetRepo, INSTALL_MANIFEST_REL), "utf-8"));
5432
5519
  } catch {
5433
5520
  return null;
5434
5521
  }
@@ -5458,7 +5545,8 @@ function recordSettingsMerge(draft, existing, generated, preexisted, merged) {
5458
5545
  hooksCreated: existing.hooks === void 0,
5459
5546
  permissionsCreated: existing.permissions === void 0,
5460
5547
  denyCreated: existing.permissions?.deny === void 0,
5461
- ...ourStatusLineSha256(merged) !== void 0 ? { statusLineSha256: ourStatusLineSha256(merged) } : {}
5548
+ ...ourStatusLineSha256(merged) !== void 0 ? { statusLineSha256: ourStatusLineSha256(merged) } : {},
5549
+ ...attributionWrittenThisRun(existing, merged) ? { attributionSha256: canonicalSha256(HALFCYCLE_ATTRIBUTION) } : {}
5462
5550
  });
5463
5551
  }
5464
5552
  function ourStatusLineSha256(settings) {
@@ -5467,6 +5555,11 @@ function ourStatusLineSha256(settings) {
5467
5555
  const hash = canonicalSha256(settings["statusLine"]);
5468
5556
  return hash === canonicalSha256(HALFCYCLE_STATUS_LINE) ? hash : void 0;
5469
5557
  }
5558
+ function attributionWrittenThisRun(existing, merged) {
5559
+ if ("attribution" in existing || "includeCoAuthoredBy" in existing)
5560
+ return false;
5561
+ return "attribution" in merged && canonicalSha256(merged["attribution"]) === canonicalSha256(HALFCYCLE_ATTRIBUTION);
5562
+ }
5470
5563
  function recordMcpMerge(draft, existingText, writtenText) {
5471
5564
  const before = existingText === null ? void 0 : JSON.parse(existingText);
5472
5565
  const servers = isPlainObject(before) ? before["mcpServers"] : void 0;
@@ -5532,8 +5625,8 @@ function findNotShared(targetRepo, manifest, collidedPaths) {
5532
5625
  for (let dir = posix2.dirname(rel); dir !== "."; dir = posix2.dirname(dir)) {
5533
5626
  let plain = dirVerdicts.get(dir);
5534
5627
  if (plain === void 0) {
5535
- const abs = join6(targetRepo, dir);
5536
- plain = !lstatOrNull(abs)?.isSymbolicLink() && lstatOrNull(join6(abs, ".git")) === null;
5628
+ const abs = join7(targetRepo, dir);
5629
+ plain = !lstatOrNull(abs)?.isSymbolicLink() && lstatOrNull(join7(abs, ".git")) === null;
5537
5630
  dirVerdicts.set(dir, plain);
5538
5631
  }
5539
5632
  if (!plain)
@@ -5547,7 +5640,7 @@ function findNotShared(targetRepo, manifest, collidedPaths) {
5547
5640
  ...merged,
5548
5641
  INSTALL_MANIFEST_REL
5549
5642
  ])
5550
- ].filter((rel) => !collided.has(rel)).filter((rel) => lstatOrNull(join6(targetRepo, rel)) !== null).filter(travelsThroughPlainDirs).sort();
5643
+ ].filter((rel) => !collided.has(rel)).filter((rel) => lstatOrNull(join7(targetRepo, rel)) !== null).filter(travelsThroughPlainDirs).sort();
5551
5644
  const ignored = readIgnoredPaths(targetRepo, paths);
5552
5645
  if (ignored === null)
5553
5646
  return null;
@@ -5570,7 +5663,7 @@ function lstatOrNull(path) {
5570
5663
  }
5571
5664
  function readFileOrNull(path) {
5572
5665
  try {
5573
- return readFileSync5(path, "utf-8");
5666
+ return readFileSync6(path, "utf-8");
5574
5667
  } catch {
5575
5668
  return null;
5576
5669
  }
@@ -5579,8 +5672,8 @@ function isPlainObject(value) {
5579
5672
  return value !== null && typeof value === "object" && !Array.isArray(value);
5580
5673
  }
5581
5674
  function readPluginManifest() {
5582
- const manifestPath = join6(BUNDLE_ROOT, ".claude-plugin", "plugin.json");
5583
- const raw = readFileSync5(manifestPath, "utf-8");
5675
+ const manifestPath = join7(BUNDLE_ROOT, ".claude-plugin", "plugin.json");
5676
+ const raw = readFileSync6(manifestPath, "utf-8");
5584
5677
  return JSON.parse(raw);
5585
5678
  }
5586
5679
  function parseCommandStub(raw, name) {
@@ -5623,6 +5716,9 @@ function codexSkillBody(name, sourceBody) {
5623
5716
  }
5624
5717
  return transformed;
5625
5718
  }
5719
+ function commandFileText(source, version) {
5720
+ return rewriteHalfcycleCommands(source, releaseTagOfVersion(version));
5721
+ }
5626
5722
  function codexSkillContent(name, sourceRaw) {
5627
5723
  const { description, body } = parseCommandStub(sourceRaw, name);
5628
5724
  const transformed = codexSkillBody(name, body);
@@ -5635,14 +5731,14 @@ ${transformed}`;
5635
5731
  function planManifestCommands(manifest, targetRepo, previous, writeSet) {
5636
5732
  const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
5637
5733
  return (manifest.commands ?? []).map((cmd) => {
5638
- const srcFile = join6(BUNDLE_ROOT, ".claude-plugin", cmd.path);
5639
- if (!existsSync4(srcFile)) {
5734
+ const srcFile = join7(BUNDLE_ROOT, ".claude-plugin", cmd.path);
5735
+ if (!existsSync5(srcFile)) {
5640
5736
  throw new Error(`[halfcycle] plugin.json declares command "${cmd.name}" at "${cmd.path}", but no file exists there (${srcFile}). The manifest and commands/ must agree.`);
5641
5737
  }
5642
5738
  const rel = commandStubRel(cmd.name);
5643
- const content = readFileSync5(srcFile, "utf-8");
5739
+ const content = commandFileText(readFileSync6(srcFile, "utf-8"), ownVersion());
5644
5740
  const recorded = recordedFiles.get(rel);
5645
- const planned = decideCollisionSafe(join6(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
5741
+ const planned = decideCollisionSafe(join7(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
5646
5742
  return { ...planned, editedCollided: planned.outcome === "collided" && recorded !== void 0 };
5647
5743
  });
5648
5744
  }
@@ -5652,8 +5748,8 @@ function putPlannedFile(targetRepoRoot, planned) {
5652
5748
  if (!isAllowlisted(planned.rel)) {
5653
5749
  throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${planned.rel}". Only the method surface and scaffolding paths are writable.`);
5654
5750
  }
5655
- const target = join6(targetRepoRoot, planned.rel);
5656
- mkdirSync4(dirname3(target), { recursive: true });
5751
+ const target = join7(targetRepoRoot, planned.rel);
5752
+ mkdirSync4(dirname4(target), { recursive: true });
5657
5753
  writeFileSync4(target, planned.text, "utf-8");
5658
5754
  }
5659
5755
  function writeAllowlisted(targetAbsPath, targetRepoRoot, content, writtenPaths) {
@@ -5663,7 +5759,7 @@ function writeAllowlisted(targetAbsPath, targetRepoRoot, content, writtenPaths)
5663
5759
  }
5664
5760
  if (readFileOrNull(targetAbsPath) === content)
5665
5761
  return;
5666
- mkdirSync4(dirname3(targetAbsPath), { recursive: true });
5762
+ mkdirSync4(dirname4(targetAbsPath), { recursive: true });
5667
5763
  writeFileSync4(targetAbsPath, content, "utf-8");
5668
5764
  writtenPaths.push(rel);
5669
5765
  }
@@ -5672,7 +5768,7 @@ function decideCollisionSafe(targetAbsPath, targetRepoRoot, content, isOurs) {
5672
5768
  if (!isAllowlisted(rel)) {
5673
5769
  throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
5674
5770
  }
5675
- const current = existsSync4(targetAbsPath) ? readFileSync5(targetAbsPath) : null;
5771
+ const current = existsSync5(targetAbsPath) ? readFileSync6(targetAbsPath) : null;
5676
5772
  const text = inLineEndingOf(current?.toString("utf-8") ?? null, content);
5677
5773
  if (current === null)
5678
5774
  return { rel, text, outcome: "written", replacedExisting: false };
@@ -5695,7 +5791,7 @@ function decideOwned(targetAbsPath, targetRepoRoot, content) {
5695
5791
  const text = inLineEndingOf(current, content);
5696
5792
  if (current === text)
5697
5793
  return { rel, text, outcome: "skipped", replacedExisting: false };
5698
- return { rel, text, outcome: "written", replacedExisting: existsSync4(targetAbsPath) };
5794
+ return { rel, text, outcome: "written", replacedExisting: existsSync5(targetAbsPath) };
5699
5795
  }
5700
5796
  var GENERATED_HEADER_LINE_INDEX = 1;
5701
5797
  function decideOwnedGenerated(targetAbsPath, targetRepoRoot, content, generatedMarker) {
@@ -5703,9 +5799,9 @@ function decideOwnedGenerated(targetAbsPath, targetRepoRoot, content, generatedM
5703
5799
  if (!isAllowlisted(rel)) {
5704
5800
  throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
5705
5801
  }
5706
- if (!existsSync4(targetAbsPath))
5802
+ if (!existsSync5(targetAbsPath))
5707
5803
  return { rel, text: content, outcome: "written", replacedExisting: false };
5708
- const current = readFileSync5(targetAbsPath, "utf-8");
5804
+ const current = readFileSync6(targetAbsPath, "utf-8");
5709
5805
  if (current === content)
5710
5806
  return { rel, text: content, outcome: "skipped", replacedExisting: false };
5711
5807
  const headerLine = current.split("\n")[GENERATED_HEADER_LINE_INDEX] ?? "";
@@ -5735,8 +5831,8 @@ var DENY_PATTERNS = [
5735
5831
  ];
5736
5832
  var USER_PROMPT_REMINDER_SENTENCE = "Where this project stands, and what order to work in, is recorded in the Halfcycle service and nowhere in this repository \u2014 call halfcycle_resolve before you answer or plan, because the files here will look complete and current and still be wrong about position.";
5737
5833
  function readExistingSettings(targetRepo) {
5738
- const settingsPath = join6(targetRepo, SETTINGS_REL);
5739
- return existsSync4(settingsPath) ? JSON.parse(readFileSync5(settingsPath, "utf-8")) : void 0;
5834
+ const settingsPath = join7(targetRepo, SETTINGS_REL);
5835
+ return existsSync5(settingsPath) ? JSON.parse(readFileSync6(settingsPath, "utf-8")) : void 0;
5740
5836
  }
5741
5837
  var SettingsParseRefused = class extends Error {
5742
5838
  constructor(message) {
@@ -5745,33 +5841,37 @@ var SettingsParseRefused = class extends Error {
5745
5841
  }
5746
5842
  };
5747
5843
  function settingsParseRefusal(targetRepo, err) {
5748
- const file = join6(targetRepo, SETTINGS_REL);
5844
+ const file = join7(targetRepo, SETTINGS_REL);
5749
5845
  const offset = /position (\d+)/.exec(err instanceof Error ? err.message : "")?.[1];
5750
5846
  let where = "";
5751
5847
  if (offset !== void 0) {
5752
- const before = readFileSync5(file, "utf-8").slice(0, Number(offset)).split("\n");
5848
+ const before = readFileSync6(file, "utf-8").slice(0, Number(offset)).split("\n");
5753
5849
  where = ` (line ${before.length}, column ${before[before.length - 1].length + 1})`;
5754
5850
  }
5755
- return `${file} could not be read as JSON${where} \u2014 fix it and run "npx halfcycle" again.`;
5851
+ return `${file} could not be read as JSON${where} \u2014 fix it and run "${ownBareCommand()}" again.`;
5756
5852
  }
5757
5853
  var SETTINGS_SCHEMA = "https://json.schemastore.org/claude-code-settings.json";
5758
5854
  var HALFCYCLE_STATUS_LINE = Object.freeze({
5759
5855
  type: "command",
5760
5856
  command: 'bash "$CLAUDE_PROJECT_DIR"/.claude/hooks/guard-runner.sh statusline'
5761
5857
  });
5858
+ var HALFCYCLE_ATTRIBUTION = Object.freeze({
5859
+ commit: "",
5860
+ pr: ""
5861
+ });
5762
5862
  function statusLineSetElsewhere(targetRepo, home = homedir2(), env = process.env) {
5763
- const places = [join6(targetRepo, ".claude", "settings.local.json"), join6(home, ".claude", "settings.json")];
5863
+ const places = [join7(targetRepo, ".claude", "settings.local.json"), join7(home, ".claude", "settings.json")];
5764
5864
  const configDir = env["CLAUDE_CONFIG_DIR"];
5765
5865
  if (configDir !== void 0 && configDir !== "") {
5766
- const expanded = configDir === "~" ? home : configDir.startsWith("~/") ? join6(home, configDir.slice(2)) : configDir;
5767
- places.push(join6(isAbsolute2(expanded) ? expanded : resolve(expanded), "settings.json"));
5866
+ const expanded = configDir === "~" ? home : configDir.startsWith("~/") ? join7(home, configDir.slice(2)) : configDir;
5867
+ places.push(join7(isAbsolute2(expanded) ? expanded : resolve(expanded), "settings.json"));
5768
5868
  }
5769
5869
  return places.some((path) => {
5770
- if (!existsSync4(path))
5870
+ if (!existsSync5(path))
5771
5871
  return false;
5772
5872
  let value;
5773
5873
  try {
5774
- value = JSON.parse(readFileSync5(path, "utf-8"));
5874
+ value = JSON.parse(readFileSync6(path, "utf-8"));
5775
5875
  } catch {
5776
5876
  return true;
5777
5877
  }
@@ -6114,8 +6214,10 @@ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the
6114
6214
  # "autonomy on" or "autonomy off". Also lets the person who is typing let one
6115
6215
  # step Halfcycle paused run once, by typing "allow", or "allow" and the step's
6116
6216
  # number ("allow 2"), as the whole prompt, and change
6117
- # how a build is paced, by typing "faster", "run 5 agents", "keep 30% free" or
6118
- # "use up to 70%" as the whole prompt, and answer a phase's usage limit by
6217
+ # how many agents a build runs, by typing "faster", "run 5 agents", "run 20
6218
+ # agents at once", "max pace" or "as many agents as possible", or how much is
6219
+ # kept free, by typing "keep 30% free" or "use up to 70%", as the whole prompt,
6220
+ # with or without a build running, and answer a phase's usage limit by
6119
6221
  # typing "keep going", "stop", "limit it to 1.5 times" or "limit it to 10% of
6120
6222
  # the week" as the whole prompt.
6121
6223
  #
@@ -6124,7 +6226,7 @@ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the
6124
6226
  # any whitespace (typed, or escaped in the input), with "approve ", "send back ",
6125
6227
  # "accept ", "question ", "carry ", "drop ", "reject " or "autonomy ", in any case,
6126
6228
  # or is "allow" (alone, or with spaces and a number of up to four digits), one
6127
- # of the five pacing phrases or one of the usage-limit phrases
6229
+ # of the pacing phrases or one of the usage-limit phrases
6128
6230
  # and nothing else but whitespace,
6129
6231
  # it exits 0 with no output, and Node never starts. A quote inside any other field arrives
6130
6232
  # escaped, so no other field (the folder the session is in, for one) can read as
@@ -6152,7 +6254,11 @@ IFS= read -r -d '' HC_INPUT || true
6152
6254
  # \\u escape, or as the UTF-8 bytes of a Unicode space. Compared byte for byte.
6153
6255
  HC_LEAD='[[:space:]]|\\\\[nrtf]|\\\\u(000[9a-d]|0020|00a0|1680|200[0-9a]|202[89f]|205f|3000|feff)'
6154
6256
  HC_LEAD="$HC_LEAD|"$'\\xc2\\xa0|\\xe1\\x9a\\x80|\\xe2\\x80[\\x80-\\x8a\\xa8\\xa9\\xaf]|\\xe2\\x81\\x9f|\\xe3\\x80\\x80|\\xef\\xbb\\xbf'
6155
- if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*((approve|send back|accept|question|carry|drop|reject|autonomy|decline) |allow( +[0-9]{1,4})?($HC_LEAD)*\\"|(faster|go faster|run [0-9]{1,3} agents?|keep [0-9]{1,3}% free|use up to [0-9]{1,3}%|keep going|stop|stop here|limit it to [0-9]{1,9}(\\.[0-9]{1,2})? (times|million|billion|million tokens|billion tokens)|limit it to [0-9]{1,3}% of the week)($HC_LEAD)*\\")"; then
6257
+ # The pace phrases, matched whole after an optional "please" (the verb's own
6258
+ # reading is the same set or narrower): a number of agents, the most, faster, a
6259
+ # vague amount it answers with an ask for a number, or a free share.
6260
+ HC_PACE='((run|use|start|go)( +with| +at)? +)?(up +to +|the +)?(faster|more +agents|(-?[0-9]+(\\.[0-9]+)?|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty) +agents?|(max|maximum|full|top) +(pace|speed)|(max|maximum)( +possible)?( +number +of)? +agents|as +many +agents +as +(possible|you +can)|(lots +of|a +lot +of|loads +of|plenty +of|many|some|several|a +few|a +bunch +of) +agents)( +(at +once|at +a +time|in +parallel|concurrently|simultaneously|together))?|keep +[0-9]{1,3}% +free|use +up +to +[0-9]{1,3}%'
6261
+ if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*((approve|send back|accept|question|carry|drop|reject|autonomy|decline) |allow( +[0-9]{1,4})?($HC_LEAD)*\\"|(keep going|stop|stop here|limit it to [0-9]{1,9}(\\.[0-9]{1,2})? (times|million|billion|million tokens|billion tokens)|limit it to [0-9]{1,3}% of the week)($HC_LEAD)*\\"|(please,? +)?($HC_PACE)(,? +please)?[.!]*($HC_LEAD)*\\")"; then
6156
6262
  exit 0
6157
6263
  fi
6158
6264
 
@@ -6238,12 +6344,12 @@ function isTrackedAndClean(targetRepo, relPath) {
6238
6344
  return true;
6239
6345
  }
6240
6346
  function decideRetiredRemoval(targetRepo, rel, headerLineIndex, knownHeaderHashes) {
6241
- const path = join6(targetRepo, rel);
6242
- if (!existsSync4(path))
6347
+ const path = join7(targetRepo, rel);
6348
+ if (!existsSync5(path))
6243
6349
  return "absent";
6244
6350
  let raw;
6245
6351
  try {
6246
- raw = readFileSync5(path, "utf-8");
6352
+ raw = readFileSync6(path, "utf-8");
6247
6353
  } catch {
6248
6354
  return "failed";
6249
6355
  }
@@ -6257,7 +6363,7 @@ function decideRetiredRemoval(targetRepo, rel, headerLineIndex, knownHeaderHashe
6257
6363
  }
6258
6364
  function removePlannedRetiredFile(targetRepo, rel) {
6259
6365
  try {
6260
- rmSync2(join6(targetRepo, rel));
6366
+ rmSync2(join7(targetRepo, rel));
6261
6367
  } catch {
6262
6368
  return "failed";
6263
6369
  }
@@ -6278,7 +6384,7 @@ var MCP_HEADERS_HELPER_COMMAND = `node -e "${nodeFloorProgram([
6278
6384
  `const u=p.dirname(d);`,
6279
6385
  `if(u===d)break;`,
6280
6386
  `d=u}`,
6281
- `console.error('halfcycle: no ${VENDORED_BIN_REL} at or above '+process.cwd()+'; run npx halfcycle in the project folder');`,
6387
+ `console.error('halfcycle: no ${VENDORED_BIN_REL} at or above '+process.cwd()+'; run ${ownBareCommand()} in the project folder');`,
6282
6388
  `process.exit(1)`
6283
6389
  ].join(""))}"`;
6284
6390
  var CODEX_ADDRESS_PATTERN = /^https?:\/\/[A-Za-z0-9.-]+(:[0-9]{1,5})?(\/[A-Za-z0-9._-]+)*\/mcp$/;
@@ -6302,7 +6408,7 @@ function codexMcpHeadersHelperCommand(address) {
6302
6408
  `if(u===d)break;`,
6303
6409
  `d=u}`,
6304
6410
  `if(b===''){`,
6305
- `console.error('halfcycle: no ${VENDORED_BIN_REL} at or above '+process.cwd()+'; run npx halfcycle in the project folder');`,
6411
+ `console.error('halfcycle: no ${VENDORED_BIN_REL} at or above '+process.cwd()+'; run ${ownBareCommand()} in the project folder');`,
6306
6412
  `n()}else{`,
6307
6413
  `const r=c.spawnSync(process.execPath,[b,'mcp-headers','--url','${address}'],{stdio:['ignore','pipe','inherit']});`,
6308
6414
  `if(r.status===0&&r.stdout&&r.stdout.length){o=1;process.stdout.write(r.stdout)}else{n()}}`,
@@ -6628,9 +6734,9 @@ function gitignoreCovers(content) {
6628
6734
  return REQUIRED_GITIGNORE_ENTRIES.every((entry) => lines.has(entry));
6629
6735
  }
6630
6736
  function planGitignore(targetRepo) {
6631
- const gitignorePath = join6(targetRepo, GITIGNORE_REL);
6737
+ const gitignorePath = join7(targetRepo, GITIGNORE_REL);
6632
6738
  const header = GITIGNORE_HEADER;
6633
- const before = existsSync4(gitignorePath) ? readFileSync5(gitignorePath, "utf-8") : null;
6739
+ const before = existsSync5(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : null;
6634
6740
  if (before === null) {
6635
6741
  return { before, after: [header, ...REQUIRED_GITIGNORE_ENTRIES].join("\n") + "\n", outcome: "written" };
6636
6742
  }
@@ -6646,7 +6752,7 @@ function putPlannedGitignore(targetRepo, planned) {
6646
6752
  if (planned.outcome !== "written" || planned.after === null)
6647
6753
  return planned.outcome;
6648
6754
  try {
6649
- writeFileSync4(join6(targetRepo, GITIGNORE_REL), planned.after, "utf-8");
6755
+ writeFileSync4(join7(targetRepo, GITIGNORE_REL), planned.after, "utf-8");
6650
6756
  return "written";
6651
6757
  } catch {
6652
6758
  return "failed";
@@ -6655,7 +6761,7 @@ function putPlannedGitignore(targetRepo, planned) {
6655
6761
  var GITATTRIBUTES_HEADER = "# Halfcycle \u2014 generated scripts keep LF line endings";
6656
6762
  function readTextOrNull(path) {
6657
6763
  try {
6658
- return readFileSync5(path, "utf-8");
6764
+ return readFileSync6(path, "utf-8");
6659
6765
  } catch {
6660
6766
  return null;
6661
6767
  }
@@ -6665,12 +6771,12 @@ function lfRules(paths) {
6665
6771
  }
6666
6772
  var REQUIRED_GITATTRIBUTES_RULES = lfRules(Object.keys(OWNED_GENERATED_HEADERS));
6667
6773
  function planGitattributes(targetRepo) {
6668
- const path = join6(targetRepo, GITATTRIBUTES_REL);
6774
+ const path = join7(targetRepo, GITATTRIBUTES_REL);
6669
6775
  const rules = REQUIRED_GITATTRIBUTES_RULES;
6670
6776
  const before = readTextOrNull(path);
6671
6777
  if (lstatOrNull(path)?.isSymbolicLink() === true)
6672
6778
  return { before, after: before, outcome: "failed" };
6673
- if (!existsSync4(path)) {
6779
+ if (!existsSync5(path)) {
6674
6780
  return { before, after: [GITATTRIBUTES_HEADER, ...rules].join("\n") + "\n", outcome: "written" };
6675
6781
  }
6676
6782
  if (before === null)
@@ -6688,7 +6794,7 @@ function putPlannedGitattributes(targetRepo, planned) {
6688
6794
  if (planned.outcome !== "written" || planned.after === null)
6689
6795
  return planned.outcome;
6690
6796
  try {
6691
- writeAllowlisted(join6(targetRepo, GITATTRIBUTES_REL), targetRepo, planned.after, []);
6797
+ writeAllowlisted(join7(targetRepo, GITATTRIBUTES_REL), targetRepo, planned.after, []);
6692
6798
  return "written";
6693
6799
  } catch {
6694
6800
  return "failed";
@@ -6707,12 +6813,12 @@ function previousPinForEngagement(targetRepoRoot, engagementId) {
6707
6813
  function nonEmpty(value) {
6708
6814
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
6709
6815
  }
6710
- function planBundlePin(targetRepoRoot, version, engagementId, engagementType, createdByAccountId, methodVersion) {
6816
+ function planBundlePin(targetRepoRoot, version, engagementId, engagementType, createdByAccountId, methodVersion, serviceUrl) {
6711
6817
  const previous = previousPinForEngagement(targetRepoRoot, engagementId);
6712
6818
  const carried = pinCreatedByAccountId(previous) ?? nonEmpty(createdByAccountId);
6713
6819
  const carriedMethodVersion = nonEmpty(methodVersion) ?? nonEmpty(previous?.methodVersion);
6714
6820
  const installedAt = nonEmpty(previous?.installedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
6715
- const projectPage = projectPageUrl(engagementId);
6821
+ const projectPage = projectPageUrl(engagementId, serviceUrl);
6716
6822
  const pin = {
6717
6823
  version,
6718
6824
  engagementId,
@@ -6722,7 +6828,7 @@ function planBundlePin(targetRepoRoot, version, engagementId, engagementType, cr
6722
6828
  ...carriedMethodVersion !== void 0 ? { methodVersion: carriedMethodVersion } : {},
6723
6829
  ...projectPage !== void 0 ? { projectPage } : {}
6724
6830
  };
6725
- const current = readFileOrNull(join6(targetRepoRoot, BUNDLE_PIN_REL));
6831
+ const current = readFileOrNull(join7(targetRepoRoot, BUNDLE_PIN_REL));
6726
6832
  const text = inLineEndingOf(current, JSON.stringify(pin, null, 2) + "\n");
6727
6833
  return {
6728
6834
  rel: BUNDLE_PIN_REL,
@@ -6750,10 +6856,10 @@ function planBundlePinPreview(targetRepoRoot, version, home) {
6750
6856
  if (pinned === null || pinned.engagementId !== existing.engagementId || pinned.credential === void 0) {
6751
6857
  return null;
6752
6858
  }
6753
- return planBundlePin(targetRepoRoot, version, existing.engagementId, existing.engagementType, void 0, void 0);
6859
+ return planBundlePin(targetRepoRoot, version, existing.engagementId, existing.engagementType, void 0, void 0, pinned.credential.serviceUrl);
6754
6860
  }
6755
6861
  function writeBundlePin(targetRepoRoot, planned, writtenPaths) {
6756
- const pinPath = join6(targetRepoRoot, BUNDLE_PIN_REL);
6862
+ const pinPath = join7(targetRepoRoot, BUNDLE_PIN_REL);
6757
6863
  writeAllowlisted(pinPath, targetRepoRoot, planned.text, writtenPaths);
6758
6864
  }
6759
6865
  function renderCrewRoster() {
@@ -6768,10 +6874,10 @@ function pinCreatedByAccountId(pin) {
6768
6874
  return nonEmpty(pin?.createdByAccountId) ?? nonEmpty(pin?.accountId);
6769
6875
  }
6770
6876
  function readBundlePin(targetRepoRoot) {
6771
- const pinPath = join6(targetRepoRoot, BUNDLE_PIN_REL);
6772
- if (!existsSync4(pinPath))
6877
+ const pinPath = join7(targetRepoRoot, BUNDLE_PIN_REL);
6878
+ if (!existsSync5(pinPath))
6773
6879
  return null;
6774
- return JSON.parse(readFileSync5(pinPath, "utf-8"));
6880
+ return JSON.parse(readFileSync6(pinPath, "utf-8"));
6775
6881
  }
6776
6882
  function readPinnedEngagement(targetRepoRoot, home) {
6777
6883
  const pin = readBundlePin(targetRepoRoot);
@@ -6794,7 +6900,7 @@ function readPinnedEngagement(targetRepoRoot, home) {
6794
6900
  }
6795
6901
  function readLegacyEnvLocal(targetRepoRoot) {
6796
6902
  try {
6797
- return parseEnvText(readFileSync5(join6(targetRepoRoot, LEGACY_ENV_LOCAL_REL), "utf-8"));
6903
+ return parseEnvText(readFileSync6(join7(targetRepoRoot, LEGACY_ENV_LOCAL_REL), "utf-8"));
6798
6904
  } catch {
6799
6905
  return null;
6800
6906
  }
@@ -6843,7 +6949,7 @@ function writeEngagementCredential(credential, engagementId, home) {
6843
6949
  function planEngagementCredential(credential, engagementId, home) {
6844
6950
  let existing;
6845
6951
  try {
6846
- existing = readFileSync5(engagementEnvPath(engagementId, home), "utf-8");
6952
+ existing = readFileSync6(engagementEnvPath(engagementId, home), "utf-8");
6847
6953
  } catch {
6848
6954
  existing = null;
6849
6955
  }
@@ -6886,12 +6992,12 @@ function engagementCredentialValues(credential, engagementId) {
6886
6992
  }
6887
6993
  var LEGACY_ENV_LOCAL_REL = ".env.local";
6888
6994
  function migrateLegacyEnvLocal(targetRepo, stored) {
6889
- const path = join6(targetRepo, LEGACY_ENV_LOCAL_REL);
6890
- if (!existsSync4(path))
6995
+ const path = join7(targetRepo, LEGACY_ENV_LOCAL_REL);
6996
+ if (!existsSync5(path))
6891
6997
  return { outcome: "absent", unbacked: [], missingForAdoption: [] };
6892
6998
  let raw;
6893
6999
  try {
6894
- raw = readFileSync5(path, "utf-8");
7000
+ raw = readFileSync6(path, "utf-8");
6895
7001
  } catch {
6896
7002
  return { outcome: "failed", unbacked: [], missingForAdoption: [] };
6897
7003
  }
@@ -6917,7 +7023,7 @@ function migrateLegacyEnvLocal(targetRepo, stored) {
6917
7023
  return { outcome: "stripped", unbacked: [], missingForAdoption: [], keptText: kept.join("\n") };
6918
7024
  }
6919
7025
  function applyLegacyMigration(targetRepo, decided) {
6920
- const path = join6(targetRepo, LEGACY_ENV_LOCAL_REL);
7026
+ const path = join7(targetRepo, LEGACY_ENV_LOCAL_REL);
6921
7027
  try {
6922
7028
  if (decided.outcome === "removed") {
6923
7029
  rmSync2(path);
@@ -6943,23 +7049,23 @@ function planCodexSkills(targetRepo, previous, writeSet) {
6943
7049
  const manifest = readPluginManifest();
6944
7050
  const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
6945
7051
  return (manifest.commands ?? []).map((cmd) => {
6946
- const srcFile = join6(BUNDLE_ROOT, ".claude-plugin", cmd.path);
6947
- if (!existsSync4(srcFile)) {
7052
+ const srcFile = join7(BUNDLE_ROOT, ".claude-plugin", cmd.path);
7053
+ if (!existsSync5(srcFile)) {
6948
7054
  throw new Error(`[halfcycle] plugin.json declares command "${cmd.name}" at "${cmd.path}", but no file exists there (${srcFile}). The manifest and commands/ must agree.`);
6949
7055
  }
6950
7056
  const rel = skillRel(cmd.name);
6951
- const content = codexSkillContent(cmd.name, readFileSync5(srcFile, "utf-8"));
7057
+ const content = codexSkillContent(cmd.name, commandFileText(readFileSync6(srcFile, "utf-8"), ownVersion()));
6952
7058
  const recorded = recordedFiles.get(rel);
6953
- const planned = decideCollisionSafe(join6(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
7059
+ const planned = decideCollisionSafe(join7(targetRepo, rel), targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
6954
7060
  return { ...planned, editedCollided: planned.outcome === "collided" && recorded !== void 0 };
6955
7061
  });
6956
7062
  }
6957
7063
  function planVendoredBinary(targetRepo) {
6958
- return decideOwned(join6(targetRepo, ".halfcycle", "bin", "bin.bundle.mjs"), targetRepo, readFileSync5(resolveVendoredBinary(), "utf-8"));
7064
+ return decideOwned(join7(targetRepo, ".halfcycle", "bin", "bin.bundle.mjs"), targetRepo, readFileSync6(resolveVendoredBinary(), "utf-8"));
6959
7065
  }
6960
7066
  function planSettings(targetRepo, previous, home, env) {
6961
- const settingsPath = join6(targetRepo, SETTINGS_REL);
6962
- const preexisted = existsSync4(settingsPath);
7067
+ const settingsPath = join7(targetRepo, SETTINGS_REL);
7068
+ const preexisted = existsSync5(settingsPath);
6963
7069
  const generated = JSON.parse(generateSettingsJson());
6964
7070
  let existing;
6965
7071
  try {
@@ -6969,7 +7075,7 @@ function planSettings(targetRepo, previous, home, env) {
6969
7075
  }
6970
7076
  const retiredOurs = retiredRulesThisInstallAdded(previous);
6971
7077
  const statusLine = statusLineSetElsewhere(targetRepo, home, env) ? void 0 : HALFCYCLE_STATUS_LINE;
6972
- const merged = mergeSettings(existing, generated, retiredOurs, statusLine);
7078
+ const merged = mergeSettings(existing, generated, retiredOurs, statusLine, HALFCYCLE_ATTRIBUTION);
6973
7079
  const current = readFileOrNull(settingsPath);
6974
7080
  const text = inLineEndingOf(current, JSON.stringify(merged, null, 2) + "\n");
6975
7081
  const file = {
@@ -6983,11 +7089,11 @@ function planSettings(targetRepo, previous, home, env) {
6983
7089
  return { file, preexisted, existing, generated, merged, retiredOurs };
6984
7090
  }
6985
7091
  function planHookScripts(targetRepo) {
6986
- return hookScriptsThisVersionWrites().map(([rel, content]) => decideOwnedGenerated(join6(targetRepo, rel), targetRepo, content, OWNED_GENERATED_HEADERS[rel]));
7092
+ return hookScriptsThisVersionWrites().map(([rel, content]) => decideOwnedGenerated(join7(targetRepo, rel), targetRepo, content, OWNED_GENERATED_HEADERS[rel]));
6987
7093
  }
6988
7094
  function planMcpRegistration(targetRepo, credential) {
6989
- const mcpPath = join6(targetRepo, MCP_REGISTRATION_REL);
6990
- const existing = existsSync4(mcpPath) ? readFileSync5(mcpPath, "utf-8") : null;
7095
+ const mcpPath = join7(targetRepo, MCP_REGISTRATION_REL);
7096
+ const existing = existsSync5(mcpPath) ? readFileSync6(mcpPath, "utf-8") : null;
6991
7097
  if (credential === void 0)
6992
7098
  return { kind: "none", existing };
6993
7099
  if (credential === "pending")
@@ -6999,11 +7105,11 @@ function planMcpRegistration(targetRepo, credential) {
6999
7105
  function putPlannedMcp(targetRepo, planned) {
7000
7106
  if (planned.kind !== "write" || planned.outcome === "skipped")
7001
7107
  return;
7002
- writeAllowlisted(join6(targetRepo, MCP_REGISTRATION_REL), targetRepo, planned.text, []);
7108
+ writeAllowlisted(join7(targetRepo, MCP_REGISTRATION_REL), targetRepo, planned.text, []);
7003
7109
  }
7004
7110
  function fileInFolderPlace(root, rel) {
7005
7111
  for (let dir = posix2.dirname(rel); dir !== "."; dir = posix2.dirname(dir)) {
7006
- const found = lstatOrNull(join6(root, dir));
7112
+ const found = lstatOrNull(join7(root, dir));
7007
7113
  if (found !== null && !found.isDirectory())
7008
7114
  return true;
7009
7115
  }
@@ -7012,7 +7118,7 @@ function fileInFolderPlace(root, rel) {
7012
7118
  function symlinkOnPath(root, rel) {
7013
7119
  let current = root;
7014
7120
  for (const segment of rel.split("/")) {
7015
- current = join6(current, segment);
7121
+ current = join7(current, segment);
7016
7122
  try {
7017
7123
  if (lstatSync(current).isSymbolicLink())
7018
7124
  return true;
@@ -7033,14 +7139,14 @@ function planCodexHooks(targetRepo) {
7033
7139
  });
7034
7140
  if (symlinkOnPath(targetRepo, CODEX_HOOKS_REL2))
7035
7141
  return leave("link", null);
7036
- const folder = lstatOrNull(join6(targetRepo, posix2.dirname(CODEX_HOOKS_REL2)));
7142
+ const folder = lstatOrNull(join7(targetRepo, posix2.dirname(CODEX_HOOKS_REL2)));
7037
7143
  if (folder !== null && !folder.isDirectory())
7038
7144
  return leave("unreadable", null);
7039
- const path = join6(targetRepo, CODEX_HOOKS_REL2);
7145
+ const path = join7(targetRepo, CODEX_HOOKS_REL2);
7040
7146
  let before = null;
7041
7147
  if (lstatOrNull(path) !== null) {
7042
7148
  try {
7043
- before = readFileSync5(path, "utf-8");
7149
+ before = readFileSync6(path, "utf-8");
7044
7150
  } catch {
7045
7151
  return leave("unreadable", null);
7046
7152
  }
@@ -7074,13 +7180,13 @@ function planCodexHooks(targetRepo) {
7074
7180
  function putPlannedCodexHooks(targetRepo, planned) {
7075
7181
  if (planned.outcome !== "written" || planned.text === null)
7076
7182
  return;
7077
- writeAllowlisted(join6(targetRepo, CODEX_HOOKS_REL2), targetRepo, planned.text, []);
7183
+ writeAllowlisted(join7(targetRepo, CODEX_HOOKS_REL2), targetRepo, planned.text, []);
7078
7184
  }
7079
7185
  function readCodexConfig(targetRepo) {
7080
- const folder = lstatOrNull(join6(targetRepo, posix2.dirname(CODEX_CONFIG_REL2)));
7186
+ const folder = lstatOrNull(join7(targetRepo, posix2.dirname(CODEX_CONFIG_REL2)));
7081
7187
  if (folder !== null && !folder.isDirectory())
7082
7188
  return "unreadable";
7083
- const path = join6(targetRepo, CODEX_CONFIG_REL2);
7189
+ const path = join7(targetRepo, CODEX_CONFIG_REL2);
7084
7190
  const stat = lstatOrNull(path);
7085
7191
  if (stat === null)
7086
7192
  return { text: null };
@@ -7088,7 +7194,7 @@ function readCodexConfig(targetRepo) {
7088
7194
  return "unreadable";
7089
7195
  let bytes;
7090
7196
  try {
7091
- bytes = readFileSync5(path);
7197
+ bytes = readFileSync6(path);
7092
7198
  } catch {
7093
7199
  return "unreadable";
7094
7200
  }
@@ -7134,10 +7240,10 @@ function planCodexConfig(targetRepo, credential) {
7134
7240
  function putPlannedCodexConfig(targetRepo, planned) {
7135
7241
  if (planned.outcome !== "written" || planned.text === null)
7136
7242
  return;
7137
- writeAllowlisted(join6(targetRepo, CODEX_CONFIG_REL2), targetRepo, planned.text, []);
7243
+ writeAllowlisted(join7(targetRepo, CODEX_CONFIG_REL2), targetRepo, planned.text, []);
7138
7244
  }
7139
7245
  function planCrewRoster(targetRepo) {
7140
- return decideOwned(join6(targetRepo, CREW_ROSTER_REL), targetRepo, renderCrewRoster());
7246
+ return decideOwned(join7(targetRepo, CREW_ROSTER_REL), targetRepo, renderCrewRoster());
7141
7247
  }
7142
7248
  function planCrewProfiles(targetRepo, previous, writeSet) {
7143
7249
  const recordedFiles = new Map((previous?.files ?? []).filter((file) => writeSet.files.has(file.path)).map((file) => [file.path, file]));
@@ -7152,7 +7258,7 @@ function planCrewProfiles(targetRepo, previous, writeSet) {
7152
7258
  });
7153
7259
  }
7154
7260
  function planInstallRecord(targetRepo, text) {
7155
- return decideOwned(join6(targetRepo, INSTALL_MANIFEST_REL), targetRepo, text);
7261
+ return decideOwned(join7(targetRepo, INSTALL_MANIFEST_REL), targetRepo, text);
7156
7262
  }
7157
7263
  function accountInstall(steps, actual) {
7158
7264
  const { targetRepo, writeSet, pin } = steps;
@@ -7526,8 +7632,8 @@ async function requestEngagementValues(method, url, requestBody, credential, sha
7526
7632
  }
7527
7633
 
7528
7634
  // dist/setup/manifest.js
7529
- import { existsSync as existsSync5, lstatSync as lstatSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync5 } from "node:fs";
7530
- import { dirname as dirname4, join as join7, posix as posix3 } from "node:path";
7635
+ import { existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync7, statSync, writeFileSync as writeFileSync5 } from "node:fs";
7636
+ import { dirname as dirname5, join as join8, posix as posix3 } from "node:path";
7531
7637
  var DECLARED_ROOT_FILES = ["CLAUDE.md", "AGENTS.md", "ENGAGEMENT.md"];
7532
7638
  var DECLARED_DIR_PREFIX = "docs";
7533
7639
  var RESERVED_METHOD_PATH = "docs/method";
@@ -7612,7 +7718,7 @@ function firstSymlinkOnPath(targetRepo, path) {
7612
7718
  const segments = path.split("/").filter((s) => s.length > 0);
7613
7719
  let walked = targetRepo;
7614
7720
  for (let i = 0; i < segments.length; i++) {
7615
- walked = join7(walked, segments[i]);
7721
+ walked = join8(walked, segments[i]);
7616
7722
  try {
7617
7723
  if (lstatSync2(walked).isSymbolicLink())
7618
7724
  return segments.slice(0, i + 1).join("/");
@@ -7630,30 +7736,30 @@ function symlinkEntry(path, link, isDirectoryRow) {
7630
7736
  };
7631
7737
  }
7632
7738
  function applyFileRow(targetRepo, path, content) {
7633
- const abs = join7(targetRepo, path);
7739
+ const abs = join8(targetRepo, path);
7634
7740
  const link = firstSymlinkOnPath(targetRepo, path);
7635
7741
  if (link !== null)
7636
7742
  return symlinkEntry(path, link, false);
7637
- if (existsSync5(abs)) {
7638
- const current = readFileSync6(abs, "utf-8");
7743
+ if (existsSync6(abs)) {
7744
+ const current = readFileSync7(abs, "utf-8");
7639
7745
  return current === content ? { path, outcome: "already-present", note: "already present and correct \u2014 nothing written" } : { path, outcome: "left-alone", note: "you already own this file \u2014 left exactly as it was" };
7640
7746
  }
7641
- mkdirSync5(dirname4(abs), { recursive: true });
7747
+ mkdirSync5(dirname5(abs), { recursive: true });
7642
7748
  writeFileSync5(abs, content, "utf-8");
7643
7749
  return { path, outcome: "created", note: "created" };
7644
7750
  }
7645
7751
  function applySectionRow(targetRepo, path, heading, body) {
7646
- const abs = join7(targetRepo, path);
7752
+ const abs = join8(targetRepo, path);
7647
7753
  const link = firstSymlinkOnPath(targetRepo, path);
7648
7754
  if (link !== null)
7649
7755
  return symlinkEntry(path, link, false);
7650
7756
  const section = renderSection(heading, body);
7651
- if (!existsSync5(abs)) {
7652
- mkdirSync5(dirname4(abs), { recursive: true });
7757
+ if (!existsSync6(abs)) {
7758
+ mkdirSync5(dirname5(abs), { recursive: true });
7653
7759
  writeFileSync5(abs, section, "utf-8");
7654
7760
  return { path, outcome: "created", note: `created, with a "${heading}" section` };
7655
7761
  }
7656
- const existing = readFileSync6(abs, "utf-8");
7762
+ const existing = readFileSync7(abs, "utf-8");
7657
7763
  if (hasSection(existing, heading)) {
7658
7764
  return {
7659
7765
  path,
@@ -7670,11 +7776,11 @@ function applySectionRow(targetRepo, path, heading, body) {
7670
7776
  };
7671
7777
  }
7672
7778
  function applyDirectoryRow(targetRepo, path) {
7673
- const abs = join7(targetRepo, path);
7779
+ const abs = join8(targetRepo, path);
7674
7780
  const link = firstSymlinkOnPath(targetRepo, path);
7675
7781
  if (link !== null)
7676
7782
  return symlinkEntry(path, link, true);
7677
- if (existsSync5(abs)) {
7783
+ if (existsSync6(abs)) {
7678
7784
  const kind = statSync(abs).isDirectory() ? "directory" : "file";
7679
7785
  return kind === "directory" ? { path: `${path}/`, outcome: "already-present", note: "already there \u2014 nothing written" } : { path, outcome: "left-alone", note: "you already own this path \u2014 left exactly as it was" };
7680
7786
  }
@@ -7682,7 +7788,7 @@ function applyDirectoryRow(targetRepo, path) {
7682
7788
  return { path: `${path}/`, outcome: "created", note: "created" };
7683
7789
  }
7684
7790
  function applySetupWrites(targetRepo, rows) {
7685
- if (!existsSync5(targetRepo)) {
7791
+ if (!existsSync6(targetRepo)) {
7686
7792
  throw new Error(`[halfcycle setup] The project directory does not exist: ${targetRepo}`);
7687
7793
  }
7688
7794
  assertNoConflictingDuplicates(rows);
@@ -7793,8 +7899,8 @@ function trackerHomeRow(home = FLAT_FILE_TRACKER_HOME) {
7793
7899
  }
7794
7900
 
7795
7901
  // dist/setup/discovery.js
7796
- import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync, statSync as statSync2 } from "node:fs";
7797
- import { join as join8 } from "node:path";
7902
+ import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync, statSync as statSync2 } from "node:fs";
7903
+ import { join as join9 } from "node:path";
7798
7904
  function renderAnchor(anchor) {
7799
7905
  return anchor.kind === "file" ? `${anchor.path}:${anchor.line}` : `$ ${anchor.command}`;
7800
7906
  }
@@ -7818,8 +7924,8 @@ function lineOf(text, needle) {
7818
7924
  return null;
7819
7925
  }
7820
7926
  function readOrNull(repo, rel) {
7821
- const abs = join8(repo, rel);
7822
- return existsSync6(abs) && statSync2(abs).isFile() ? readFileSync7(abs, "utf-8") : null;
7927
+ const abs = join9(repo, rel);
7928
+ return existsSync7(abs) && statSync2(abs).isFile() ? readFileSync8(abs, "utf-8") : null;
7823
7929
  }
7824
7930
  function anchoredIn(repo, rel, needle, value) {
7825
7931
  const text = readOrNull(repo, rel);
@@ -7831,8 +7937,8 @@ function anchoredIn(repo, rel, needle, value) {
7831
7937
  return { value, anchor: { kind: "file", path: rel, line } };
7832
7938
  }
7833
7939
  function presenceAnchor(repo, rel) {
7834
- const abs = join8(repo, rel);
7835
- if (!existsSync6(abs))
7940
+ const abs = join9(repo, rel);
7941
+ if (!existsSync7(abs))
7836
7942
  return null;
7837
7943
  return statSync2(abs).isDirectory() ? { kind: "command", command: `ls -d ${rel}`, output: `${rel}/` } : { kind: "file", path: rel, line: 1 };
7838
7944
  }
@@ -7873,8 +7979,8 @@ function firstMatch(items, f) {
7873
7979
  }
7874
7980
  var CONTEXT_INDEX_FILES = ["AGENTS.md", "CLAUDE.md"];
7875
7981
  function ciWorkflows(repo) {
7876
- const dir = join8(repo, ".github", "workflows");
7877
- if (!existsSync6(dir) || !statSync2(dir).isDirectory())
7982
+ const dir = join9(repo, ".github", "workflows");
7983
+ if (!existsSync7(dir) || !statSync2(dir).isDirectory())
7878
7984
  return [];
7879
7985
  return readdirSync(dir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")).sort();
7880
7986
  }
@@ -7882,21 +7988,21 @@ function existingLayerDocuments(repo) {
7882
7988
  const found = [];
7883
7989
  for (const paths of Object.values(LAYER_ARTEFACT_PATHS)) {
7884
7990
  for (const p of paths ?? []) {
7885
- if (existsSync6(join8(repo, p)))
7991
+ if (existsSync7(join9(repo, p)))
7886
7992
  found.push(p);
7887
7993
  }
7888
7994
  }
7889
7995
  return found.sort();
7890
7996
  }
7891
7997
  function discoverRepository(targetRepo) {
7892
- if (!existsSync6(targetRepo)) {
7998
+ if (!existsSync7(targetRepo)) {
7893
7999
  throw new Error(`[halfcycle setup] The project directory does not exist: ${targetRepo}`);
7894
8000
  }
7895
8001
  const findings = [];
7896
8002
  const questions = [];
7897
8003
  settleOrAsk("project-language", firstMatch(MANIFESTS, ([file, needle, language]) => anchoredIn(targetRepo, file, needle, language)), "What is this project written in?", findings, questions);
7898
8004
  settleOrAsk("package-manager", firstMatch(LOCKFILES, ([file, manager]) => anchoredByPresence(targetRepo, file, manager)), "Which package manager does this project use?", findings, questions);
7899
- settleOrAsk("repo-structure", firstMatch(WORKSPACE_MARKERS, ([file, needle, shape]) => anchoredIn(targetRepo, file, needle, shape)) ?? (existsSync6(join8(targetRepo, "package.json")) || existsSync6(join8(targetRepo, "go.mod")) || existsSync6(join8(targetRepo, "pyproject.toml")) ? anchoredByPresence(targetRepo, "package.json", "a single-package repository") ?? anchoredByPresence(targetRepo, "go.mod", "a single-package repository") ?? anchoredByPresence(targetRepo, "pyproject.toml", "a single-package repository") : null), "Is this one repository for one product, or one of several?", findings, questions);
8005
+ settleOrAsk("repo-structure", firstMatch(WORKSPACE_MARKERS, ([file, needle, shape]) => anchoredIn(targetRepo, file, needle, shape)) ?? (existsSync7(join9(targetRepo, "package.json")) || existsSync7(join9(targetRepo, "go.mod")) || existsSync7(join9(targetRepo, "pyproject.toml")) ? anchoredByPresence(targetRepo, "package.json", "a single-package repository") ?? anchoredByPresence(targetRepo, "go.mod", "a single-package repository") ?? anchoredByPresence(targetRepo, "pyproject.toml", "a single-package repository") : null), "Is this one repository for one product, or one of several?", findings, questions);
7900
8006
  const workflows = ciWorkflows(targetRepo);
7901
8007
  settleOrAsk("ci-strategy", workflows.length > 0 ? {
7902
8008
  value: `GitHub Actions \u2014 ${workflows.length === 1 ? "one workflow" : `${workflows.length} workflows`}: ${workflows.join(", ")}`,
@@ -8566,11 +8672,11 @@ function renderBuildRecordV2Markdown(record2) {
8566
8672
  }
8567
8673
 
8568
8674
  // dist/build-record/write.js
8569
- import { existsSync as existsSync7, mkdirSync as mkdirSync6, rmSync as rmSync4 } from "node:fs";
8570
- import { basename, join as join9, resolve as resolve2, relative as relative2, isAbsolute as isAbsolute3 } from "node:path";
8675
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, rmSync as rmSync4 } from "node:fs";
8676
+ import { basename, join as join10, resolve as resolve2, relative as relative2, isAbsolute as isAbsolute3 } from "node:path";
8571
8677
 
8572
8678
  // dist/build-record/pair-io.js
8573
- import { closeSync, openSync, readFileSync as readFileSync8, renameSync as renameSync2, rmSync as rmSync3, writeSync } from "node:fs";
8679
+ import { closeSync, openSync, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync3, writeSync } from "node:fs";
8574
8680
  function createExclusiveOnDisk(path, data) {
8575
8681
  const bytes = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
8576
8682
  const fd = openSync(path, "wx");
@@ -8601,7 +8707,7 @@ function isEexist(err) {
8601
8707
  }
8602
8708
  function readIfPresent(path) {
8603
8709
  try {
8604
- return readFileSync8(path, "utf-8");
8710
+ return readFileSync9(path, "utf-8");
8605
8711
  } catch {
8606
8712
  return null;
8607
8713
  }
@@ -8633,7 +8739,7 @@ function replaceOne(io, tmp, finalPath, text) {
8633
8739
  }
8634
8740
 
8635
8741
  // dist/build-record/write.js
8636
- var BUILD_RECORD_DIR = join9("docs", "build-records");
8742
+ var BUILD_RECORD_DIR = join10("docs", "build-records");
8637
8743
  function assertInsideOutDir(outDir, candidate, identity) {
8638
8744
  const rel = relative2(outDir, resolve2(candidate));
8639
8745
  const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute3(rel) && rel === basename(rel);
@@ -8671,8 +8777,8 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
8671
8777
  }
8672
8778
  const outDir = resolve2(repoRoot, BUILD_RECORD_DIR);
8673
8779
  const segment = renderPhaseSegment(phase);
8674
- const jsonPath = join9(outDir, `${segment}.json`);
8675
- const mdPath = join9(outDir, `${segment}.md`);
8780
+ const jsonPath = join10(outDir, `${segment}.json`);
8781
+ const mdPath = join10(outDir, `${segment}.md`);
8676
8782
  const jsonTmp = `${jsonPath}.tmp`;
8677
8783
  const mdTmp = `${mdPath}.tmp`;
8678
8784
  for (const candidate of [jsonPath, mdPath, jsonTmp, mdTmp]) {
@@ -8686,7 +8792,7 @@ function writeRecordPair(repoRoot, phase, rawRecord, record2, io = NODE_RECORD_P
8686
8792
  }
8687
8793
  const existingJson = readIfPresent(jsonPath);
8688
8794
  if (existingJson === null) {
8689
- return { kind: "in-the-way", path: existsSync7(jsonPath) ? jsonPath : mdPath };
8795
+ return { kind: "in-the-way", path: existsSync8(jsonPath) ? jsonPath : mdPath };
8690
8796
  }
8691
8797
  if (existingJson === jsonText) {
8692
8798
  replaceOne(io, mdTmp, mdPath, mdText);
@@ -8748,20 +8854,20 @@ function isMissingRoute(method, status, body) {
8748
8854
  }
8749
8855
 
8750
8856
  // dist/build-record/write-change.js
8751
- import { basename as basename2, isAbsolute as isAbsolute4, join as join10, relative as relative3, resolve as resolve3 } from "node:path";
8752
- var CHANGE_RECORD_DIR = join10("docs", "build-records", "changes");
8857
+ import { basename as basename2, isAbsolute as isAbsolute4, join as join11, relative as relative3, resolve as resolve3 } from "node:path";
8858
+ var CHANGE_RECORD_DIR = join11("docs", "build-records", "changes");
8753
8859
 
8754
8860
  // dist/change.js
8755
- var CHANGE_USAGE = `${HALFCYCLE_COMMAND} change sort --request "<one line>" --touches <path>\u2026 [--contract <from>:<to>]\u2026 [--stored-data] [--standing-rule] [--new-part] [--repo <root>]
8756
- ${HALFCYCLE_COMMAND} change up <mini|full> (--change <id> | --phase <phase>) [--repo <root>]
8757
- ${HALFCYCLE_COMMAND} change done <id> [--repo <root>]`;
8861
+ var CHANGE_USAGE = `${ownCommand()} change sort --request "<one line>" --touches <path>\u2026 [--contract <from>:<to>]\u2026 [--stored-data] [--standing-rule] [--new-part] [--repo <root>]
8862
+ ${ownCommand()} change up <mini|full> (--change <id> | --phase <phase>) [--repo <root>]
8863
+ ${ownCommand()} change done <id> [--repo <root>]`;
8758
8864
 
8759
8865
  // dist/plan.js
8760
- var PLAN_USAGE = `${HALFCYCLE_COMMAND} plan <phase> [--show] [--repo <root>]`;
8866
+ var PLAN_USAGE = `${ownCommand()} plan <phase> [--show] [--repo <root>]`;
8761
8867
 
8762
8868
  // dist/review.js
8763
- var REVIEW_USAGE = `${HALFCYCLE_COMMAND} review <phase> (--show | --answer <part> "<answer>") [--repo <root>]`;
8764
- var RV9_LINE = `halfcycle review: this project is not signed in on this computer. ${HOOK_REMEDY}`;
8869
+ var REVIEW_USAGE = `${ownCommand()} review <phase> (--show | --answer <part> "<answer>") [--repo <root>]`;
8870
+ var RV9_LINE = `halfcycle review: this project is not signed in on this computer. ${hookRemedy(ownVersion())}`;
8765
8871
 
8766
8872
  // dist/open-phase.js
8767
8873
  var OPEN_FROM_SIZE_REFUSALS = Object.freeze({
@@ -8877,16 +8983,16 @@ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_P
8877
8983
  };
8878
8984
  }
8879
8985
  function refetchCommand(phase) {
8880
- return `${HALFCYCLE_COMMAND} build-record ${quoteForShell(phase)}`;
8986
+ return `${ownCommand()} build-record ${quoteForShell(phase)}`;
8881
8987
  }
8882
8988
 
8883
8989
  // dist/check-tasks.js
8884
- var CHECK_TASKS_USAGE = `${HALFCYCLE_COMMAND} check-tasks <phase> [--with-task-list] [--repo <root>]`;
8990
+ var CHECK_TASKS_USAGE = `${ownCommand()} check-tasks <phase> [--with-task-list] [--repo <root>]`;
8885
8991
  var MISSING_PLAN_LINE = `there is no ${TASK_PLAN_FILE} in this project yet. Run the task-graph step to write it, then run this again.`;
8886
8992
  var SPEC_FOLDER = LAYER_ARTEFACT_PATHS.l4[0];
8887
8993
 
8888
8994
  // dist/ready.js
8889
- var READY_USAGE = `${HALFCYCLE_COMMAND} ready <phase> [--cap <n>] [--repo <root>]`;
8995
+ var READY_USAGE = `${ownCommand()} ready <phase> [--cap <n>] [--repo <root>]`;
8890
8996
  var STATES_TEXT = buildTaskStateSchema.options.join(", ");
8891
8997
  var READY_LINES = {
8892
8998
  start: (id, title) => `Ready to start: ${named(id, title)}`,