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.
@@ -65,6 +65,8 @@ export interface ProjectIdentity {
65
65
  */
66
66
  note: string;
67
67
  }
68
+ /** This process's environment without the repository-locating git variables. */
69
+ export declare function gitEnvForNamedRepo(): NodeJS.ProcessEnv;
68
70
  /** Read the repository's root commit hash (the first commit, reachable from HEAD). */
69
71
  export declare function readRootCommit(targetRepoRoot: string): string | null;
70
72
  /** Read the `origin` remote URL. */
@@ -79,6 +81,43 @@ export declare function readRemote(targetRepoRoot: string): string | null;
79
81
  * the banner drops the whole clause rather than printing a zero it did not measure.
80
82
  */
81
83
  export declare function readCommitCount(targetRepoRoot: string): number | null;
84
+ /** One path git ignores, with the rule that ignores it. */
85
+ export interface IgnoredPath {
86
+ /** The path as it was asked about, relative to the directory the install ran in. */
87
+ path: string;
88
+ /** The pattern that matched, as written in its source. */
89
+ pattern: string;
90
+ /**
91
+ * Where the pattern lives, as git names it: relative to the repository root for a
92
+ * file inside it, else absolute. Made absolute when the install ran below the root.
93
+ */
94
+ source: string;
95
+ /** The pattern's line number in `source`. */
96
+ line: number;
97
+ }
98
+ /**
99
+ * Which of `paths` git would leave out of a commit, and the rule that says so, or `null`
100
+ * when git cannot answer (no repository, no `git`) or nothing is ignored. An empty
101
+ * `paths` asks git nothing.
102
+ *
103
+ * ONE `git check-ignore -v -z --stdin`: the paths go NUL-separated on stdin and every
104
+ * answer comes back as four NUL-separated fields — source, line, pattern, path. `-z`
105
+ * because an absolute excludes-file path may contain `:`, which the default output
106
+ * uses as its separator; `--stdin` because `-z` requires it. The command is index
107
+ * aware, so a tracked file is never reported however its rules read.
108
+ *
109
+ * A `!` pattern is a negation: `-v` prints the one that matched with exit 0 although
110
+ * the path is NOT ignored, so it is dropped here rather than reported. Git refuses the
111
+ * whole batch (exit 128) over one path below a symbolic link or inside a submodule;
112
+ * the caller drops those paths before asking, and a refusal here reads as "cannot
113
+ * answer", with no retry.
114
+ *
115
+ * Git reports a source inside the repository relative to the repository ROOT, not to
116
+ * the directory it ran in, so when the install ran below the root such a source is
117
+ * joined to `--show-toplevel` (which resolves symbolic links) and returned absolute.
118
+ * Those two calls are made only when there is something to report.
119
+ */
120
+ export declare function readIgnoredPaths(targetRepoRoot: string, paths: readonly string[]): IgnoredPath[] | null;
82
121
  /**
83
122
  * Return the existing committed identity if `.halfcycle/project.json` is present,
84
123
  * otherwise mint a fresh one. Minting never overwrites the MINTED fields
@@ -1 +1 @@
1
- {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,qBAAqB,QAGF,CAAC;AAEjC,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,gEAAgE;IAChE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;;;OAOG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAgBD,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMpE;AAED,oCAAoC;AACpC,wBAAgB,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKrE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAmBzG"}
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,qBAAqB,QAGF,CAAC;AAEjC,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,gEAAgE;IAChE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;;;OAOG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AA6BD,gFAAgF;AAChF,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,UAAU,CAItD;AAqBD,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMpE;AAED,oCAAoC;AACpC,wBAAgB,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKrE;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,EAAE,GAAG,IAAI,CAyBvG;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAmBzG"}
package/dist/index.js CHANGED
@@ -1,13 +1,10 @@
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 existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
4
+ import { existsSync as existsSync3, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
5
5
  import { dirname as dirname2, join as join5, posix, relative } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
- // ../events/dist/result.js
9
- import { z as z2 } from "zod";
10
-
11
8
  // ../core/dist/guard-record.js
12
9
  import { z } from "zod";
13
10
  var severitySchema = z.enum(["info", "warn", "block"]);
@@ -113,7 +110,76 @@ ${block}`);
113
110
  return result;
114
111
  }
115
112
 
113
+ // ../core/dist/hook-wiring.js
114
+ var SETTINGS_REL = ".claude/settings.json";
115
+ var VENDORED_BIN_REL = ".halfcycle/bin/bin.bundle.mjs";
116
+ var SESSION_START_MARKER_REL = ".claude/hooks/session-start-marker.sh";
117
+ var GUARD_RUNNER_REL = ".claude/hooks/guard-runner.sh";
118
+ var USER_PROMPT_REMINDER_REL = ".claude/hooks/user-prompt-reminder.sh";
119
+ var SESSION_END_MARKER_REL = ".claude/hooks/session-end-marker.sh";
120
+ var WRITE_CAPABLE_TOOLS = ["Edit", "MultiEdit", "NotebookEdit", "Write"];
121
+ var HALFCYCLE_HOOK_WIRING = [
122
+ {
123
+ event: "SessionStart",
124
+ matcher: null,
125
+ timeout: 5,
126
+ command: `bash ./${SESSION_START_MARKER_REL}`,
127
+ script: SESSION_START_MARKER_REL
128
+ },
129
+ {
130
+ event: "SessionStart",
131
+ matcher: null,
132
+ timeout: 10,
133
+ command: `bash ./${GUARD_RUNNER_REL} refresh-credential`,
134
+ script: GUARD_RUNNER_REL
135
+ },
136
+ {
137
+ event: "UserPromptSubmit",
138
+ matcher: null,
139
+ timeout: 5,
140
+ command: `bash ./${USER_PROMPT_REMINDER_REL}`,
141
+ script: USER_PROMPT_REMINDER_REL
142
+ },
143
+ {
144
+ event: "PostToolUse",
145
+ matcher: WRITE_CAPABLE_TOOLS.join("|"),
146
+ timeout: 60,
147
+ command: `bash ./${GUARD_RUNNER_REL} hook post-tool-use`,
148
+ script: GUARD_RUNNER_REL
149
+ },
150
+ {
151
+ event: "Stop",
152
+ matcher: null,
153
+ timeout: 60,
154
+ command: `bash ./${GUARD_RUNNER_REL} hook stop`,
155
+ script: GUARD_RUNNER_REL
156
+ },
157
+ {
158
+ event: "SubagentStop",
159
+ matcher: null,
160
+ timeout: 60,
161
+ command: `bash ./${GUARD_RUNNER_REL} hook subagent-stop`,
162
+ script: GUARD_RUNNER_REL
163
+ },
164
+ {
165
+ event: "SessionEnd",
166
+ matcher: null,
167
+ timeout: 5,
168
+ command: `bash ./${SESSION_END_MARKER_REL}`,
169
+ script: SESSION_END_MARKER_REL
170
+ }
171
+ ];
172
+ var RETIRED_HOOK_COMMANDS = [];
173
+ var HALFCYCLE_HOOK_COMMANDS = /* @__PURE__ */ new Set([
174
+ ...HALFCYCLE_HOOK_WIRING.map((entry) => entry.command),
175
+ ...RETIRED_HOOK_COMMANDS
176
+ ]);
177
+ var HALFCYCLE_HOOK_WIRING_PATHS = [
178
+ .../* @__PURE__ */ new Set([SETTINGS_REL, ...HALFCYCLE_HOOK_WIRING.map((entry) => entry.script), VENDORED_BIN_REL])
179
+ ];
180
+
116
181
  // ../events/dist/result.js
