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/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +310 -40
- package/dist/bin.js +212 -108
- package/dist/bin.js.map +3 -3
- package/dist/identity.d.ts +39 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/index.js +196 -102
- package/dist/index.js.map +3 -3
- package/dist/install.d.ts +60 -40
- package/dist/install.d.ts.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 !== ".." && !
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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
|