halfcycle 0.13.0-rc.1 → 0.13.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcycle",
3
- "version": "0.13.0-rc.1",
3
+ "version": "0.13.0-rc.2",
4
4
  "description": "Halfcycle Method bundle — resolution-stub slash commands, remote method-delivery registration, and governance-hook wiring for a Halfcycle engagement repo. It ships NO worker role files: a project's roles are authored from that project's own recorded decisions at orchestration kickoff (FX-2, W3-F-27).",
5
5
  "commands": [
6
6
  {
package/dist/cli.js CHANGED
@@ -11849,12 +11849,14 @@ function tidy(projectDir) {
11849
11849
  if (!existsSync7(at))
11850
11850
  continue;
11851
11851
  const shown2 = relative2(root, at).split(sep).join("/");
11852
- if (tree.locked || holdsWork(at)) {
11852
+ const held = tree.locked ? "work" : holdsWork(at, mainPath);
11853
+ if (held === "work") {
11853
11854
  kept.push(shown2);
11854
11855
  continue;
11855
11856
  }
11856
11857
  clearCaches(at, at);
11857
- if (!git2(root, ["worktree", "remove", at]).ok) {
11858
+ const remove = held === "duplicates" ? ["worktree", "remove", "--force", at] : ["worktree", "remove", at];
11859
+ if (!git2(root, remove).ok) {
11858
11860
  kept.push(shown2);
11859
11861
  continue;
11860
11862
  }
@@ -11902,23 +11904,53 @@ function parseWorktrees(porcelain) {
11902
11904
  }
11903
11905
  return trees;
11904
11906
  }
11905
- function holdsWork(copyDir) {
11907
+ function holdsWork(copyDir, mainDir) {
11906
11908
  const status = git2(copyDir, ["status", "--porcelain", "-z", "--untracked-files=all"]);
11907
11909
  if (!status.ok)
11908
- return true;
11910
+ return "work";
11911
+ let duplicates = false;
11909
11912
  const fields = status.out.split("\0");
11910
11913
  for (let i = 0; i < fields.length; i += 1) {
11911
11914
  const field = fields[i];
11912
11915
  if (field === "")
11913
11916
  continue;
11914
11917
  const code = field.slice(0, 2);
11915
- if (code !== "??")
11916
- return true;
11917
11918
  const path = field.slice(3);
11918
- if (!path.split("/").some((segment) => CACHE_NAMES.has(segment)))
11919
- return true;
11919
+ if (code === "??" && path.split("/").some((segment) => CACHE_NAMES.has(segment)))
11920
+ continue;
11921
+ if (code !== "??" && code !== " M" && code !== "M " && code !== "A ")
11922
+ return "work";
11923
+ if (!duplicatesMain(copyDir, mainDir, path))
11924
+ return "work";
11925
+ duplicates = true;
11920
11926
  }
11921
- return false;
11927
+ return duplicates ? "duplicates" : "clean";
11928
+ }
11929
+ function duplicatesMain(copyDir, mainDir, path) {
11930
+ if (path === "" || path.endsWith("/"))
11931
+ return false;
11932
+ let executable;
11933
+ try {
11934
+ const entry = lstatSync2(join9(copyDir, ...path.split("/")));
11935
+ if (!entry.isFile())
11936
+ return false;
11937
+ executable = (entry.mode & 64) !== 0;
11938
+ } catch {
11939
+ return false;
11940
+ }
11941
+ const listed = git2(mainDir, ["--literal-pathspecs", "ls-tree", "-z", "--full-tree", "HEAD", "--", path]);
11942
+ if (!listed.ok)
11943
+ return false;
11944
+ const entries = listed.out.split("\0").filter((entry) => entry !== "");
11945
+ if (entries.length !== 1)
11946
+ return false;
11947
+ const match = /^(100644|100755) blob ([0-9a-f]+)\t([\s\S]*)$/.exec(entries[0]);
11948
+ if (match === null || match[3] !== path)
11949
+ return false;
11950
+ if (process.platform !== "win32" && executable !== (match[1] === "100755"))
11951
+ return false;
11952
+ const hashed = git2(copyDir, ["hash-object", "--no-filters", "--", path]);
11953
+ return hashed.ok && hashed.out.trim() === match[2];
11922
11954
  }
11923
11955
  function clearCaches(copyDir, dir) {
11924
11956
  let names;