halfcycle 0.13.0 → 0.14.0-rc.1
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 +3499 -3147
- package/bin/pre-tool-use-filter.sh +13 -0
- package/dist/cli-contract.d.ts +1 -1
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/cli.js +212 -47
- package/dist/cli.js.map +3 -3
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/local-allow.d.ts +1 -1
- package/dist/local-allow.d.ts.map +1 -1
- package/dist/worker-copies.d.ts +43 -2
- package/dist/worker-copies.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@ halfcycle_pt_skip() {
|
|
|
10
10
|
hc_pt_live_text= hc_pt_live_sid= hc_pt_live_agent= hc_pt_live_dir=
|
|
11
11
|
hc_pt_own_file= hc_pt_own_agent_id= hc_pt_own_tool_use_id=
|
|
12
12
|
halfcycle_pt_agent "$@" || return 1
|
|
13
|
+
halfcycle_pt_notes || return 1
|
|
13
14
|
halfcycle_pt_quiet || return 1
|
|
14
15
|
halfcycle_pt_live || return 1
|
|
15
16
|
halfcycle_pt_own_copy || return 1
|
|
@@ -24,6 +25,18 @@ halfcycle_pt_agent() {
|
|
|
24
25
|
[ "$#" -eq 4 ] && [ "$3" = --agent ] && [ "$4" = codex ] && { hc_pt_agent=codex; return 0; }
|
|
25
26
|
return 1
|
|
26
27
|
}
|
|
28
|
+
halfcycle_pt_notes() {
|
|
29
|
+
local LC_ALL=C hc_val hc_dir hc_re='^[A-Za-z0-9-]{1,128}$'
|
|
30
|
+
halfcycle_pt_string agent_id
|
|
31
|
+
case $? in 0) [ -z "$hc_val" ] || return 0 ;; 1) ;; *) return 1 ;; esac
|
|
32
|
+
halfcycle_pt_string session_id
|
|
33
|
+
case $? in 0) ;; 1) return 0 ;; *) return 1 ;; esac
|
|
34
|
+
[[ $hc_val =~ $hc_re ]] || return 0
|
|
35
|
+
hc_dir=${TMPDIR:-/tmp}
|
|
36
|
+
while [ "${hc_dir%/}" != "$hc_dir" ]; do hc_dir=${hc_dir%/}; done
|
|
37
|
+
[ -e "$hc_dir/halfcycle-notes-$hc_val.json" ] && return 1
|
|
38
|
+
return 0
|
|
39
|
+
}
|
|
27
40
|
halfcycle_pt_own_read() {
|
|
28
41
|
local LC_ALL=C hc_val hc_dir hc_re='^[A-Za-z0-9_-]{1,128}$'
|
|
29
42
|
halfcycle_pt_string tool_name || return 1
|
package/dist/cli-contract.d.ts
CHANGED
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
* home is the failure mode it exists to prevent.
|
|
63
63
|
*/
|
|
64
64
|
/** The verbs the CLI dispatches. Anything else after `halfcycle ` is unknown. */
|
|
65
|
-
export declare const CLI_VERBS: readonly ["install", "check-drift", "build-record", "open-phase", "close-phase", "system", "draw", "plan", "review", "check-tasks", "check-criteria", "check-report", "ready", "uninstall", "change", "pace", "route", "prove", "preview", "doctor", "restore", "request-shapes", "allow", "size", "regression", "crew"];
|
|
65
|
+
export declare const CLI_VERBS: readonly ["install", "check-drift", "build-record", "open-phase", "close-phase", "system", "draw", "plan", "review", "check-tasks", "check-criteria", "check-report", "ready", "uninstall", "change", "pace", "route", "prove", "preview", "doctor", "restore", "request-shapes", "allow", "size", "regression", "crew", "remove-copy"];
|
|
66
66
|
export type CliVerb = (typeof CLI_VERBS)[number];
|
|
67
67
|
/** A flag every invocation of the verb must carry. */
|
|
68
68
|
export interface RequiredFlag {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-contract.d.ts","sourceRoot":"","sources":["../src/cli-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAKH,iFAAiF;AACjF,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"cli-contract.d.ts","sourceRoot":"","sources":["../src/cli-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAKH,iFAAiF;AACjF,eAAO,MAAM,SAAS,yUAA0U,CAAC;AAEjW,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,sDAAsD;AACtD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,CAAC;CAClD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,CAAC;IACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;CAC1C;AAiMD,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAElE;AAED,iDAAiD;AACjD,MAAM,WAAW,SAAS;IACxB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oCAAoC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAmBD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAAE,CAyBxF;AAuBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMtE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMvE;AASD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;CAErB,CAAC;AA2BX;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAa1F"}
|
package/dist/cli.js
CHANGED
|
@@ -2394,6 +2394,9 @@ function requestShapeLineText(line) {
|
|
|
2394
2394
|
return ordered(`${cut > 0 ? prefix.slice(0, cut) : ""}\u2026`);
|
|
2395
2395
|
}
|
|
2396
2396
|
|
|
2397
|
+
// ../core/dist/worker-copy-caches.js
|
|
2398
|
+
var WORKER_COPY_CACHE_FOLDERS = ["__pycache__", ".pytest_cache", ".mypy_cache", "node_modules"];
|
|
2399
|
+
|
|
2397
2400
|
// dist/request-shapes.js
|
|
2398
2401
|
var folder = join(homedir(), HALFCYCLE_DIR_NAME, REQUEST_SHAPES_DIR_NAME);
|
|
2399
2402
|
var logPath = join(folder, REQUEST_SHAPES_LOG_FILENAME);
|
|
@@ -6408,6 +6411,7 @@ var pacingBasisTaskSchema = z24.object({
|
|
|
6408
6411
|
human: z24.boolean(),
|
|
6409
6412
|
kind: buildTaskKindSchema.optional()
|
|
6410
6413
|
}).strict();
|
|
6414
|
+
var pacingBasisDoneTaskSchema = pacingBasisTaskSchema.extend({ done: z24.boolean() }).strict();
|
|
6411
6415
|
var pacingBasisSchema = z24.object({
|
|
6412
6416
|
outcome: z24.literal("basis"),
|
|
6413
6417
|
claudePlan: z24.string().nullable().optional(),
|
|
@@ -6418,6 +6422,7 @@ var pacingBasisSchema = z24.object({
|
|
|
6418
6422
|
medianTaskMinutes: z24.number().nonnegative().nullable(),
|
|
6419
6423
|
medianTaskTokens: z24.number().nonnegative().nullable()
|
|
6420
6424
|
}).strict();
|
|
6425
|
+
var pacingBasisDoneSchema = pacingBasisSchema.extend({ tasks: z24.array(pacingBasisDoneTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT) }).strict();
|
|
6421
6426
|
var paceSuggestionSchema = z24.object({
|
|
6422
6427
|
number: z24.number().int().min(1).max(PACE_AGENTS_MAX),
|
|
6423
6428
|
minutes: z24.number().int().nonnegative().nullable(),
|
|
@@ -9490,12 +9495,15 @@ var CREW_TIER_WORDS = {
|
|
|
9490
9495
|
fast: "You work quickly on clear, well-defined work, and say so when a task needs more thought."
|
|
9491
9496
|
};
|
|
9492
9497
|
var CREW_REVIEWER_WORDS = "You check the work you are given and never change it: you read it, run what you need to, and report what you find.";
|
|
9493
|
-
var
|
|
9494
|
-
var
|
|
9498
|
+
var CREW_SHELL_RULE = "Read and search files with your file tools, not the shell. Run one plain shell command per call, on one line: no cd first, and nothing joined to it with &&, ;, a pipe, a loop or a here-document.";
|
|
9499
|
+
var CREW_HALFCYCLE_TOOLS = `mcp__${MCP_SERVER_KEY}__*`;
|
|
9500
|
+
var CREW_REVIEWER_TOOLS = `Read, Grep, Glob, Bash, ${CREW_HALFCYCLE_TOOLS}`;
|
|
9501
|
+
var CREW_MEMBER_TOOLS = `Read, Edit, Write, Bash, Grep, Glob, ${CREW_HALFCYCLE_TOOLS}`;
|
|
9495
9502
|
function crewProfileBody(role, group, tier) {
|
|
9496
9503
|
const lines = [`You are a ${CREW_ROLE_WORDS[role]} on this project's build crew.`, CREW_TIER_WORDS[tier]];
|
|
9497
9504
|
if (group === "reviewer")
|
|
9498
9505
|
lines.push(CREW_REVIEWER_WORDS);
|
|
9506
|
+
lines.push(CREW_SHELL_RULE);
|
|
9499
9507
|
return `${lines.join("\n")}
|
|
9500
9508
|
`;
|
|
9501
9509
|
}
|
|
@@ -10495,8 +10503,8 @@ ${GENERATED_USER_PROMPT_FINDINGS_HEADER} the Halfcycle installer, now also for C
|
|
|
10495
10503
|
# whatever Halfcycle's checks found at an earlier turn end, once. Any arguments
|
|
10496
10504
|
# this script was given (a Codex row passes its agent's name) are handed on.
|
|
10497
10505
|
#
|
|
10498
|
-
# AN ORDINARY PROMPT STARTS NOTHING. With no findings file
|
|
10499
|
-
# script reads its session id with a bash regex (no node) and exits 0 with no
|
|
10506
|
+
# AN ORDINARY PROMPT STARTS NOTHING. With no findings file and no notes file for
|
|
10507
|
+
# this session, this script reads its session id with a bash regex (no node) and exits 0 with no
|
|
10500
10508
|
# output once it finds no file \u2014 node and grep never start.
|
|
10501
10509
|
trap 'exit 0' ERR
|
|
10502
10510
|
set -e
|
|
@@ -10520,7 +10528,8 @@ fi
|
|
|
10520
10528
|
HC_FINDINGS_DIR="\${TMPDIR:-/tmp}"
|
|
10521
10529
|
HC_FINDINGS_DIR="\${HC_FINDINGS_DIR%/}" # strip trailing slash (macOS TMPDIR ends in /)
|
|
10522
10530
|
HC_FINDINGS_FILE="\${HC_FINDINGS_DIR}/halfcycle-findings-\${HC_SESSION_ID}.json"
|
|
10523
|
-
|
|
10531
|
+
HC_NOTES_FILE="\${HC_FINDINGS_DIR}/halfcycle-notes-\${HC_SESSION_ID}.json"
|
|
10532
|
+
if [ ! -f "$HC_FINDINGS_FILE" ] && [ ! -f "$HC_NOTES_FILE" ]; then
|
|
10524
10533
|
exit 0
|
|
10525
10534
|
fi
|
|
10526
10535
|
|
|
@@ -11796,8 +11805,8 @@ function installSelfBuild(options) {
|
|
|
11796
11805
|
// dist/worker-copies.js
|
|
11797
11806
|
import { spawnSync } from "node:child_process";
|
|
11798
11807
|
import { existsSync as existsSync7, lstatSync as lstatSync2, readdirSync, realpathSync, rmdirSync, rmSync as rmSync3 } from "node:fs";
|
|
11799
|
-
import { isAbsolute as isAbsolute3, join as join9, relative as relative2, resolve as resolve2, sep } from "node:path";
|
|
11800
|
-
var
|
|
11808
|
+
import { basename, dirname as dirname5, isAbsolute as isAbsolute3, join as join9, relative as relative2, resolve as resolve2, sep } from "node:path";
|
|
11809
|
+
var REMOVE_COPY_USAGE = `${ownCommand()} remove-copy <path>`;
|
|
11801
11810
|
var COPIES_SHOWN = ".claude/worktrees/";
|
|
11802
11811
|
var CACHE_NAMES = new Set(WORKER_COPY_CACHE_FOLDERS);
|
|
11803
11812
|
var NOTHING = { removed: 0, kept: [], message: void 0 };
|
|
@@ -11837,27 +11846,18 @@ function tidy(projectDir) {
|
|
|
11837
11846
|
const mainPath = canonical(main2.path);
|
|
11838
11847
|
if (!samePath(mainPath, canonical(top.out.trim())))
|
|
11839
11848
|
return NOTHING;
|
|
11840
|
-
const
|
|
11849
|
+
const project = { root, mainPath, copiesDir: join9(root, ".claude", "worktrees") };
|
|
11841
11850
|
let removed = 0;
|
|
11842
11851
|
const kept = [];
|
|
11843
11852
|
for (const tree of worktrees.slice(1)) {
|
|
11844
11853
|
if (tree.bare)
|
|
11845
11854
|
continue;
|
|
11846
11855
|
const at = canonical(tree.path);
|
|
11847
|
-
|
|
11856
|
+
const settled = settleCopy(project, tree, at);
|
|
11857
|
+
if (settled === "not-ours" || settled === "gone")
|
|
11848
11858
|
continue;
|
|
11849
|
-
if (
|
|
11850
|
-
|
|
11851
|
-
const shown2 = relative2(root, at).split(sep).join("/");
|
|
11852
|
-
const held = tree.locked ? "work" : holdsWork(at, mainPath);
|
|
11853
|
-
if (held === "work") {
|
|
11854
|
-
kept.push(shown2);
|
|
11855
|
-
continue;
|
|
11856
|
-
}
|
|
11857
|
-
clearCaches(at, at);
|
|
11858
|
-
const remove = held === "duplicates" ? ["worktree", "remove", "--force", at] : ["worktree", "remove", at];
|
|
11859
|
-
if (!git2(root, remove).ok) {
|
|
11860
|
-
kept.push(shown2);
|
|
11859
|
+
if (settled !== "removed") {
|
|
11860
|
+
kept.push(shownPath(root, at));
|
|
11861
11861
|
continue;
|
|
11862
11862
|
}
|
|
11863
11863
|
removed += 1;
|
|
@@ -11866,6 +11866,128 @@ function tidy(projectDir) {
|
|
|
11866
11866
|
}
|
|
11867
11867
|
return { removed, kept, message: lineFor(removed, kept) };
|
|
11868
11868
|
}
|
|
11869
|
+
function settleCopy(project, tree, at) {
|
|
11870
|
+
if (samePath(at, project.mainPath) || !isInside(project.copiesDir, at))
|
|
11871
|
+
return "not-ours";
|
|
11872
|
+
if (!existsSync7(at))
|
|
11873
|
+
return "gone";
|
|
11874
|
+
if (tree.locked)
|
|
11875
|
+
return "running";
|
|
11876
|
+
const held = holdsWork(at, project.mainPath);
|
|
11877
|
+
if (held === "work")
|
|
11878
|
+
return "changes";
|
|
11879
|
+
clearCaches(at, at);
|
|
11880
|
+
const remove = held === "duplicates" ? ["worktree", "remove", "--force", at] : ["worktree", "remove", at];
|
|
11881
|
+
return git2(project.root, remove).ok ? "removed" : "changes";
|
|
11882
|
+
}
|
|
11883
|
+
function shownPath(root, at) {
|
|
11884
|
+
return relative2(root, at).split(sep).join("/");
|
|
11885
|
+
}
|
|
11886
|
+
var GitSilent = class extends Error {
|
|
11887
|
+
};
|
|
11888
|
+
var GIT_SILENT = {
|
|
11889
|
+
line: "[halfcycle] The worker copy was not removed, because git did not answer.",
|
|
11890
|
+
exitCode: 1,
|
|
11891
|
+
verdict: false
|
|
11892
|
+
};
|
|
11893
|
+
function removeWorkerCopy(cwd, given) {
|
|
11894
|
+
try {
|
|
11895
|
+
return removeOne(cwd, given);
|
|
11896
|
+
} catch {
|
|
11897
|
+
return GIT_SILENT;
|
|
11898
|
+
}
|
|
11899
|
+
}
|
|
11900
|
+
function removeOne(cwd, given) {
|
|
11901
|
+
const at = canonicalLoose(resolve2(cwd, given));
|
|
11902
|
+
const notACopy = {
|
|
11903
|
+
line: `[halfcycle] ${given} is not a worker copy under ${COPIES_SHOWN}, so nothing was removed.`,
|
|
11904
|
+
exitCode: 1,
|
|
11905
|
+
verdict: true
|
|
11906
|
+
};
|
|
11907
|
+
const top = git2(cwd, ["rev-parse", "--show-toplevel"]);
|
|
11908
|
+
if (!top.ran)
|
|
11909
|
+
throw new GitSilent();
|
|
11910
|
+
if (!top.ok)
|
|
11911
|
+
return notACopy;
|
|
11912
|
+
const listed = git2(cwd, ["worktree", "list", "--porcelain"]);
|
|
11913
|
+
if (!listed.ok)
|
|
11914
|
+
throw new GitSilent();
|
|
11915
|
+
const worktrees = parseWorktrees(listed.out);
|
|
11916
|
+
const main2 = worktrees[0];
|
|
11917
|
+
if (main2 === void 0 || main2.bare)
|
|
11918
|
+
throw new GitSilent();
|
|
11919
|
+
const mainPath = canonical(main2.path);
|
|
11920
|
+
if (!samePath(mainPath, canonical(top.out.trim()))) {
|
|
11921
|
+
return {
|
|
11922
|
+
line: `[halfcycle] Run this from the project's main folder, not from inside a worker copy. Nothing was removed.`,
|
|
11923
|
+
exitCode: 1,
|
|
11924
|
+
verdict: true
|
|
11925
|
+
};
|
|
11926
|
+
}
|
|
11927
|
+
const project = { root: mainPath, mainPath, copiesDir: join9(mainPath, ".claude", "worktrees") };
|
|
11928
|
+
if (!samePath(dirname5(at), project.copiesDir) || samePath(at, mainPath))
|
|
11929
|
+
return notACopy;
|
|
11930
|
+
const shown2 = shownPath(mainPath, at);
|
|
11931
|
+
const folder2 = basename(at);
|
|
11932
|
+
const tree = worktrees.slice(1).find((entry) => !entry.bare && samePath(canonicalLoose(entry.path), at));
|
|
11933
|
+
const done = (line) => ({ line, exitCode: 0, verdict: false });
|
|
11934
|
+
const kept = (why) => done(why === "running" ? `[halfcycle] Kept the worker copy ${shown2}: an agent is still working in it. Nothing was removed.` : `[halfcycle] Kept the worker copy ${shown2}: it holds changes that are not saved anywhere else. Nothing was removed.`);
|
|
11935
|
+
const goneLine = `[halfcycle] The worker copy ${shown2} is already gone.`;
|
|
11936
|
+
if (tree === void 0) {
|
|
11937
|
+
if (entryExists(at))
|
|
11938
|
+
return notACopy;
|
|
11939
|
+
return done(withBranch(goneLine, deleteCopyBranch(mainPath, folder2), false));
|
|
11940
|
+
}
|
|
11941
|
+
const settled = settleCopy(project, tree, at);
|
|
11942
|
+
if (settled === "not-ours")
|
|
11943
|
+
return notACopy;
|
|
11944
|
+
if (settled === "running" || settled === "changes")
|
|
11945
|
+
return kept(settled);
|
|
11946
|
+
if (settled === "removed")
|
|
11947
|
+
return done(withBranch(`[halfcycle] Removed the worker copy ${shown2}.`, deleteCopyBranch(mainPath, folder2), true));
|
|
11948
|
+
if (tree.locked)
|
|
11949
|
+
return kept("running");
|
|
11950
|
+
if (tree.detached && (tree.head === void 0 || !onSomeRef(mainPath, tree.head)))
|
|
11951
|
+
return kept("changes");
|
|
11952
|
+
const unlisted = git2(mainPath, ["worktree", "remove", tree.path]);
|
|
11953
|
+
if (!unlisted.ran)
|
|
11954
|
+
throw new GitSilent();
|
|
11955
|
+
if (!unlisted.ok)
|
|
11956
|
+
return kept("changes");
|
|
11957
|
+
return done(withBranch(goneLine, deleteCopyBranch(mainPath, folder2), false));
|
|
11958
|
+
}
|
|
11959
|
+
function deleteCopyBranch(root, folder2) {
|
|
11960
|
+
const name = `worktree-${folder2}`;
|
|
11961
|
+
if (!git2(root, ["show-ref", "--verify", "--quiet", `refs/heads/${name}`]).ok)
|
|
11962
|
+
return { name, outcome: "none" };
|
|
11963
|
+
return { name, outcome: git2(root, ["branch", "-d", name]).ok ? "deleted" : "kept" };
|
|
11964
|
+
}
|
|
11965
|
+
function withBranch(line, branch, namesDeleted) {
|
|
11966
|
+
if (branch.outcome === "none")
|
|
11967
|
+
return line;
|
|
11968
|
+
if (branch.outcome === "deleted")
|
|
11969
|
+
return namesDeleted ? `${line.slice(0, -1)} and its branch ${branch.name}.` : line;
|
|
11970
|
+
return `${line} Kept its branch ${branch.name}, which holds commits that are not merged.`;
|
|
11971
|
+
}
|
|
11972
|
+
function entryExists(path) {
|
|
11973
|
+
try {
|
|
11974
|
+
lstatSync2(path);
|
|
11975
|
+
return true;
|
|
11976
|
+
} catch {
|
|
11977
|
+
return false;
|
|
11978
|
+
}
|
|
11979
|
+
}
|
|
11980
|
+
function canonicalLoose(path) {
|
|
11981
|
+
const full = resolve2(path);
|
|
11982
|
+
try {
|
|
11983
|
+
return realpathSync(full);
|
|
11984
|
+
} catch {
|
|
11985
|
+
const parent = dirname5(full);
|
|
11986
|
+
if (parent === full)
|
|
11987
|
+
return full;
|
|
11988
|
+
return join9(canonicalLoose(parent), basename(full));
|
|
11989
|
+
}
|
|
11990
|
+
}
|
|
11869
11991
|
function lineFor(removed, kept) {
|
|
11870
11992
|
if (removed === 0 && kept.length === 0)
|
|
11871
11993
|
return void 0;
|
|
@@ -11890,10 +12012,14 @@ function parseWorktrees(porcelain) {
|
|
|
11890
12012
|
let current;
|
|
11891
12013
|
for (const line of porcelain.split(/\r?\n/)) {
|
|
11892
12014
|
if (line.startsWith("worktree ")) {
|
|
11893
|
-
current = { path: line.slice("worktree ".length), branch: void 0, locked: false, bare: false };
|
|
12015
|
+
current = { path: line.slice("worktree ".length), head: void 0, branch: void 0, detached: false, locked: false, bare: false };
|
|
11894
12016
|
trees.push(current);
|
|
11895
12017
|
} else if (current === void 0) {
|
|
11896
12018
|
continue;
|
|
12019
|
+
} else if (line.startsWith("HEAD ")) {
|
|
12020
|
+
current.head = line.slice("HEAD ".length);
|
|
12021
|
+
} else if (line === "detached") {
|
|
12022
|
+
current.detached = true;
|
|
11897
12023
|
} else if (line.startsWith("branch refs/heads/")) {
|
|
11898
12024
|
current.branch = line.slice("branch refs/heads/".length);
|
|
11899
12025
|
} else if (line === "locked" || line.startsWith("locked ")) {
|
|
@@ -11905,6 +12031,10 @@ function parseWorktrees(porcelain) {
|
|
|
11905
12031
|
return trees;
|
|
11906
12032
|
}
|
|
11907
12033
|
function holdsWork(copyDir, mainDir) {
|
|
12034
|
+
if (operationInProgress(copyDir))
|
|
12035
|
+
return "work";
|
|
12036
|
+
if (!onSomeRef(copyDir, "HEAD"))
|
|
12037
|
+
return "work";
|
|
11908
12038
|
const status = git2(copyDir, ["status", "--porcelain", "-z", "--untracked-files=all"]);
|
|
11909
12039
|
if (!status.ok)
|
|
11910
12040
|
return "work";
|
|
@@ -11926,6 +12056,20 @@ function holdsWork(copyDir, mainDir) {
|
|
|
11926
12056
|
}
|
|
11927
12057
|
return duplicates ? "duplicates" : "clean";
|
|
11928
12058
|
}
|
|
12059
|
+
var IN_PROGRESS_MARKS = ["rebase-merge", "rebase-apply", "MERGE_HEAD", "CHERRY_PICK_HEAD"];
|
|
12060
|
+
function operationInProgress(copyDir) {
|
|
12061
|
+
const paths = git2(copyDir, ["rev-parse", ...IN_PROGRESS_MARKS.flatMap((mark) => ["--git-path", mark])]);
|
|
12062
|
+
if (!paths.ok)
|
|
12063
|
+
return true;
|
|
12064
|
+
const lines = paths.out.split(/\r?\n/).filter((line) => line !== "");
|
|
12065
|
+
if (lines.length !== IN_PROGRESS_MARKS.length)
|
|
12066
|
+
return true;
|
|
12067
|
+
return lines.some((line) => entryExists(resolve2(copyDir, line)));
|
|
12068
|
+
}
|
|
12069
|
+
function onSomeRef(cwd, commit) {
|
|
12070
|
+
const holding = git2(cwd, ["for-each-ref", "--count=1", "--format=%(refname)", "--contains", commit]);
|
|
12071
|
+
return holding.ok && holding.out.trim() !== "";
|
|
12072
|
+
}
|
|
11929
12073
|
function duplicatesMain(copyDir, mainDir, path) {
|
|
11930
12074
|
if (path === "" || path.endsWith("/"))
|
|
11931
12075
|
return false;
|
|
@@ -13421,7 +13565,7 @@ async function confirmActingIdentity(serviceUrl, credential, act, deps = {}) {
|
|
|
13421
13565
|
}
|
|
13422
13566
|
|
|
13423
13567
|
// dist/project-name.js
|
|
13424
|
-
import { basename, resolve as resolve3 } from "node:path";
|
|
13568
|
+
import { basename as basename2, resolve as resolve3 } from "node:path";
|
|
13425
13569
|
var MAX_DERIVED_NAME_LEN = 100;
|
|
13426
13570
|
var EMPTY_SEGMENTS = /* @__PURE__ */ new Set(["", ".", "..", "/", "\\"]);
|
|
13427
13571
|
function repositoryNameFromRemote(remote) {
|
|
@@ -13441,7 +13585,7 @@ function isLocalRemote(remote) {
|
|
|
13441
13585
|
function deriveProjectName(targetRepoRoot) {
|
|
13442
13586
|
const remote = readRemote(targetRepoRoot);
|
|
13443
13587
|
const fromRemote = remote && !isLocalRemote(remote) ? repositoryNameFromRemote(remote) : void 0;
|
|
13444
|
-
const candidate = fromRemote ??
|
|
13588
|
+
const candidate = fromRemote ?? basename2(resolve3(targetRepoRoot));
|
|
13445
13589
|
const name = candidate.trim();
|
|
13446
13590
|
if (EMPTY_SEGMENTS.has(name))
|
|
13447
13591
|
return void 0;
|
|
@@ -13532,7 +13676,7 @@ async function joinPinnedEngagement(serviceUrl, engagementId, targetRepo, deps =
|
|
|
13532
13676
|
// dist/pre-create-checks.js
|
|
13533
13677
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
13534
13678
|
import { existsSync as existsSync8, readFileSync as readFileSync9, realpathSync as realpathSync3 } from "node:fs";
|
|
13535
|
-
import { delimiter, dirname as
|
|
13679
|
+
import { delimiter, dirname as dirname6, join as join11 } from "node:path";
|
|
13536
13680
|
var KEPT_FOLDER_ID_LINE = `Nothing on the Halfcycle service needs undoing, and this folder keeps its project id, so running "${ownBareCommand()}" again picks up where it left off.`;
|
|
13537
13681
|
var PreCreateRefused = class extends Error {
|
|
13538
13682
|
constructor(message) {
|
|
@@ -13574,7 +13718,7 @@ function refuseSystemFolder(targetRepo, platform6 = process.platform) {
|
|
|
13574
13718
|
}
|
|
13575
13719
|
function refuseInsideProject(targetRepo) {
|
|
13576
13720
|
let folder2 = realOrLiteral(targetRepo);
|
|
13577
|
-
for (let parent =
|
|
13721
|
+
for (let parent = dirname6(folder2); parent !== folder2; folder2 = parent, parent = dirname6(parent)) {
|
|
13578
13722
|
if (existsSync8(join11(parent, BUNDLE_PIN_REL))) {
|
|
13579
13723
|
throw new PreCreateRefused(`${targetRepo} is inside the Halfcycle project at ${parent} \u2014 open that folder in your coding agent instead, or run "${ownBareCommand()}" in a folder outside it.`);
|
|
13580
13724
|
}
|
|
@@ -13702,7 +13846,7 @@ function isSelfBuildInstallForm(engagementIdArg, engagementTypeArg) {
|
|
|
13702
13846
|
// dist/local-allow.js
|
|
13703
13847
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
13704
13848
|
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync11, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
13705
|
-
import { dirname as
|
|
13849
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
13706
13850
|
var LOCAL_SETTINGS_REL2 = ".claude/settings.local.json";
|
|
13707
13851
|
var LOCAL_ALLOW_RECORD_REL = ".halfcycle/local-allow.json";
|
|
13708
13852
|
var LOCAL_ALLOW_EDIT_MODE = "acceptEdits";
|
|
@@ -13758,6 +13902,7 @@ var LOCAL_ALLOW_RULES = [
|
|
|
13758
13902
|
bash("bun add"),
|
|
13759
13903
|
bash("gh repo view"),
|
|
13760
13904
|
bash("gh issue create"),
|
|
13905
|
+
bash("gh pr merge"),
|
|
13761
13906
|
bash("go mod tidy"),
|
|
13762
13907
|
bash("go get"),
|
|
13763
13908
|
bash("cargo add"),
|
|
@@ -13797,7 +13942,7 @@ var LOCAL_ALLOW_RULES = [
|
|
|
13797
13942
|
bash("tr"),
|
|
13798
13943
|
HALFCYCLE_TOOLS_RULE
|
|
13799
13944
|
];
|
|
13800
|
-
var LOCAL_ALLOW_QUESTION = "Let your coding agent work without asking on this computer? It would run your tests and builds, install packages, run local tools, make and move files, use git locally, run Halfcycle, and edit files in this project without a permission prompt. Opening a ticket on GitHub
|
|
13945
|
+
var LOCAL_ALLOW_QUESTION = "Let your coding agent work without asking on this computer? It would run your tests and builds, install packages, run local tools, make and move files, use git locally, run Halfcycle, and edit files in this project without a permission prompt. Opening a ticket and merging reviewed work on GitHub do not ask either. Pushing, publishing, deploying to production and deleting still ask. This is saved only on this computer, in .claude/settings.local.json, which is not committed.\nType yes to allow it, or press Enter to keep asking: ";
|
|
13801
13946
|
var LOCAL_ALLOW_ON_LINE = `Your coding agent can now work without asking on this computer. To undo it, run: ${ownCommand()} allow --remove`;
|
|
13802
13947
|
var LOCAL_ALLOW_OFF_LINE = `Your coding agent will ask before it runs commands. To stop most of those questions on this computer, run: ${ownCommand()} allow`;
|
|
13803
13948
|
var LOCAL_ALLOW_TRACKED_LINE = "Your .claude/settings.local.json is committed to git, so Halfcycle will not add rules to it. Remove it from git first if you want them.";
|
|
@@ -13837,7 +13982,7 @@ function readRecord2(repo) {
|
|
|
13837
13982
|
}
|
|
13838
13983
|
function writeRecord(repo, record2) {
|
|
13839
13984
|
const path = join13(repo, LOCAL_ALLOW_RECORD_REL);
|
|
13840
|
-
mkdirSync7(
|
|
13985
|
+
mkdirSync7(dirname7(path), { recursive: true });
|
|
13841
13986
|
writeFileSync7(path, JSON.stringify({ rules: record2.rules, defaultMode: record2.defaultMode, before: record2.before }, null, 2) + "\n");
|
|
13842
13987
|
}
|
|
13843
13988
|
function holdsLocalAllowRecord(repo) {
|
|
@@ -13888,7 +14033,7 @@ function writeLocalAllow(repo) {
|
|
|
13888
14033
|
if (setsMode)
|
|
13889
14034
|
permissions["defaultMode"] = LOCAL_ALLOW_EDIT_MODE;
|
|
13890
14035
|
const next = { ...settings, permissions };
|
|
13891
|
-
mkdirSync7(
|
|
14036
|
+
mkdirSync7(dirname7(path), { recursive: true });
|
|
13892
14037
|
writeFileSync7(path, render(next, text));
|
|
13893
14038
|
const earlier = readRecord2(repo);
|
|
13894
14039
|
writeRecord(repo, {
|
|
@@ -14009,7 +14154,7 @@ function approveOwnServer(repo) {
|
|
|
14009
14154
|
const plan = planOwnServerApproval(repo, registersOwnServer(repo));
|
|
14010
14155
|
if (plan.after !== null) {
|
|
14011
14156
|
const path = join13(repo, LOCAL_SETTINGS_REL2);
|
|
14012
|
-
mkdirSync7(
|
|
14157
|
+
mkdirSync7(dirname7(path), { recursive: true });
|
|
14013
14158
|
writeFileSync7(path, plan.after);
|
|
14014
14159
|
}
|
|
14015
14160
|
return plan.outcome;
|
|
@@ -14754,7 +14899,7 @@ function codexInstallLine(codex) {
|
|
|
14754
14899
|
|
|
14755
14900
|
// dist/build-record/write.js
|
|
14756
14901
|
import { existsSync as existsSync13, mkdirSync as mkdirSync8, rmSync as rmSync7 } from "node:fs";
|
|
14757
|
-
import { basename as
|
|
14902
|
+
import { basename as basename3, join as join17, resolve as resolve4, relative as relative3, isAbsolute as isAbsolute4 } from "node:path";
|
|
14758
14903
|
|
|
14759
14904
|
// dist/build-record/render-v2.js
|
|
14760
14905
|
var HIDDEN = /[\p{Cc}\p{Zl}\p{Zp}\p{Default_Ignorable_Code_Point}]/u;
|
|
@@ -15426,7 +15571,7 @@ function replaceOne(io, tmp, finalPath, text) {
|
|
|
15426
15571
|
var BUILD_RECORD_DIR = join17("docs", "build-records");
|
|
15427
15572
|
function assertInsideOutDir(outDir, candidate, identity) {
|
|
15428
15573
|
const rel = relative3(outDir, resolve4(candidate));
|
|
15429
|
-
const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute4(rel) && rel ===
|
|
15574
|
+
const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute4(rel) && rel === basename3(rel);
|
|
15430
15575
|
if (!isPlainFilename) {
|
|
15431
15576
|
throw new InvalidPhaseIdentityError(identity, BUILD_RECORD_DIR);
|
|
15432
15577
|
}
|
|
@@ -15517,7 +15662,7 @@ import { join as join23 } from "node:path";
|
|
|
15517
15662
|
|
|
15518
15663
|
// dist/draw/write.js
|
|
15519
15664
|
import { closeSync as closeSync2, lstatSync as lstatSync4, mkdirSync as mkdirSync9, openSync as openSync2, readFileSync as readFileSync15, readSync, realpathSync as realpathSync7, rmSync as rmSync8 } from "node:fs";
|
|
15520
|
-
import { dirname as
|
|
15665
|
+
import { dirname as dirname8, isAbsolute as isAbsolute5, join as join18, relative as relative4, resolve as resolve5, sep as sep2 } from "node:path";
|
|
15521
15666
|
var DOCS_DIR = "docs";
|
|
15522
15667
|
var DIAGRAMS_DIR = "diagrams";
|
|
15523
15668
|
var DIAGRAM_DIR_REL = `${DOCS_DIR}/${DIAGRAMS_DIR}`;
|
|
@@ -15610,7 +15755,7 @@ function checkDiagramPlace(paths) {
|
|
|
15610
15755
|
}
|
|
15611
15756
|
let ancestor = paths.dir;
|
|
15612
15757
|
while (lstatOrNull2(ancestor) === null) {
|
|
15613
|
-
const parent =
|
|
15758
|
+
const parent = dirname8(ancestor);
|
|
15614
15759
|
if (parent === ancestor)
|
|
15615
15760
|
break;
|
|
15616
15761
|
ancestor = parent;
|
|
@@ -15851,7 +15996,7 @@ function isMissingRoute(method, status, body) {
|
|
|
15851
15996
|
import { join as join21 } from "node:path";
|
|
15852
15997
|
|
|
15853
15998
|
// dist/cli-contract.js
|
|
15854
|
-
var CLI_VERBS = ["install", "check-drift", "build-record", "open-phase", "close-phase", "system", "draw", "plan", "review", "check-tasks", "check-criteria", "check-report", "ready", "uninstall", "change", "pace", "route", "prove", "preview", "doctor", "restore", "request-shapes", "allow", "size", "regression", "crew"];
|
|
15999
|
+
var CLI_VERBS = ["install", "check-drift", "build-record", "open-phase", "close-phase", "system", "draw", "plan", "review", "check-tasks", "check-criteria", "check-report", "ready", "uninstall", "change", "pace", "route", "prove", "preview", "doctor", "restore", "request-shapes", "allow", "size", "regression", "crew", "remove-copy"];
|
|
15855
16000
|
var CONTRACTS = [
|
|
15856
16001
|
{ verb: "install", required: [], conditional: [] },
|
|
15857
16002
|
{ verb: "check-drift", required: [], conditional: [] },
|
|
@@ -15871,6 +16016,7 @@ var CONTRACTS = [
|
|
|
15871
16016
|
{ verb: "doctor", required: [], conditional: [] },
|
|
15872
16017
|
{ verb: "restore", required: [], conditional: [] },
|
|
15873
16018
|
{ verb: "allow", required: [], conditional: [] },
|
|
16019
|
+
{ verb: "remove-copy", required: [], conditional: [] },
|
|
15874
16020
|
{
|
|
15875
16021
|
verb: "request-shapes",
|
|
15876
16022
|
required: [],
|
|
@@ -16322,7 +16468,7 @@ async function runSystem(repoRoot, deps) {
|
|
|
16322
16468
|
|
|
16323
16469
|
// dist/build-record/write-change.js
|
|
16324
16470
|
import { existsSync as existsSync14, mkdirSync as mkdirSync10 } from "node:fs";
|
|
16325
|
-
import { basename as
|
|
16471
|
+
import { basename as basename4, isAbsolute as isAbsolute6, join as join20, relative as relative5, resolve as resolve6 } from "node:path";
|
|
16326
16472
|
|
|
16327
16473
|
// dist/build-record/render-change.js
|
|
16328
16474
|
function counted2(n, one, many) {
|
|
@@ -16478,7 +16624,7 @@ var InvalidChangeIdError = class extends Error {
|
|
|
16478
16624
|
};
|
|
16479
16625
|
function assertInsideChangeDir(outDir, candidate, id) {
|
|
16480
16626
|
const rel = relative5(outDir, resolve6(candidate));
|
|
16481
|
-
const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute6(rel) && rel ===
|
|
16627
|
+
const isPlainFilename = rel !== "" && rel !== ".." && !isAbsolute6(rel) && rel === basename4(rel);
|
|
16482
16628
|
if (!isPlainFilename)
|
|
16483
16629
|
throw new InvalidChangeIdError(id);
|
|
16484
16630
|
}
|
|
@@ -19568,7 +19714,7 @@ function runProveChild(binary, credential, repoRoot, words, deps = {}) {
|
|
|
19568
19714
|
// dist/uninstall.js
|
|
19569
19715
|
import { existsSync as existsSync17, lstatSync as lstatSync5, readFileSync as readFileSync23, readdirSync as readdirSync2, rmSync as rmSync9, rmdirSync as rmdirSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
19570
19716
|
import { homedir as homedir7 } from "node:os";
|
|
19571
|
-
import { dirname as
|
|
19717
|
+
import { dirname as dirname9, join as join28, relative as relative7, sep as sep3 } from "node:path";
|
|
19572
19718
|
var REMOVE_CREDENTIAL_FLAG = "--remove-credential";
|
|
19573
19719
|
function parseUninstallArgs(args2) {
|
|
19574
19720
|
let removeCredential = false;
|
|
@@ -19669,7 +19815,7 @@ function validate(options, rules) {
|
|
|
19669
19815
|
}
|
|
19670
19816
|
if (engagementId !== void 0) {
|
|
19671
19817
|
const expectedParent = join28(home ?? homedir7(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME);
|
|
19672
|
-
if (
|
|
19818
|
+
if (dirname9(engagementStateDir(engagementId, home)) !== expectedParent) {
|
|
19673
19819
|
throw new Refusal(`the project id in ${BUNDLE_PIN_REL} does not name a folder under ~/.halfcycle/engagements. Nothing was removed.`);
|
|
19674
19820
|
}
|
|
19675
19821
|
}
|
|
@@ -20452,7 +20598,7 @@ function filesUnder(root, rel) {
|
|
|
20452
20598
|
walk(rel);
|
|
20453
20599
|
return out;
|
|
20454
20600
|
}
|
|
20455
|
-
function
|
|
20601
|
+
function shownPath2(path, home) {
|
|
20456
20602
|
const base = home ?? homedir7();
|
|
20457
20603
|
const rel = relative7(base, path);
|
|
20458
20604
|
const under = rel !== "" && !rel.startsWith("..") && !rel.startsWith(sep3);
|
|
@@ -20483,7 +20629,7 @@ function renderUninstallReport(result, home) {
|
|
|
20483
20629
|
const c = result.credential;
|
|
20484
20630
|
switch (c.kind) {
|
|
20485
20631
|
case "still-held":
|
|
20486
|
-
out.push(`[halfcycle] This machine still holds this project's credential, at ${
|
|
20632
|
+
out.push(`[halfcycle] This machine still holds this project's credential, at ${shownPath2(c.dir, home)}.`);
|
|
20487
20633
|
out.push(" Every checkout and worktree of this project on this machine uses it.");
|
|
20488
20634
|
out.push(` To remove it too: ${ownCommand()} uninstall ${REMOVE_CREDENTIAL_FLAG}`);
|
|
20489
20635
|
break;
|
|
@@ -20491,18 +20637,18 @@ function renderUninstallReport(result, home) {
|
|
|
20491
20637
|
out.push(`[halfcycle] This machine may still hold a credential for this project, but the project id in ${BUNDLE_PIN_REL} is not valid, so it is not shown as a path.`);
|
|
20492
20638
|
break;
|
|
20493
20639
|
case "removed":
|
|
20494
|
-
out.push(`[halfcycle] Removed this machine's credential for this project, at ${
|
|
20640
|
+
out.push(`[halfcycle] Removed this machine's credential for this project, at ${shownPath2(c.dir, home)}.`);
|
|
20495
20641
|
out.push(" Every other checkout and worktree of this project on this machine used it too. Each stops reaching");
|
|
20496
20642
|
out.push(` Halfcycle until \`${ownCommand()}\` is run there again.`);
|
|
20497
20643
|
break;
|
|
20498
20644
|
case "not-held":
|
|
20499
|
-
out.push(`[halfcycle] This machine holds no credential for this project (nothing at ${
|
|
20645
|
+
out.push(`[halfcycle] This machine holds no credential for this project (nothing at ${shownPath2(c.dir, home)}).`);
|
|
20500
20646
|
break;
|
|
20501
20647
|
case "no-project-id":
|
|
20502
20648
|
out.push(`[halfcycle] No credential was removed: ${BUNDLE_PIN_REL} is missing, so there is no project id to find it by.`);
|
|
20503
20649
|
break;
|
|
20504
20650
|
case "failed":
|
|
20505
|
-
out.push(`[halfcycle] Could not remove this machine's credential for this project, at ${
|
|
20651
|
+
out.push(`[halfcycle] Could not remove this machine's credential for this project, at ${shownPath2(c.dir, home)}: ${c.reason}. Run "${ownCommand()} uninstall ${REMOVE_CREDENTIAL_FLAG}" again to finish.`);
|
|
20506
20652
|
break;
|
|
20507
20653
|
case "nothing-to-say":
|
|
20508
20654
|
break;
|
|
@@ -20512,7 +20658,7 @@ function renderUninstallReport(result, home) {
|
|
|
20512
20658
|
}
|
|
20513
20659
|
|
|
20514
20660
|
// dist/banner-facts.js
|
|
20515
|
-
import { basename as
|
|
20661
|
+
import { basename as basename5, resolve as resolve8 } from "node:path";
|
|
20516
20662
|
var BRAND_URL = "halfcycle.ai";
|
|
20517
20663
|
var TAGLINE = [
|
|
20518
20664
|
"Fewer cycles.",
|
|
@@ -20632,7 +20778,7 @@ function openingBannerContent(projectDir, env = process.env, probes = REAL_PROBE
|
|
|
20632
20778
|
}
|
|
20633
20779
|
function safeBasename(dir) {
|
|
20634
20780
|
try {
|
|
20635
|
-
const name =
|
|
20781
|
+
const name = basename5(resolve8(dir)).trim();
|
|
20636
20782
|
return name === "" ? "project" : name;
|
|
20637
20783
|
} catch {
|
|
20638
20784
|
return "project";
|
|
@@ -21910,6 +22056,9 @@ var USAGE = ` ${ownCommand()} [install] [target-repo] [engagement-id] [self-bui
|
|
|
21910
22056
|
let your coding agent work without asking on this computer, for tests, builds, installs, local
|
|
21911
22057
|
tools, files and local git; pushing, publishing, deploying, deleting and GitHub still ask;
|
|
21912
22058
|
--remove takes out what this added and keeps everything of yours
|
|
22059
|
+
${REMOVE_COPY_USAGE}
|
|
22060
|
+
remove one worker copy under .claude/worktrees/ and its worktree- branch, from the project's
|
|
22061
|
+
main folder; a copy that may hold work is kept
|
|
21913
22062
|
${ownCommand()} uninstall [${REMOVE_CREDENTIAL_FLAG}]
|
|
21914
22063
|
take out what the installer added to this project, keeping anything you changed
|
|
21915
22064
|
${REMOVE_CREDENTIAL_FLAG}: also remove this machine's credential for the project, which every
|
|
@@ -22089,6 +22238,22 @@ ${USAGE}`);
|
|
|
22089
22238
|
process.exitCode = refused3 ? 1 : 0;
|
|
22090
22239
|
return;
|
|
22091
22240
|
}
|
|
22241
|
+
if (cmd === "remove-copy") {
|
|
22242
|
+
const given = rest[0];
|
|
22243
|
+
if (rest.length !== 1 || given === void 0 || given === "" || given.startsWith("-")) {
|
|
22244
|
+
process.stderr.write(`halfcycle remove-copy: usage: ${REMOVE_COPY_USAGE}
|
|
22245
|
+
`);
|
|
22246
|
+
process.exitCode = 2;
|
|
22247
|
+
return;
|
|
22248
|
+
}
|
|
22249
|
+
const done = removeWorkerCopy(process.cwd(), given);
|
|
22250
|
+
if (done.verdict)
|
|
22251
|
+
exitIsAVerdict();
|
|
22252
|
+
(done.exitCode === 0 ? process.stdout : process.stderr).write(`${done.line}
|
|
22253
|
+
`);
|
|
22254
|
+
process.exitCode = done.exitCode;
|
|
22255
|
+
return;
|
|
22256
|
+
}
|
|
22092
22257
|
if (installArm) {
|
|
22093
22258
|
const engagementIdArg = positionals[1];
|
|
22094
22259
|
const engagementTypeRaw = positionals[2] ?? "client";
|