182
+ import { z as z2 } from "zod";
117
183
  var firedGuardSchema = z2.object({
118
184
  guardId: z2.string(),
119
185
  patternRef: z2.string(),
@@ -925,14 +991,36 @@ halfcycle_env_file() {
925
991
  // dist/identity.js
926
992
  import { execFileSync } from "node:child_process";
927
993
  import { existsSync, readFileSync as readFileSync2 } from "node:fs";
928
- import { join as join3 } from "node:path";
994
+ import { isAbsolute, join as join3 } from "node:path";
929
995
  import { randomUUID } from "node:crypto";
930
996
  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.';
931
- function git(targetRepoRoot, args) {
997
+ var REPOSITORY_LOCATING_GIT_VARS = [
998
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
999
+ "GIT_COMMON_DIR",
1000
+ "GIT_DIR",
1001
+ "GIT_GRAFT_FILE",
1002
+ "GIT_IMPLICIT_WORK_TREE",
1003
+ "GIT_INDEX_FILE",
1004
+ "GIT_NO_REPLACE_OBJECTS",
1005
+ "GIT_OBJECT_DIRECTORY",
1006
+ "GIT_PREFIX",
1007
+ "GIT_REPLACE_REF_BASE",
1008
+ "GIT_SHALLOW_FILE",
1009
+ "GIT_WORK_TREE"
1010
+ ];
1011
+ function gitEnvForNamedRepo() {
1012
+ const env = { ...process.env };
1013
+ for (const name of REPOSITORY_LOCATING_GIT_VARS)
1014
+ delete env[name];
1015
+ return env;
1016
+ }
1017
+ function git(targetRepoRoot, args, stdin) {
932
1018
  try {
933
1019
  const out = execFileSync("git", ["-C", targetRepoRoot, ...args], {
934
1020
  encoding: "utf-8",
935
- stdio: ["ignore", "pipe", "ignore"]
1021
+ env: gitEnvForNamedRepo(),
1022
+ ...stdin === void 0 ? {} : { input: stdin },
1023
+ stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "ignore"]
936
1024
  });
937
1025
  const trimmed = out.trim();
938
1026
  return trimmed.length > 0 ? trimmed : null;
@@ -950,6 +1038,38 @@ function readRootCommit(targetRepoRoot) {
950
1038
  function readRemote(targetRepoRoot) {
951
1039
  return git(targetRepoRoot, ["remote", "get-url", "origin"]);
952
1040
  }
1041
+ function readIgnoredPaths(targetRepoRoot, paths) {
1042
+ if (paths.length === 0)
1043
+ return null;
1044
+ const out = git(targetRepoRoot, ["check-ignore", "-v", "-z", "--stdin"], paths.map((p) => `${p}\0`).join(""));
1045
+ if (out === null)
1046
+ return null;
1047
+ const fields = out.split("\0");
1048
+ if (fields[fields.length - 1] === "")
1049
+ fields.pop();
1050
+ if (fields.length % 4 !== 0)
1051
+ return null;
1052
+ const ignored = [];
1053
+ for (let i = 0; i < fields.length; i += 4) {
1054
+ const [source, line, pattern, path] = fields.slice(i, i + 4);
1055
+ if (pattern.startsWith("!"))
1056
+ continue;
1057
+ const lineNumber = Number.parseInt(line, 10);
1058
+ if (!Number.isFinite(lineNumber))
1059
+ return null;
1060
+ ignored.push({ path, pattern, source, line: lineNumber });
1061
+ }
1062
+ if (ignored.length === 0)
1063
+ return null;
1064
+ if (ignored.every((entry) => isAbsolute(entry.source)))
1065
+ return ignored;
1066
+ if (git(targetRepoRoot, ["rev-parse", "--show-prefix"]) === null)
1067
+ return ignored;
1068
+ const toplevel = git(targetRepoRoot, ["rev-parse", "--show-toplevel"]);
1069
+ if (toplevel === null)
1070
+ return null;
1071
+ return ignored.map((entry) => isAbsolute(entry.source) ? entry : { ...entry, source: join3(toplevel, entry.source) });
1072
+ }
953
1073
  function mintOrReadIdentity(targetRepoRoot) {
954
1074
  const path = join3(targetRepoRoot, ".halfcycle", "project.json");
955
1075
  if (existsSync(path)) {
@@ -1399,18 +1519,12 @@ var BUNDLE_PIN_REL = ".halfcycle/bundle.json";
1399
1519
  var PROJECT_IDENTITY_REL = ".halfcycle/project.json";
1400
1520
  var CREW_ROSTER_REL = ".halfcycle/crew.json";
1401
1521
  var CAPTURED_INDEX_REL = "test/fixtures/captured/manifest.json";
1402
- var SETTINGS_REL = ".claude/settings.json";
1403
1522
  var GITIGNORE_REL = ".gitignore";
1404
1523
  function commandStubRel(name) {
1405
1524
  return `.claude/commands/${name}.md`;
1406
1525
  }
1407
1526
  var RETIRED_WRITE_SET_FILES = [];
1408
- var RETIRED_HOOK_COMMANDS = [];
1409
1527
  var RETIRED_DENY_PATTERNS = [];
1410
- function installerHookCommands() {
1411
- const settings = JSON.parse(generateSettingsJson());
1412
- return Object.values(settings.hooks ?? {}).flatMap((entries) => entries.flatMap((entry) => entry.hooks.map((h) => h.command)));
1413
- }
1414
1528
  function closedWriteSet() {
1415
1529
  const files = /* @__PURE__ */ new Set([
1416
1530
  ...(readPluginManifest().commands ?? []).map((cmd) => commandStubRel(cmd.name)),
@@ -1436,7 +1550,7 @@ function closedWriteSet() {
1436
1550
  merged,
1437
1551
  dirs,
1438
1552
  ownedDirs,
1439
- hookCommands: /* @__PURE__ */ new Set([...installerHookCommands(), ...RETIRED_HOOK_COMMANDS]),
1553
+ hookCommands: new Set(HALFCYCLE_HOOK_COMMANDS),
1440
1554
  denyRuleSha256: new Set([...DENY_PATTERNS, ...RETIRED_DENY_PATTERNS].map((rule) => sha256Hex(rule))),
1441
1555
  gitignoreLines: /* @__PURE__ */ new Set([GITIGNORE_HEADER, ...REQUIRED_GITIGNORE_ENTRIES, LEGACY_ENV_LOCAL_REL])
1442
1556
  };
@@ -1521,6 +1635,54 @@ function assertManifestInWriteSet(manifest, writeSet) {
1521
1635
  throw new Error(`[bundle install] the install record names paths outside the write set: ${strays.join(", ")}`);
1522
1636
  }
1523
1637
  }
1638
+ function findNotShared(targetRepo, manifest, collidedPaths) {
1639
+ const merged = [
1640
+ SETTINGS_REL,
1641
+ ...manifest.mcp !== void 0 ? [MCP_REGISTRATION_REL] : [],
1642
+ ...manifest.gitignore.created || manifest.gitignore.appended.length > 0 ? [GITIGNORE_REL] : []
1643
+ ];
1644
+ const collided = new Set(collidedPaths);
1645
+ const dirVerdicts = /* @__PURE__ */ new Map();
1646
+ const travelsThroughPlainDirs = (rel) => {
1647
+ for (let dir = posix.dirname(rel); dir !== "."; dir = posix.dirname(dir)) {
1648
+ let plain = dirVerdicts.get(dir);
1649
+ if (plain === void 0) {
1650
+ const abs = join5(targetRepo, dir);
1651
+ plain = !lstatOrNull(abs)?.isSymbolicLink() && lstatOrNull(join5(abs, ".git")) === null;
1652
+ dirVerdicts.set(dir, plain);
1653
+ }
1654
+ if (!plain)
1655
+ return false;
1656
+ }
1657
+ return true;
1658
+ };
1659
+ const paths = [
1660
+ .../* @__PURE__ */ new Set([
1661
+ ...manifest.files.filter((f) => !("perMachine" in f)).map((f) => f.path),
1662
+ ...merged,
1663
+ INSTALL_MANIFEST_REL
1664
+ ])
1665
+ ].filter((rel) => !collided.has(rel)).filter((rel) => lstatOrNull(join5(targetRepo, rel)) !== null).filter(travelsThroughPlainDirs).sort();
1666
+ const ignored = readIgnoredPaths(targetRepo, paths);
1667
+ if (ignored === null)
1668
+ return null;
1669
+ const sorted = [...ignored].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
1670
+ const named = sorted.find((entry) => entry.path === SETTINGS_REL) ?? sorted[0];
1671
+ return {
1672
+ path: named.path,
1673
+ pattern: named.pattern,
1674
+ source: named.source,
1675
+ line: named.line,
1676
+ others: sorted.length - 1
1677
+ };
1678
+ }
1679
+ function lstatOrNull(path) {
1680
+ try {
1681
+ return lstatSync(path);
1682
+ } catch {
1683
+ return null;
1684
+ }
1685
+ }
1524
1686
  function isPlainObject(value) {
1525
1687
  return value !== null && typeof value === "object" && !Array.isArray(value);
1526
1688
  }
@@ -1635,9 +1797,15 @@ var DENY_PATTERNS = [
1635
1797
  "Bash(git push --no-verify:*)",
1636
1798
  "Bash(git commit --no-verify:*)"
1637
1799
  ];
1638
- var WRITE_CAPABLE_TOOLS = ["Edit", "MultiEdit", "NotebookEdit", "Write"];
1639
1800
  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.";
1640
1801
  function generateSettingsJson() {
1802
+ const hooks = {};
1803
+ for (const { event, matcher, timeout, command } of HALFCYCLE_HOOK_WIRING) {
1804
+ (hooks[event] ??= []).push({
1805
+ ...matcher === null ? {} : { matcher },
1806
+ hooks: [{ type: "command", command, timeout }]
1807
+ });
1808
+ }
1641
1809
  const settings = {
1642
1810
  $schema: "https://json.schemastore.org/claude-code-settings.json",
1643
1811
  permissions: {
@@ -1645,88 +1813,10 @@ function generateSettingsJson() {
1645
1813
  // and is the machine owner's grant to make, not ours.
1646
1814
  deny: [...DENY_PATTERNS]
1647
1815
  },
1648
- hooks: {
1649
- SessionStart: [
1650
- {
1651
- hooks: [
1652
- {
1653
- type: "command",
1654
- command: "bash ./.claude/hooks/session-start-marker.sh",
1655
- timeout: 5
1656
- }
1657
- ]
1658
- },
1659
- {
1660
- hooks: [
1661
- {
1662
- type: "command",
1663
- command: "bash ./.claude/hooks/guard-runner.sh refresh-credential",
1664
- timeout: 10
1665
- }
1666
- ]
1667
- }
1668
- ],
1669
- UserPromptSubmit: [
1670
- {
1671
- hooks: [
1672
- {
1673
- type: "command",
1674
- command: "bash ./.claude/hooks/user-prompt-reminder.sh",
1675
- timeout: 5
1676
- }
1677
- ]
1678
- }
1679
- ],
1680
- PostToolUse: [
1681
- {
1682
- matcher: WRITE_CAPABLE_TOOLS.join("|"),
1683
- hooks: [
1684
- {
1685
- type: "command",
1686
- command: "bash ./.claude/hooks/guard-runner.sh hook post-tool-use",
1687
- timeout: 60
1688
- }
1689
- ]
1690
- }
1691
- ],
1692
- Stop: [
1693
- {
1694
- hooks: [
1695
- {
1696
- type: "command",
1697
- command: "bash ./.claude/hooks/guard-runner.sh hook stop",
1698
- timeout: 60
1699
- }
1700
- ]
1701
- }
1702
- ],
1703
- SubagentStop: [
1704
- {
1705
- hooks: [
1706
- {
1707
- type: "command",
1708
- command: "bash ./.claude/hooks/guard-runner.sh hook subagent-stop",
1709
- timeout: 60
1710
- }
1711
- ]
1712
- }
1713
- ],
1714
- SessionEnd: [
1715
- {
1716
- hooks: [
1717
- {
1718
- type: "command",
1719
- command: "bash ./.claude/hooks/session-end-marker.sh",
1720
- timeout: 5
1721
- }
1722
- ]
1723
- }
1724
- ]
1725
- }
1816
+ hooks
1726
1817
  };
1727
1818
  return JSON.stringify(settings, null, 2) + "\n";
1728
1819
  }
1729
- var VENDORED_BIN_REL = ".halfcycle/bin/bin.bundle.mjs";
1730
1820
  var GENERATED_GUARD_RUNNER_HEADER = "# Governance hook wrapper \u2014 generated by";
1731
1821
  function generateGuardRunnerWrapper() {
1732
1822
  return `#!/usr/bin/env bash
@@ -1948,14 +2038,16 @@ var CI_STANZA_KNOWN_HEADER_HASHES = [
1948
2038
  function isTrackedAndClean(targetRepo, relPath) {
1949
2039
  try {
1950
2040
  execFileSync2("git", ["-C", targetRepo, "ls-files", "--error-unmatch", "--", relPath], {
1951
- stdio: "ignore"
2041
+ stdio: "ignore",
2042
+ env: gitEnvForNamedRepo()
1952
2043
  });
1953
2044
  } catch {
1954
2045
  return false;
1955
2046
  }
1956
2047
  try {
1957
2048
  execFileSync2("git", ["-C", targetRepo, "diff", "--quiet", "HEAD", "--", relPath], {
1958
- stdio: "ignore"
2049
+ stdio: "ignore",
2050
+ env: gitEnvForNamedRepo()
1959
2051
  });
1960
2052
  } catch {
1961
2053
  return false;
@@ -2533,6 +2625,7 @@ async function install(options) {
2533
2625
  const installManifest = finishManifest(draft);
2534
2626
  assertManifestInWriteSet(installManifest, writeSet);
2535
2627
  recordOwned(report, join5(targetRepo, INSTALL_MANIFEST_REL), targetRepo, serializeManifest(installManifest), INSTALL_MANIFEST_REL);
2628
+ const notShared = findNotShared(targetRepo, installManifest, report.collidedPaths);
2536
2629
  return {
2537
2630
  version: manifest.version,
2538
2631
  writtenPaths: report.writtenPaths,
@@ -2541,7 +2634,8 @@ async function install(options) {
2541
2634
  collidedPaths: report.collidedPaths,
2542
2635
  replacedPaths: report.replacedPaths,
2543
2636
  bootstrapScanRan: scanResult.ran,
2544
- legacyCredentialKept: migration.outcome === "kept" ? { keys: migration.unbacked, missing: migration.missingForAdoption } : null
2637
+ legacyCredentialKept: migration.outcome === "kept" ? { keys: migration.unbacked, missing: migration.missingForAdoption } : null,
2638
+ notShared
2545
2639
  };
2546
2640
  }
2547
2641
 
@@ -2722,7 +2816,7 @@ async function mintBoardEnterCode(baseUrl, sessionToken) {
2722
2816
  }
2723
2817
 
2724
2818
  // dist/setup/manifest.js
2725
- import { existsSync as existsSync4, lstatSync, mkdirSync as mkdirSync4, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync4 } from "node:fs";
2819
+ import { existsSync as existsSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync4 } from "node:fs";
2726
2820
  import { dirname as dirname3, join as join6, posix as posix2 } from "node:path";
2727
2821
  var DECLARED_ROOT_FILES = ["CLAUDE.md", "AGENTS.md", "ENGAGEMENT.md"];
2728
2822
  var DECLARED_DIR_PREFIX = "docs";
@@ -2810,7 +2904,7 @@ function firstSymlinkOnPath(targetRepo, path) {
2810
2904
  for (let i = 0; i < segments.length; i++) {
2811
2905
  walked = join6(walked, segments[i]);
2812
2906
  try {
2813
- if (lstatSync(walked).isSymbolicLink())
2907
+ if (lstatSync2(walked).isSymbolicLink())
2814
2908
  return segments.slice(0, i + 1).join("/");
2815
2909
  } catch {
2816
2910
  return null;
@@ -3280,7 +3374,7 @@ function parseCloseRecord(path, parsed) {
3280
3374
 
3281
3375
  // dist/build-record/write.js
3282
3376
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
3283
- import { basename, join as join9, resolve, relative as relative2, isAbsolute } from "node:path";
3377
+ import { basename, join as join9, resolve, relative as relative2, isAbsolute as isAbsolute2 } from "node:path";
3284
3378
 
3285
3379
  // dist/build-record/template.js
3286
3380
  function severityLabel(seed) {
@@ -3334,7 +3428,7 @@ function renderBuildRecordMarkdown(record2) {
3334
3428
  `- **Tools:** ${record2.delivered.tools.join(", ")}`,
3335
3429
  `- **Dogfood:** ${record2.delivered.dogfood}`,
3336
3430
  "",
3337
- 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.",
3431
+ 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.",
3338
3432
  "",
3339
3433
  `## Acceptance (independent walk \u2014 walker ${record2.acceptance.walker})`,
3340
3434
  "",
@@ -3359,7 +3453,7 @@ function serialiseRecordJson(record2) {
3359
3453
  }
3360
3454
  function assertInsideOutDir(outDir, candidate, identity) {
3361
3455
  const rel = relative2(outDir, resolve(candidate));
3362
- const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute(rel) && rel === basename(rel);
3456
+ const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute2(rel) && rel === basename(rel);
3363
3457
  if (!isPlainFilename) {
3364
3458
  throw new InvalidPhaseIdentityError(identity, BUILD_RECORD_DIR);
3365
3459
  }