halfcycle 0.3.31 → 0.3.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -8,13 +8,10 @@ import { join as join12 } from "node:path";
8
8
  // dist/install.js
9
9
  import { execFileSync as execFileSync2 } from "node:child_process";
10
10
  import { createHash as createHash2 } from "node:crypto";
11
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
11
+ import { existsSync as existsSync3, lstatSync, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
12
12
  import { dirname as dirname2, join as join5, posix, relative } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
14
 
15
- // ../events/dist/result.js
16
- import { z as z2 } from "zod";
17
-
18
15
  // ../core/dist/guard-record.js
19
16
  import { z } from "zod";
20
17
  var severitySchema = z.enum(["info", "warn", "block"]);
@@ -138,7 +135,76 @@ function parseAccountStoreText(raw) {
138
135
  }
139
136
  }
140
137
 
138
+ // ../core/dist/hook-wiring.js
139
+ var SETTINGS_REL = ".claude/settings.json";
140
+ var VENDORED_BIN_REL = ".halfcycle/bin/bin.bundle.mjs";
141
+ var SESSION_START_MARKER_REL = ".claude/hooks/session-start-marker.sh";
142
+ var GUARD_RUNNER_REL = ".claude/hooks/guard-runner.sh";
143
+ var USER_PROMPT_REMINDER_REL = ".claude/hooks/user-prompt-reminder.sh";
144
+ var SESSION_END_MARKER_REL = ".claude/hooks/session-end-marker.sh";
145
+ var WRITE_CAPABLE_TOOLS = ["Edit", "MultiEdit", "NotebookEdit", "Write"];
146
+ var HALFCYCLE_HOOK_WIRING = [
147
+ {
148
+ event: "SessionStart",
149
+ matcher: null,
150
+ timeout: 5,
151
+ command: `bash ./${SESSION_START_MARKER_REL}`,
152
+ script: SESSION_START_MARKER_REL
153
+ },
154
+ {
155
+ event: "SessionStart",
156
+ matcher: null,
157
+ timeout: 10,
158
+ command: `bash ./${GUARD_RUNNER_REL} refresh-credential`,
159
+ script: GUARD_RUNNER_REL
160
+ },
161
+ {
162
+ event: "UserPromptSubmit",
163
+ matcher: null,
164
+ timeout: 5,
165
+ command: `bash ./${USER_PROMPT_REMINDER_REL}`,
166
+ script: USER_PROMPT_REMINDER_REL
167
+ },
168
+ {
169
+ event: "PostToolUse",
170
+ matcher: WRITE_CAPABLE_TOOLS.join("|"),
171
+ timeout: 60,
172
+ command: `bash ./${GUARD_RUNNER_REL} hook post-tool-use`,
173
+ script: GUARD_RUNNER_REL
174
+ },
175
+ {
176
+ event: "Stop",
177
+ matcher: null,
178
+ timeout: 60,
179
+ command: `bash ./${GUARD_RUNNER_REL} hook stop`,
180
+ script: GUARD_RUNNER_REL
181
+ },
182
+ {
183
+ event: "SubagentStop",
184
+ matcher: null,
185
+ timeout: 60,
186
+ command: `bash ./${GUARD_RUNNER_REL} hook subagent-stop`,
187
+ script: GUARD_RUNNER_REL
188
+ },
189
+ {
190
+ event: "SessionEnd",
191
+ matcher: null,
192
+ timeout: 5,
193
+ command: `bash ./${SESSION_END_MARKER_REL}`,
194
+ script: SESSION_END_MARKER_REL
195
+ }
196
+ ];
197
+ var RETIRED_HOOK_COMMANDS = [];
198
+ var HALFCYCLE_HOOK_COMMANDS = /* @__PURE__ */ new Set([
199
+ ...HALFCYCLE_HOOK_WIRING.map((entry) => entry.command),
200
+ ...RETIRED_HOOK_COMMANDS
201
+ ]);
202
+ var HALFCYCLE_HOOK_WIRING_PATHS = [
203
+ .../* @__PURE__ */ new Set([SETTINGS_REL, ...HALFCYCLE_HOOK_WIRING.map((entry) => entry.script), VENDORED_BIN_REL])
204
+ ];
205
+
141
206
  // ../events/dist/result.js
