halfcycle 0.4.2 → 0.4.4

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.
@@ -69,11 +69,12 @@
69
69
  * silence back permanently and for every future question this CLI asks.
70
70
  *
71
71
  * So what this population gets instead is a record that does not live in scrollback.
72
- * The account id settled on here is RETURNED to the caller, which writes it into the
73
- * committed `.halfcycle/bundle.json` (`writeBundlePin` in `install.ts`) — the id and
74
- * never the email, because that file is committed into the client's repository. A
75
- * wrong-identity install is then discoverable afterwards, from the repository, by the
76
- * person whose repository it is. Printing is not recording.
72
+ * The account id settled on here is RETURNED to the caller, which on a CREATE writes it
73
+ * into the committed `.halfcycle/bundle.json` as `createdByAccountId` (`writeBundlePin`
74
+ * in `install.ts`; only the create writes it — E1, #1598) — the id and never the email,
75
+ * because that file is committed into the client's repository. A wrong-identity install
76
+ * is then discoverable afterwards, from the repository, by the person whose repository
77
+ * it is. Printing is not recording.
77
78
  *
78
79
  * The measurement is `isInteractiveRun` (a TTY plus the absence of a CI marker), whose
79
80
  * one home is `device-signin.ts`. It is never inferred from anything else. (The
@@ -132,8 +133,9 @@ export interface ConfirmIdentityDeps extends ResolveOptions {
132
133
  * every run and it is the wrong thing to put beside the value the reader is being
133
134
  * asked to check: a person cannot verify a uuid, and thirty-eight characters of one
134
135
  * pushed the email they CAN verify leftward into a longer line. It is not lost —
135
- * `.halfcycle/bundle.json` records the account this install acted as, which is the
136
- * copy a support conversation can actually be pointed at once the terminal is gone.
136
+ * `.halfcycle/bundle.json` records the account that created the project, which is
137
+ * the copy a support conversation can be pointed at once the terminal is gone.
138
+ * The account a later run acted as is not recorded there (E1, #1598).
137
139
  */
138
140
  verbose?: boolean;
139
141
  /**
@@ -228,24 +230,42 @@ export declare function describeAccount(serviceUrl: string, credential: string,
228
230
  * and a human needs the email. The first half of that is true and the conclusion was
229
231
  * wrong: this label's reader is a person deciding whether a project should belong to
230
232
  * this account, they cannot verify a uuid, and a uuid printed beside the one value they
231
- * CAN verify buries it. The id has a better home already — `writeBundlePin` records it
232
- * in `.halfcycle/bundle.json`, which outlives the scrollback — and `--verbose` puts it
233
- * back on screen for the run that wants it.
233
+ * CAN verify buries it. The creating account's id has a better home already —
234
+ * `writeBundlePin` records it in `.halfcycle/bundle.json`, which outlives the
235
+ * scrollback — and `--verbose` puts this run's id back on screen for the run that
236
+ * wants it.
234
237
  *
235
- * WITH NO EMAIL the id is all there is, and it is printed: an unnamed account named by
236
- * its row is better than an unnamed account.
238
+ * WITH NO EMAIL BUT AN ANSWERED ACCOUNT the id is all there is, and it is printed —
239
+ * ONLY when `identity` itself is defined: an unnamed account named by its row is better
240
+ * than an unnamed account, but that is a claim about what `GET /account` just said, not
241
+ * about a credential this run merely holds. `(no email on record for it)` is the
242
+ * accepted exception to the "no internal words" scan precisely because it is scoped to
243
+ * this one case.
237
244
  *
238
- * WHEN NOTHING IS KNOWN IT SAYS SO, and does not invent. `'the account this machine
239
- * signed in as'` is a true sentence about an unnamed account; `'unknown'` printed where
240
- * a name goes reads as a name.
245
+ * **`identity === undefined` MEANS THE SAME THING EVERYWHERE THIS FUNCTION IS CALLED,
246
+ * AND IT MEANS "SAY NOTHING THAT CLAIMS AN ANSWER" (onboarding fix wave 2, F-01, the
247
+ * review's ruling on RW-02/RW-03 F-2, 2026-10-18).** `storedAccountId` is a credential
248
+ * this machine holds, not a fact the service stated — printing it, or printing
249
+ * "(no email on record for it)" beside it, asserts an answer this run never got.
250
+ * EVERY CALLER funnels through this one branch rather than deciding per-caller: `bin.ts`'s
251
+ * final report never reaches it (guarded by `identity !== undefined` before the call), and
252
+ * `confirm-identity.ts`'s own preamble (`accountLine`, gated the same way) and its stop
253
+ * remedy both go through it. The first cut of this fix gated only the preamble and left the
254
+ * stop remedy printing `account <id> (no email on record for it)` offline — the review's
255
+ * finding that fixing one caller and not the function itself reproduces the defect at the
256
+ * next call site.
241
257
  *
242
- * `confirmed` (onboarding fix wave T-16, #1567) is false only for the re-use arm's
243
- * offline report: `askAccount` was asked and got no response at all, so "no email on
244
- * record for it" would be a claim this run never actually confirmed, printed beside a
245
- * raw account id with nothing true to say about why there is no email. `false` takes
246
- * neither branch below and says plainly that this run could not confirm who signed in.
258
+ * **RETURNS `undefined` FOR THAT CASE RATHER THAN A SPLICED-IN NOUN PHRASE (round 2 of the
259
+ * same review, 2026-10-18).** A first attempt here returned a generic phrase ("the account
260
+ * this machine's saved sign-in belongs to" / "this credential's account") for every caller
261
+ * to interpolate — which reads naturally nowhere it is actually interpolated ("This
262
+ * machine's saved sign-in is for the account this machine's saved sign-in belongs to" / "is
263
+ * the credential for this credential's account"). Pushing the UNKNOWN case out to
264
+ * `undefined` keeps the one rule here (no id, no email claim, when `identity` is absent)
265
+ * while leaving each caller free to write its own grammatically whole sentence for that
266
+ * case — `armScript` below is the one caller that needs to, and does.
247
267
  */
248
- export declare function accountLabel(identity: AccountIdentity | undefined, storedAccountId: string | undefined, verbose?: boolean, confirmed?: boolean): string;
268
+ export declare function accountLabel(identity: AccountIdentity | undefined, storedAccountId: string | undefined, verbose?: boolean): string | undefined;
249
269
  /**
250
270
  * Read one line from the terminal. Kept out of the decision so the decision is testable.
251
271
  * Also the Git for Windows offer's prompt (`pre-create-checks.ts`), so the terminal is
@@ -311,7 +331,7 @@ export declare function identitySwitchRemedy(home: string | undefined, interacti
311
331
  *
312
332
  * Returns the credential to use, which is a DIFFERENT one from the one passed in when
313
333
  * the person switched account, and which carries the ACCOUNT ID this run acts as so the
314
- * caller can record it on disk (`writeBundlePin`). Throws `SignInRefused` when they
334
+ * caller can record it on disk when it creates the project (`writeBundlePin`). Throws `SignInRefused` when they
315
335
  * stop, which the CLI already prints as a refusal rather than as a crash: nothing has
316
336
  * been created and nothing has been written at the point this is called, and that is
317
337
  * what makes stopping a real option rather than an apology.
@@ -1 +1 @@
1
- {"version":3,"file":"confirm-identity.d.ts","sourceRoot":"","sources":["../src/confirm-identity.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"confirm-identity.d.ts","sourceRoot":"","sources":["../src/confirm-identity.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"device-signin.d.ts","sourceRoot":"","sources":["../src/device-signin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"device-signin.d.ts","sourceRoot":"","sources":["../src/device-signin.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * INV-001: imports only from @halfcycle/core, @halfcycle/events, or Node builtins.
10
10
  */
11
- export { install, checkDrift, readBundlePin, ZONE_B_DIR } from './install.js';
11
+ export { install, checkDrift, readBundlePin, pinCreatedByAccountId, ZONE_B_DIR } from './install.js';
12
12
  export type { InstallOptions, InstallResult, BundlePin, EngagementType, DeviceCredential } from './install.js';
13
13
  export { mintOrReadIdentity, readRootCommit, readRemote } from './identity.js';
14
14
  export type { ProjectIdentity } from './identity.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC9E,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC/G,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC/E,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACnF,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAyBhE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnG,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAI1F,cAAc,kBAAkB,CAAC;AAKjC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACrG,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC/G,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC/E,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACnF,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAyBhE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnG,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAI1F,cAAc,kBAAkB,CAAC;AAKjC,cAAc,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -1356,12 +1356,20 @@ function createProjectIdentityFile(targetRepoRoot) {
1356
1356
  }
1357
1357
 
1358
1358
  // dist/install-manifest.js
1359
+ import { Buffer } from "node:buffer";
1359
1360
  import { createHash } from "node:crypto";
1360
1361
  var INSTALL_MANIFEST_REL = ".halfcycle/install-manifest.json";
1361
1362
  var INSTALL_MANIFEST_FORMAT = "halfcycle-install-manifest/v1";
1362
1363
  function sha256Hex(bytes) {
1363
1364
  return createHash("sha256").update(bytes).digest("hex");
1364
1365
  }
1366
+ function lineKey(line) {
1367
+ return line.endsWith("\r") ? line.slice(0, -1) : line;
1368
+ }
1369
+ function fileSha256(bytes) {
1370
+ const text = typeof bytes === "string" ? bytes : Buffer.from(bytes).toString("utf-8");
1371
+ return sha256Hex(text.split("\n").map(lineKey).join("\n"));
1372
+ }
1365
1373
  function canonicalJson(value) {
1366
1374
  if (Array.isArray(value))
1367
1375
  return value.map(canonicalJson);
@@ -1544,7 +1552,7 @@ function startManifest(previous, isMember, isMemberDir) {
1544
1552
  };
1545
1553
  }
1546
1554
  function recordInstalledFile(draft, path, bytes) {
1547
- draft.files.set(path, { path, sha256: sha256Hex(bytes) });
1555
+ draft.files.set(path, { path, sha256: fileSha256(bytes) });
1548
1556
  }
1549
1557
  function recordRemovedFile(draft, path) {
1550
1558
  draft.files.delete(path);
@@ -1650,8 +1658,20 @@ function projectPageUrl(engagementId) {
1650
1658
  }
1651
1659
 
1652
1660
  // dist/merge-settings.js
1661
+ function isHalfcycleCommand(hook, halfcycleCommands) {
1662
+ if (typeof hook !== "object" || hook === null)
1663
+ return false;
1664
+ const command = hook.command;
1665
+ return typeof command === "string" && halfcycleCommands.has(command);
1666
+ }
1653
1667
  function isHalfcycleEntry(entry, halfcycleCommands) {
1654
- return entry.hooks.some((h) => halfcycleCommands.has(h.command));
1668
+ return entry.hooks.some((h) => isHalfcycleCommand(h, halfcycleCommands));
1669
+ }
1670
+ function withoutHalfcycleCommands(entry, halfcycleCommands) {
1671
+ if (!isHalfcycleEntry(entry, halfcycleCommands))
1672
+ return entry;
1673
+ const remaining = entry.hooks.filter((h) => !isHalfcycleCommand(h, halfcycleCommands));
1674
+ return remaining.length > 0 ? { ...entry, hooks: remaining } : null;
1655
1675
  }
1656
1676
  function mergeSettings(existing, generated) {
1657
1677
  const merged = { ...existing };
@@ -1670,7 +1690,7 @@ function mergeSettings(existing, generated) {
1670
1690
  const mergedHooks = {};
1671
1691
  const events = /* @__PURE__ */ new Set([...Object.keys(existingHooks), ...Object.keys(generatedHooks)]);
1672
1692
  for (const event of events) {
1673
- const developerEntries = (existingHooks[event] ?? []).filter((entry) => !isHalfcycleEntry(entry, halfcycleCommands));
1693
+ const developerEntries = (existingHooks[event] ?? []).flatMap((entry) => withoutHalfcycleCommands(entry, halfcycleCommands) ?? []);
1674
1694
  const halfcycleEntries = generatedHooks[event] ?? [];
1675
1695
  mergedHooks[event] = [...developerEntries, ...halfcycleEntries];
1676
1696
  }
@@ -1846,6 +1866,7 @@ function closedWriteSet() {
1846
1866
  ownedDirs,
1847
1867
  hookCommands: new Set(HALFCYCLE_HOOK_COMMANDS),
1848
1868
  denyRuleSha256: new Set([...DENY_PATTERNS, ...RETIRED_DENY_PATTERNS].map((rule) => sha256Hex(rule))),
1869
+ settingsSchema: SETTINGS_SCHEMA,
1849
1870
  gitignoreLines: /* @__PURE__ */ new Set([GITIGNORE_HEADER, ...REQUIRED_GITIGNORE_ENTRIES, LEGACY_ENV_LOCAL_REL]),
1850
1871
  gitattributesLines: /* @__PURE__ */ new Set([
1851
1872
  GITATTRIBUTES_HEADER,
@@ -1858,17 +1879,17 @@ function sha256OfWhatThisVersionWrites(commands) {
1858
1879
  const out = /* @__PURE__ */ new Map();
1859
1880
  const fromFile = (rel, src) => {
1860
1881
  if (existsSync3(src))
1861
- out.set(rel, sha256Hex(readFileSync4(src)));
1882
+ out.set(rel, fileSha256(readFileSync4(src)));
1862
1883
  };
1863
1884
  for (const cmd of commands)
1864
1885
  fromFile(commandStubRel(cmd.name), join5(BUNDLE_ROOT, ".claude-plugin", cmd.path));
1865
1886
  for (const [rel, content] of hookScriptsThisVersionWrites())
1866
- out.set(rel, sha256Hex(content));
1887
+ out.set(rel, fileSha256(content));
1867
1888
  fromFile(CAPTURED_INDEX_REL, capturedIndexScaffold());
1868
1889
  return out;
1869
1890
  }
1870
1891
  function bytesAreOurs(path, bytes, recorded, writeSet) {
1871
- const hash = sha256Hex(bytes);
1892
+ const hash = fileSha256(bytes);
1872
1893
  const recordedHash = recorded !== void 0 && "sha256" in recorded ? recorded.sha256 : void 0;
1873
1894
  return hash === recordedHash || hash === writeSet.generatedSha256.get(path);
1874
1895
  }
@@ -2034,6 +2055,8 @@ function copyManifestCommands(manifest, targetRepo, report, draft, writeSet) {
2034
2055
  record(report, outcome, rel);
2035
2056
  if (replacedExisting)
2036
2057
  report.replacedPaths.push(rel);
2058
+ if (outcome === "collided" && recorded !== void 0)
2059
+ report.editedCollidedPaths.push(rel);
2037
2060
  noteFile(draft, targetRepo, rel, outcome);
2038
2061
  }
2039
2062
  }
@@ -2054,16 +2077,17 @@ function writeCollisionSafe(targetAbsPath, targetRepoRoot, content, isOurs) {
2054
2077
  throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
2055
2078
  }
2056
2079
  let replacedExisting = false;
2057
- if (existsSync3(targetAbsPath)) {
2058
- const current = readFileSync4(targetAbsPath);
2059
- if (current.toString("utf-8") === content)
2080
+ const current = existsSync3(targetAbsPath) ? readFileSync4(targetAbsPath) : null;
2081
+ const text = inLineEndingOf(current?.toString("utf-8") ?? null, content);
2082
+ if (current !== null) {
2083
+ if (current.toString("utf-8") === text)
2060
2084
  return { outcome: "skipped", replacedExisting: false };
2061
2085
  if (!isOurs(current))
2062
2086
  return { outcome: "collided", replacedExisting: false };
2063
2087
  replacedExisting = true;
2064
2088
  }
2065
2089
  mkdirSync4(dirname3(targetAbsPath), { recursive: true });
2066
- writeFileSync4(targetAbsPath, content, "utf-8");
2090
+ writeFileSync4(targetAbsPath, text, "utf-8");
2067
2091
  return { outcome: "written", replacedExisting };
2068
2092
  }
2069
2093
  var INSTALLER_OWNED_DIR = ".halfcycle/";
@@ -2075,11 +2099,12 @@ function writeOwned(targetAbsPath, targetRepoRoot, content) {
2075
2099
  if (!rel.startsWith(INSTALLER_OWNED_DIR)) {
2076
2100
  throw new Error(`[halfcycle] Path-ownership violation: attempted to claim "${rel}" by path. Only ${INSTALLER_OWNED_DIR} is owned outright by this installer; everywhere else a differing file may be a developer's, so ownership must be proved from the file's own contents (writeOwnedGenerated) rather than assumed from where it sits.`);
2077
2101
  }
2078
- if (existsSync3(targetAbsPath) && readFileSync4(targetAbsPath, "utf-8") === content) {
2102
+ const current = readFileOrNull(targetAbsPath);
2103
+ const text = inLineEndingOf(current, content);
2104
+ if (current === text)
2079
2105
  return "skipped";
2080
- }
2081
2106
  mkdirSync4(dirname3(targetAbsPath), { recursive: true });
2082
- writeFileSync4(targetAbsPath, content, "utf-8");
2107
+ writeFileSync4(targetAbsPath, text, "utf-8");
2083
2108
  return "written";
2084
2109
  }
2085
2110
  var GENERATED_HEADER_LINE_INDEX = 1;
@@ -2157,6 +2182,7 @@ function settingsParseRefusal(targetRepo, err) {
2157
2182
  }
2158
2183
  return `${file} could not be read as JSON${where} \u2014 fix it and run "npx halfcycle" again.`;
2159
2184
  }
2185
+ var SETTINGS_SCHEMA = "https://json.schemastore.org/claude-code-settings.json";
2160
2186
  function generateSettingsJson() {
2161
2187
  const hooks = {};
2162
2188
  for (const { event, matcher, timeout, command } of HALFCYCLE_HOOK_WIRING) {
@@ -2166,7 +2192,7 @@ function generateSettingsJson() {
2166
2192
  });
2167
2193
  }
2168
2194
  const settings = {
2169
- $schema: "https://json.schemastore.org/claude-code-settings.json",
2195
+ $schema: SETTINGS_SCHEMA,
2170
2196
  permissions: {
2171
2197
  // deny ONLY. See DENY_PATTERNS — an allow list evaporates under bypass mode
2172
2198
  // and is the machine owner's grant to make, not ours.
@@ -2466,19 +2492,27 @@ function adoptBlockUnderHeader(before, header, claimable) {
2466
2492
  let underHeader = false;
2467
2493
  const lines = before.split("\n");
2468
2494
  for (const [i, line] of lines.entries()) {
2469
- if (line === header) {
2470
- if (i > 0 && lines[i - 1] === "")
2471
- claimed.push("");
2495
+ const key = lineKey(line);
2496
+ if (key === header) {
2497
+ if (i > 0 && lineKey(lines[i - 1]) === "")
2498
+ claimed.push(lines[i - 1]);
2472
2499
  claimed.push(line);
2473
2500
  underHeader = true;
2474
- } else if (line.trim() === "") {
2501
+ } else if (key.trim() === "") {
2475
2502
  underHeader = false;
2476
- } else if (underHeader && claimable.has(line)) {
2503
+ } else if (underHeader && claimable.has(key)) {
2477
2504
  claimed.push(line);
2478
2505
  }
2479
2506
  }
2480
2507
  return claimed.length > 0 ? claimed.join("\n") + "\n" : "";
2481
2508
  }
2509
+ function lineEndingOf(text) {
2510
+ const at = text.indexOf("\n");
2511
+ return at > 0 && text[at - 1] === "\r" ? "\r\n" : "\n";
2512
+ }
2513
+ function inLineEndingOf(existing, generated) {
2514
+ return existing === null ? generated : generated.split("\n").join(lineEndingOf(existing));
2515
+ }
2482
2516
  function gitignoreCovers(content) {
2483
2517
  const lines = new Set(content.split("\n").map((l) => l.trim()));
2484
2518
  return REQUIRED_GITIGNORE_ENTRIES.every((entry) => lines.has(entry));
@@ -2497,9 +2531,8 @@ function reconcileGitignore(targetRepo) {
2497
2531
  return "skipped";
2498
2532
  const existingLines = new Set(existing.split("\n").map((l) => l.trim()));
2499
2533
  const toAdd = REQUIRED_GITIGNORE_ENTRIES.filter((entry) => !existingLines.has(entry));
2500
- const suffix = (existing.endsWith("\n") ? "" : "\n") + `
2501
- ${header}
2502
- ` + toAdd.join("\n") + "\n";
2534
+ const eol = lineEndingOf(existing);
2535
+ const suffix = (existing.endsWith("\n") ? "" : eol) + eol + [header, ...toAdd].join(eol) + eol;
2503
2536
  writeFileSync4(gitignorePath, existing + suffix, "utf-8");
2504
2537
  return "written";
2505
2538
  } catch {
@@ -2534,8 +2567,9 @@ function reconcileGitattributes(targetRepo) {
2534
2567
  const toAdd = rules.filter((rule) => !present.has(rule));
2535
2568
  if (toAdd.length === 0)
2536
2569
  return "skipped";
2537
- const separator = existing === "" ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
2538
- const suffix = separator + [GITATTRIBUTES_HEADER, ...toAdd].join("\n") + "\n";
2570
+ const eol = lineEndingOf(existing);
2571
+ const separator = existing === "" ? "" : existing.endsWith("\n") ? eol : eol + eol;
2572
+ const suffix = separator + [GITATTRIBUTES_HEADER, ...toAdd].join(eol) + eol;
2539
2573
  writeAllowlisted(path, targetRepo, existing + suffix, routed);
2540
2574
  return "written";
2541
2575
  } catch {
@@ -2555,9 +2589,9 @@ function previousPinForEngagement(targetRepoRoot, engagementId) {
2555
2589
  function nonEmpty(value) {
2556
2590
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
2557
2591
  }
2558
- function writeBundlePin(targetRepoRoot, version, engagementId, engagementType, accountId, methodVersion, writtenPaths) {
2592
+ function writeBundlePin(targetRepoRoot, version, engagementId, engagementType, createdByAccountId, methodVersion, writtenPaths) {
2559
2593
  const previous = previousPinForEngagement(targetRepoRoot, engagementId);
2560
- const carried = nonEmpty(accountId) ?? nonEmpty(previous?.accountId);
2594
+ const carried = pinCreatedByAccountId(previous) ?? nonEmpty(createdByAccountId);
2561
2595
  const carriedMethodVersion = nonEmpty(methodVersion) ?? nonEmpty(previous?.methodVersion);
2562
2596
  const installedAt = nonEmpty(previous?.installedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
2563
2597
  const projectPage = projectPageUrl(engagementId);
@@ -2566,12 +2600,13 @@ function writeBundlePin(targetRepoRoot, version, engagementId, engagementType, a
2566
2600
  engagementId,
2567
2601
  engagementType,
2568
2602
  installedAt,
2569
- ...carried !== void 0 ? { accountId: carried } : {},
2603
+ ...carried !== void 0 ? { createdByAccountId: carried } : {},
2570
2604
  ...carriedMethodVersion !== void 0 ? { methodVersion: carriedMethodVersion } : {},
2571
2605
  ...projectPage !== void 0 ? { projectPage } : {}
2572
2606
  };
2573
2607
  const pinPath = join5(targetRepoRoot, BUNDLE_PIN_REL);
2574
- writeAllowlisted(pinPath, targetRepoRoot, JSON.stringify(pin, null, 2) + "\n", writtenPaths);
2608
+ const pinText = inLineEndingOf(readFileOrNull(pinPath), JSON.stringify(pin, null, 2) + "\n");
2609
+ writeAllowlisted(pinPath, targetRepoRoot, pinText, writtenPaths);
2575
2610
  }
2576
2611
  function writeCrewRoster(targetRepoRoot, report) {
2577
2612
  const doc = {
@@ -2583,6 +2618,9 @@ function writeCrewRoster(targetRepoRoot, report) {
2583
2618
  const crewPath = join5(targetRepoRoot, CREW_ROSTER_REL);
2584
2619
  return recordOwned(report, crewPath, targetRepoRoot, rendered, CREW_ROSTER_REL);
2585
2620
  }
2621
+ function pinCreatedByAccountId(pin) {
2622
+ return nonEmpty(pin?.createdByAccountId) ?? nonEmpty(pin?.accountId);
2623
+ }
2586
2624
  function readBundlePin(targetRepoRoot) {
2587
2625
  const pinPath = join5(targetRepoRoot, BUNDLE_PIN_REL);
2588
2626
  if (!existsSync3(pinPath))
@@ -2683,7 +2721,7 @@ function migrateLegacyEnvLocal(targetRepo, stored) {
2683
2721
  }
2684
2722
  }
2685
2723
  async function install(options) {
2686
- const { targetRepo, engagementId, engagementType, credential, home, accountId } = options;
2724
+ const { targetRepo, engagementId, engagementType, credential, home, createdByAccountId } = options;
2687
2725
  assertEngagementId(engagementId);
2688
2726
  if (!existsSync3(targetRepo)) {
2689
2727
  throw new Error(`[halfcycle] Target repo does not exist: ${targetRepo}`);
@@ -2694,6 +2732,7 @@ async function install(options) {
2694
2732
  skippedPaths: [],
2695
2733
  mergedPaths: [],
2696
2734
  collidedPaths: [],
2735
+ editedCollidedPaths: [],
2697
2736
  replacedPaths: []
2698
2737
  };
2699
2738
  const writeSet = closedWriteSet();
@@ -2715,7 +2754,7 @@ async function install(options) {
2715
2754
  }
2716
2755
  recordSettingsMerge(draft, existingSettings, generatedSettings, settingsPreexisted);
2717
2756
  const mergedSettings = mergeSettings(existingSettings, generatedSettings);
2718
- const mergedSettingsText = JSON.stringify(mergedSettings, null, 2) + "\n";
2757
+ const mergedSettingsText = inLineEndingOf(readFileOrNull(settingsPath), JSON.stringify(mergedSettings, null, 2) + "\n");
2719
2758
  mkdirSync4(dirname3(settingsPath), { recursive: true });
2720
2759
  writeFileSync4(settingsPath, mergedSettingsText, "utf-8");
2721
2760
  (settingsPreexisted ? report.mergedPaths : report.writtenPaths).push(".claude/settings.json");
@@ -2743,7 +2782,7 @@ async function install(options) {
2743
2782
  if (credential) {
2744
2783
  const mcpPath = join5(targetRepo, MCP_REGISTRATION_REL);
2745
2784
  const existingMcp = existsSync3(mcpPath) ? readFileSync4(mcpPath, "utf-8") : null;
2746
- const mcpContent = generateMcpRegistration(existingMcp, credential.mcpUrl);
2785
+ const mcpContent = inLineEndingOf(existingMcp, generateMcpRegistration(existingMcp, credential.mcpUrl));
2747
2786
  recordMcpMerge(draft, existingMcp, mcpContent);
2748
2787
  if (existingMcp === null) {
2749
2788
  writeAllowlisted(mcpPath, targetRepo, mcpContent, report.writtenPaths);
@@ -2821,7 +2860,7 @@ async function install(options) {
2821
2860
  case "absent":
2822
2861
  break;
2823
2862
  }
2824
- writeBundlePin(targetRepo, manifest.version, engagementId, engagementType, accountId, credential?.methodVersion, report.writtenPaths);
2863
+ writeBundlePin(targetRepo, manifest.version, engagementId, engagementType, createdByAccountId, credential?.methodVersion, report.writtenPaths);
2825
2864
  noteFile(draft, targetRepo, BUNDLE_PIN_REL, "written");
2826
2865
  noteFile(draft, targetRepo, CREW_ROSTER_REL, writeCrewRoster(targetRepo, report));
2827
2866
  const scanResult = runBootstrapScan(targetRepo);
@@ -2841,6 +2880,7 @@ async function install(options) {
2841
2880
  skippedPaths: report.skippedPaths,
2842
2881
  mergedPaths: report.mergedPaths,
2843
2882
  collidedPaths: report.collidedPaths,
2883
+ editedCollidedPaths: report.editedCollidedPaths,
2844
2884
  replacedPaths: report.replacedPaths,
2845
2885
  bootstrapScanRan: scanResult.ran,
2846
2886
  legacyCredentialKept: migration.outcome === "kept" ? { keys: migration.unbacked, missing: migration.missingForAdoption } : null,
@@ -4090,6 +4130,7 @@ export {
4090
4130
  mergeWithRecords,
4091
4131
  mintOrReadIdentity,
4092
4132
  obtainBuildRecord,
4133
+ pinCreatedByAccountId,
4093
4134
  questionsToPut,
4094
4135
  readBundlePin,
4095
4136
  readRemote,