207
+ import { z as z2 } from "zod";
142
208
  var firedGuardSchema = z2.object({
143
209
  guardId: z2.string(),
144
210
  patternRef: z2.string(),
@@ -1204,14 +1270,36 @@ halfcycle_env_file() {
1204
1270
  // dist/identity.js
1205
1271
  import { execFileSync } from "node:child_process";
1206
1272
  import { existsSync, readFileSync as readFileSync3 } from "node:fs";
1207
- import { join as join3 } from "node:path";
1273
+ import { isAbsolute, join as join3 } from "node:path";
1208
1274
  import { randomUUID } from "node:crypto";
1209
1275
  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.';
1210
- function git(targetRepoRoot, args2) {
1276
+ var REPOSITORY_LOCATING_GIT_VARS = [
1277
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
1278
+ "GIT_COMMON_DIR",
1279
+ "GIT_DIR",
1280
+ "GIT_GRAFT_FILE",
1281
+ "GIT_IMPLICIT_WORK_TREE",
1282
+ "GIT_INDEX_FILE",
1283
+ "GIT_NO_REPLACE_OBJECTS",
1284
+ "GIT_OBJECT_DIRECTORY",
1285
+ "GIT_PREFIX",
1286
+ "GIT_REPLACE_REF_BASE",
1287
+ "GIT_SHALLOW_FILE",
1288
+ "GIT_WORK_TREE"
1289
+ ];
1290
+ function gitEnvForNamedRepo() {
1291
+ const env = { ...process.env };
1292
+ for (const name of REPOSITORY_LOCATING_GIT_VARS)
1293
+ delete env[name];
1294
+ return env;
1295
+ }
1296
+ function git(targetRepoRoot, args2, stdin) {
1211
1297
  try {
1212
1298
  const out = execFileSync("git", ["-C", targetRepoRoot, ...args2], {
1213
1299
  encoding: "utf-8",
1214
- stdio: ["ignore", "pipe", "ignore"]
1300
+ env: gitEnvForNamedRepo(),
1301
+ ...stdin === void 0 ? {} : { input: stdin },
1302
+ stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "ignore"]
1215
1303
  });
1216
1304
  const trimmed = out.trim();
1217
1305
  return trimmed.length > 0 ? trimmed : null;
@@ -1236,6 +1324,38 @@ function readCommitCount(targetRepoRoot) {
1236
1324
  const count = Number.parseInt(out, 10);
1237
1325
  return Number.isFinite(count) ? count : null;
1238
1326
  }
1327
+ function readIgnoredPaths(targetRepoRoot, paths) {
1328
+ if (paths.length === 0)
1329
+ return null;
1330
+ const out = git(targetRepoRoot, ["check-ignore", "-v", "-z", "--stdin"], paths.map((p) => `${p}\0`).join(""));
1331
+ if (out === null)
1332
+ return null;
1333
+ const fields = out.split("\0");
1334
+ if (fields[fields.length - 1] === "")
1335
+ fields.pop();
1336
+ if (fields.length % 4 !== 0)
1337
+ return null;
1338
+ const ignored = [];
1339
+ for (let i = 0; i < fields.length; i += 4) {
1340
+ const [source, line, pattern, path] = fields.slice(i, i + 4);
1341
+ if (pattern.startsWith("!"))
1342
+ continue;
1343
+ const lineNumber = Number.parseInt(line, 10);
1344
+ if (!Number.isFinite(lineNumber))
1345
+ return null;
1346
+ ignored.push({ path, pattern, source, line: lineNumber });
1347
+ }
1348
+ if (ignored.length === 0)
1349
+ return null;
1350
+ if (ignored.every((entry) => isAbsolute(entry.source)))
1351
+ return ignored;
1352
+ if (git(targetRepoRoot, ["rev-parse", "--show-prefix"]) === null)
1353
+ return ignored;
1354
+ const toplevel = git(targetRepoRoot, ["rev-parse", "--show-toplevel"]);
1355
+ if (toplevel === null)
1356
+ return null;
1357
+ return ignored.map((entry) => isAbsolute(entry.source) ? entry : { ...entry, source: join3(toplevel, entry.source) });
1358
+ }
1239
1359
  function mintOrReadIdentity(targetRepoRoot) {
1240
1360
  const path = join3(targetRepoRoot, ".halfcycle", "project.json");
1241
1361
  if (existsSync(path)) {
@@ -1619,6 +1739,13 @@ function runBootstrapScan(targetRepo) {
1619
1739
  }
1620
1740
 
1621
1741
  // dist/install.js
1742
+ function renderNotShared(finding) {
1743
+ const rule = `rule "${finding.pattern}" in ${finding.source}, line ${finding.line}`;
1744
+ if (finding.others === 0) {
1745
+ return `[halfcycle] Not shared: git ignores ${finding.path} (${rule}), which Halfcycle set up here, so it will not reach anyone else who gets a copy of this project.`;
1746
+ }
1747
+ return `[halfcycle] Not shared: git ignores ${finding.path} (${rule}) and ${finding.others} other file${finding.others === 1 ? "" : "s"} Halfcycle set up here, so they will not reach anyone else who gets a copy of this project.`;
1748
+ }
1622
1749
  var ZONE_B_DIR = ".workbench";
1623
1750
  var __filename = fileURLToPath(import.meta.url);
1624
1751
  var __dirname = dirname2(__filename);
@@ -1670,18 +1797,12 @@ var BUNDLE_PIN_REL = ".halfcycle/bundle.json";
1670
1797
  var PROJECT_IDENTITY_REL = ".halfcycle/project.json";
1671
1798
  var CREW_ROSTER_REL = ".halfcycle/crew.json";
1672
1799
  var CAPTURED_INDEX_REL = "test/fixtures/captured/manifest.json";
1673
- var SETTINGS_REL = ".claude/settings.json";
1674
1800
  var GITIGNORE_REL = ".gitignore";
1675
1801
  function commandStubRel(name) {
1676
1802
  return `.claude/commands/${name}.md`;
1677
1803
  }
1678
1804
  var RETIRED_WRITE_SET_FILES = [];
1679
- var RETIRED_HOOK_COMMANDS = [];
1680
1805
  var RETIRED_DENY_PATTERNS = [];
1681
- function installerHookCommands() {
1682
- const settings = JSON.parse(generateSettingsJson());
1683
- return Object.values(settings.hooks ?? {}).flatMap((entries) => entries.flatMap((entry) => entry.hooks.map((h) => h.command)));
1684
- }
1685
1806
  function closedWriteSet() {
1686
1807
  const files = /* @__PURE__ */ new Set([
1687
1808
  ...(readPluginManifest().commands ?? []).map((cmd) => commandStubRel(cmd.name)),
@@ -1707,7 +1828,7 @@ function closedWriteSet() {
1707
1828
  merged,
1708
1829
  dirs,
1709
1830
  ownedDirs,
1710
- hookCommands: /* @__PURE__ */ new Set([...installerHookCommands(), ...RETIRED_HOOK_COMMANDS]),
1831
+ hookCommands: new Set(HALFCYCLE_HOOK_COMMANDS),
1711
1832
  denyRuleSha256: new Set([...DENY_PATTERNS, ...RETIRED_DENY_PATTERNS].map((rule) => sha256Hex(rule))),
1712
1833
  gitignoreLines: /* @__PURE__ */ new Set([GITIGNORE_HEADER, ...REQUIRED_GITIGNORE_ENTRIES, LEGACY_ENV_LOCAL_REL])
1713
1834
  };
@@ -1792,6 +1913,54 @@ function assertManifestInWriteSet(manifest, writeSet) {
1792
1913
  throw new Error(`[bundle install] the install record names paths outside the write set: ${strays.join(", ")}`);
1793
1914
  }
1794
1915
  }
1916
+ function findNotShared(targetRepo, manifest, collidedPaths) {
1917
+ const merged = [
1918
+ SETTINGS_REL,
1919
+ ...manifest.mcp !== void 0 ? [MCP_REGISTRATION_REL] : [],
1920
+ ...manifest.gitignore.created || manifest.gitignore.appended.length > 0 ? [GITIGNORE_REL] : []
1921
+ ];
1922
+ const collided = new Set(collidedPaths);
1923
+ const dirVerdicts = /* @__PURE__ */ new Map();
1924
+ const travelsThroughPlainDirs = (rel) => {
1925
+ for (let dir = posix.dirname(rel); dir !== "."; dir = posix.dirname(dir)) {
1926
+ let plain = dirVerdicts.get(dir);
1927
+ if (plain === void 0) {
1928
+ const abs = join5(targetRepo, dir);
1929
+ plain = !lstatOrNull(abs)?.isSymbolicLink() && lstatOrNull(join5(abs, ".git")) === null;
1930
+ dirVerdicts.set(dir, plain);
1931
+ }
1932
+ if (!plain)
1933
+ return false;
1934
+ }
1935
+ return true;
1936
+ };
1937
+ const paths = [
1938
+ .../* @__PURE__ */ new Set([
1939
+ ...manifest.files.filter((f) => !("perMachine" in f)).map((f) => f.path),
1940
+ ...merged,
1941
+ INSTALL_MANIFEST_REL
1942
+ ])
1943
+ ].filter((rel) => !collided.has(rel)).filter((rel) => lstatOrNull(join5(targetRepo, rel)) !== null).filter(travelsThroughPlainDirs).sort();
1944
+ const ignored = readIgnoredPaths(targetRepo, paths);
1945
+ if (ignored === null)
1946
+ return null;
1947
+ const sorted = [...ignored].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
1948
+ const named = sorted.find((entry) => entry.path === SETTINGS_REL) ?? sorted[0];
1949
+ return {
1950
+ path: named.path,
1951
+ pattern: named.pattern,
1952
+ source: named.source,
1953
+ line: named.line,
1954
+ others: sorted.length - 1
1955
+ };
1956
+ }
1957
+ function lstatOrNull(path) {
1958
+ try {
1959
+ return lstatSync(path);
1960
+ } catch {
1961
+ return null;
1962
+ }
1963
+ }
1795
1964
  function isPlainObject(value) {
1796
1965
  return value !== null && typeof value === "object" && !Array.isArray(value);
1797
1966
  }
@@ -1906,9 +2075,15 @@ var DENY_PATTERNS = [
1906
2075
  "Bash(git push --no-verify:*)",
1907
2076
  "Bash(git commit --no-verify:*)"
1908
2077
  ];
1909
- var WRITE_CAPABLE_TOOLS = ["Edit", "MultiEdit", "NotebookEdit", "Write"];
1910
2078
  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.";
1911
2079
  function generateSettingsJson() {
2080
+ const hooks = {};
2081
+ for (const { event, matcher, timeout, command } of HALFCYCLE_HOOK_WIRING) {
2082
+ (hooks[event] ??= []).push({
2083
+ ...matcher === null ? {} : { matcher },
2084
+ hooks: [{ type: "command", command, timeout }]
2085
+ });
2086
+ }
1912
2087
  const settings = {
1913
2088
  $schema: "https://json.schemastore.org/claude-code-settings.json",
1914
2089
  permissions: {
@@ -1916,88 +2091,10 @@ function generateSettingsJson() {
1916
2091
  // and is the machine owner's grant to make, not ours.
1917
2092
  deny: [...DENY_PATTERNS]
1918
2093
  },
1919
- hooks: {
1920
- SessionStart: [
1921
- {
1922
- hooks: [
1923
- {
1924
- type: "command",
1925
- command: "bash ./.claude/hooks/session-start-marker.sh",
1926
- timeout: 5
1927
- }
1928
- ]
1929
- },
1930
- {
1931
- hooks: [
1932
- {
1933
- type: "command",
1934
- command: "bash ./.claude/hooks/guard-runner.sh refresh-credential",
1935
- timeout: 10
1936
- }
1937
- ]
1938
- }
1939
- ],
1940
- UserPromptSubmit: [
1941
- {
1942
- hooks: [
1943
- {
1944
- type: "command",
1945
- command: "bash ./.claude/hooks/user-prompt-reminder.sh",
1946
- timeout: 5
1947
- }
1948
- ]
1949
- }
1950
- ],
1951
- PostToolUse: [
1952
- {
1953
- matcher: WRITE_CAPABLE_TOOLS.join("|"),
1954
- hooks: [
1955
- {
1956
- type: "command",
1957
- command: "bash ./.claude/hooks/guard-runner.sh hook post-tool-use",
1958
- timeout: 60
1959
- }
1960
- ]
1961
- }
1962
- ],
1963
- Stop: [
1964
- {
1965
- hooks: [
1966
- {
1967
- type: "command",
1968
- command: "bash ./.claude/hooks/guard-runner.sh hook stop",
1969
- timeout: 60
1970
- }
1971
- ]
1972
- }
1973
- ],
1974
- SubagentStop: [
1975
- {
1976
- hooks: [
1977
- {
1978
- type: "command",
1979
- command: "bash ./.claude/hooks/guard-runner.sh hook subagent-stop",
1980
- timeout: 60
1981
- }
1982
- ]
1983
- }
1984
- ],
1985
- SessionEnd: [
1986
- {
1987
- hooks: [
1988
- {
1989
- type: "command",
1990
- command: "bash ./.claude/hooks/session-end-marker.sh",
1991
- timeout: 5
1992
- }
1993
- ]
1994
- }
1995
- ]
1996
- }
2094
+ hooks
1997
2095
  };
1998
2096
  return JSON.stringify(settings, null, 2) + "\n";
1999
2097
  }
2000
- var VENDORED_BIN_REL = ".halfcycle/bin/bin.bundle.mjs";
2001
2098
  var GENERATED_GUARD_RUNNER_HEADER = "# Governance hook wrapper \u2014 generated by";
2002
2099
  function generateGuardRunnerWrapper() {
2003
2100
  return `#!/usr/bin/env bash
@@ -2219,14 +2316,16 @@ var CI_STANZA_KNOWN_HEADER_HASHES = [
2219
2316
  function isTrackedAndClean(targetRepo, relPath) {
2220
2317
  try {
2221
2318
  execFileSync2("git", ["-C", targetRepo, "ls-files", "--error-unmatch", "--", relPath], {
2222
- stdio: "ignore"
2319
+ stdio: "ignore",
2320
+ env: gitEnvForNamedRepo()
2223
2321
  });
2224
2322
  } catch {
2225
2323
  return false;
2226
2324
  }
2227
2325
  try {
2228
2326
  execFileSync2("git", ["-C", targetRepo, "diff", "--quiet", "HEAD", "--", relPath], {
2229
- stdio: "ignore"
2327
+ stdio: "ignore",
2328
+ env: gitEnvForNamedRepo()
2230
2329
  });
2231
2330
  } catch {
2232
2331
  return false;
@@ -2842,6 +2941,7 @@ async function install(options) {
2842
2941
  const installManifest = finishManifest(draft);
2843
2942
  assertManifestInWriteSet(installManifest, writeSet);
2844
2943
  recordOwned(report, join5(targetRepo, INSTALL_MANIFEST_REL), targetRepo, serializeManifest(installManifest), INSTALL_MANIFEST_REL);
2944
+ const notShared = findNotShared(targetRepo, installManifest, report.collidedPaths);
2845
2945
  return {
2846
2946
  version: manifest.version,
2847
2947
  writtenPaths: report.writtenPaths,
@@ -2850,7 +2950,8 @@ async function install(options) {
2850
2950
  collidedPaths: report.collidedPaths,
2851
2951
  replacedPaths: report.replacedPaths,
2852
2952
  bootstrapScanRan: scanResult.ran,
2853
- legacyCredentialKept: migration.outcome === "kept" ? { keys: migration.unbacked, missing: migration.missingForAdoption } : null
2953
+ legacyCredentialKept: migration.outcome === "kept" ? { keys: migration.unbacked, missing: migration.missingForAdoption } : null,
2954
+ notShared
2854
2955
  };
2855
2956
  }
2856
2957
 
@@ -4229,7 +4330,7 @@ function parseCloseRecord(path, parsed) {
4229
4330
 
4230
4331
  // dist/build-record/write.js
4231
4332
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
4232
- import { basename as basename2, join as join7, resolve as resolve2, relative as relative2, isAbsolute } from "node:path";
4333
+ import { basename as basename2, join as join7, resolve as resolve2, relative as relative2, isAbsolute as isAbsolute2 } from "node:path";
4233
4334
 
4234
4335
  // dist/build-record/template.js
4235
4336
  function severityLabel(seed) {
@@ -4283,7 +4384,7 @@ function renderBuildRecordMarkdown(record2) {
4283
4384
  `- **Tools:** ${record2.delivered.tools.join(", ")}`,
4284
4385
  `- **Dogfood:** ${record2.delivered.dogfood}`,
4285
4386
  "",
4286
- guardsList ? `Guards evaluated (results only): ${guardsList}. *(What a guard reported about this phase is recorded; the guard's own definition and its history are not.)*` : "No guards fired during this phase.",
4387
+ guardsList ? `Guards evaluated (results only): ${guardsList}. *(What a guard reported about this phase is recorded; the guard's own definition and its history are not.)*` : "No guard firing was recorded on this machine during this phase.",
4287
4388
  "",
4288
4389
  `## Acceptance (independent walk \u2014 walker ${record2.acceptance.walker})`,
4289
4390
  "",
@@ -4308,7 +4409,7 @@ function serialiseRecordJson(record2) {
4308
4409
  }
4309
4410
  function assertInsideOutDir(outDir, candidate, identity) {
4310
4411
  const rel = relative2(outDir, resolve2(candidate));
4311
- const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute(rel) && rel === basename2(rel);
4412
+ const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute2(rel) && rel === basename2(rel);
4312
4413
  if (!isPlainFilename) {
4313
4414
  throw new InvalidPhaseIdentityError(identity, BUILD_RECORD_DIR);
4314
4415
  }
@@ -4807,7 +4908,7 @@ var PLACEHOLDERS = {
4807
4908
  };
4808
4909
 
4809
4910
  // dist/uninstall.js
4810
- import { lstatSync, readFileSync as readFileSync8, readdirSync, rmSync as rmSync2, rmdirSync, writeFileSync as writeFileSync7 } from "node:fs";
4911
+ import { lstatSync as lstatSync2, readFileSync as readFileSync8, readdirSync, rmSync as rmSync2, rmdirSync, writeFileSync as writeFileSync7 } from "node:fs";
4811
4912
  import { homedir as homedir2 } from "node:os";
4812
4913
  import { dirname as dirname4, join as join10, relative as relative3, sep } from "node:path";
4813
4914
  var REMOVE_CREDENTIAL_FLAG = "--remove-credential";
@@ -4826,7 +4927,7 @@ function parseUninstallArgs(args2) {
4826
4927
  }
4827
4928
  function lexists(path) {
4828
4929
  try {
4829
- lstatSync(path);
4930
+ lstatSync2(path);
4830
4931
  return true;
4831
4932
  } catch {
4832
4933
  return false;
@@ -4837,7 +4938,7 @@ function symlinkOnPath(root, rel) {
4837
4938
  for (const segment of rel.split("/")) {
4838
4939
  current = join10(current, segment);
4839
4940
  try {
4840
- if (lstatSync(current).isSymbolicLink())
4941
+ if (lstatSync2(current).isSymbolicLink())
4841
4942
  return true;
4842
4943
  } catch {
4843
4944
  return false;
@@ -5222,7 +5323,7 @@ function removeRecordedFile(targetRepo, entry, writeSet, lists, name) {
5222
5323
  const abs = join10(targetRepo, path);
5223
5324
  let isFile;
5224
5325
  try {
5225
- isFile = lstatSync(abs).isFile();
5326
+ isFile = lstatSync2(abs).isFile();
5226
5327
  } catch {
5227
5328
  return;
5228
5329
  }
@@ -5255,7 +5356,7 @@ function removeEmptyCreatedDir(targetRepo, dir, writeSet, lists, name) {
5255
5356
  return;
5256
5357
  const abs = join10(targetRepo, dir);
5257
5358
  try {
5258
- if (!lstatSync(abs).isDirectory() || readdirSync(abs).length > 0)
5359
+ if (!lstatSync2(abs).isDirectory() || readdirSync(abs).length > 0)
5259
5360
  return;
5260
5361
  } catch {
5261
5362
  return;
@@ -5318,7 +5419,7 @@ function handleCredential(options, checked) {
5318
5419
  if (!lexists(dir))
5319
5420
  return { kind: "not-held", dir };
5320
5421
  try {
5321
- if (lstatSync(dir).isSymbolicLink())
5422
+ if (lstatSync2(dir).isSymbolicLink())
5322
5423
  return { kind: "failed", dir, reason: "it is a symbolic link" };
5323
5424
  rmSync2(engagementEnvPath(engagementId, home), { force: true });
5324
5425
  rmSync2(notThisAccountMarkerPath(engagementId, home), { force: true });
@@ -5334,7 +5435,7 @@ function filesUnder(root, rel) {
5334
5435
  const walk = (dirRel) => {
5335
5436
  let names;
5336
5437
  try {
5337
- if (!lstatSync(join10(root, dirRel)).isDirectory())
5438
+ if (!lstatSync2(join10(root, dirRel)).isDirectory())
5338
5439
  return;
5339
5440
  names = readdirSync(join10(root, dirRel));
5340
5441
  } catch {
@@ -5343,7 +5444,7 @@ function filesUnder(root, rel) {
5343
5444
  for (const entry of names.sort()) {
5344
5445
  const childRel = `${dirRel}/${entry}`;
5345
5446
  try {
5346
- if (lstatSync(join10(root, childRel)).isDirectory())
5447
+ if (lstatSync2(join10(root, childRel)).isDirectory())
5347
5448
  walk(childRel);
5348
5449
  else
5349
5450
  out.push(childRel);
@@ -5816,6 +5917,9 @@ ${USAGE}`);
5816
5917
  process.stdout.write(`[halfcycle] Found: ${foundLine}
5817
5918
  `);
5818
5919
  process.stdout.write(`[halfcycle] Set up: ${written} file${written === 1 ? "" : "s"} written${merged > 0 ? ` (${merged} merged)` : ""}, ${credential ? "guards armed on every edit" : "not checked \u2014 this copy is not this account\u2019s project"}
5920
+ `);
5921
+ if (result.notShared)
5922
+ process.stdout.write(`${renderNotShared(result.notShared)}
5819
5923
  `);
5820
5924
  if (credential) {
5821
5925
  process.stdout.write(`[halfcycle] Next: open this folder in Claude Code \u2014 accept the workspace-trust prompt, it is expected \u2014 then run /halfcycle-setup and answer what it asks about your project