halfcycle 0.4.0 → 0.4.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/README.md +4 -8
- package/bin/bin.bundle.mjs +171 -138
- package/commands/halfcycle-setup.md +3 -3
- package/dist/bin.js +1 -6823
- package/dist/claude-version-probe.d.ts +32 -0
- package/dist/claude-version-probe.d.ts.map +1 -1
- package/dist/cli.js +7161 -0
- package/dist/cli.js.map +6 -0
- package/dist/confirm-identity.d.ts +26 -1
- package/dist/confirm-identity.d.ts.map +1 -1
- package/dist/create-engagement.d.ts +30 -0
- package/dist/create-engagement.d.ts.map +1 -1
- package/dist/device-signin.d.ts.map +1 -1
- package/dist/identity.d.ts +50 -13
- package/dist/identity.d.ts.map +1 -1
- package/dist/index.js +208 -116
- package/dist/index.js.map +3 -3
- package/dist/install.d.ts +57 -0
- package/dist/install.d.ts.map +1 -1
- package/dist/merge-settings.d.ts +11 -2
- package/dist/merge-settings.d.ts.map +1 -1
- package/dist/node-floor.d.ts +54 -0
- package/dist/node-floor.d.ts.map +1 -0
- package/dist/own-engagement.d.ts +25 -5
- package/dist/own-engagement.d.ts.map +1 -1
- package/dist/pre-create-checks.d.ts +61 -0
- package/dist/pre-create-checks.d.ts.map +1 -0
- package/dist/resolve-credential.d.ts.map +1 -1
- package/dist/uninstall.d.ts +31 -9
- package/dist/uninstall.d.ts.map +1 -1
- package/package.json +4 -1
- package/dist/bin.js.map +0 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halfcycle",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
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/README.md
CHANGED
|
@@ -167,11 +167,7 @@ Nothing you already had is changed. Existing settings are preserved, and a name
|
|
|
167
167
|
- Claude Code **2.1.118+**
|
|
168
168
|
- Node 20+
|
|
169
169
|
- A git repository
|
|
170
|
-
- On Windows,
|
|
171
|
-
|
|
172
|
-
```powershell
|
|
173
|
-
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
|
|
174
|
-
```
|
|
170
|
+
- On Windows, in PowerShell, use `npx.cmd` instead of `npx` (PowerShell's default settings block the `npx` script; `npx.cmd` always works, with nothing to change).
|
|
175
171
|
|
|
176
172
|
## Getting started
|
|
177
173
|
|
|
@@ -181,19 +177,19 @@ git init
|
|
|
181
177
|
npx halfcycle
|
|
182
178
|
```
|
|
183
179
|
|
|
184
|
-
In Windows PowerShell, separate the first two commands with `;` instead of
|
|
180
|
+
In Windows PowerShell, separate the first two commands with `;` instead of `&&`, and run `npx.cmd` instead of `npx`:
|
|
185
181
|
|
|
186
182
|
```powershell
|
|
187
183
|
mkdir my-project; cd my-project
|
|
188
184
|
git init
|
|
189
|
-
npx halfcycle
|
|
185
|
+
npx.cmd halfcycle
|
|
190
186
|
```
|
|
191
187
|
|
|
192
188
|
Then open the folder in Claude Code and run `/halfcycle-setup`, and answer the questions it asks about your project.
|
|
193
189
|
|
|
194
190
|
**Accept the workspace trust prompt when Claude Code shows it.** The install writes a credential helper and a set of hooks that are shell commands, so Claude Code asks before it runs them. It is expected.
|
|
195
191
|
|
|
196
|
-
**A second prompt follows — it is not the trust prompt again.** After you accept the trust prompt, Claude Code asks whether to use a new MCP server.
|
|
192
|
+
**A second prompt follows — it is not the trust prompt again.** After you accept the trust prompt, Claude Code asks whether to use a new MCP server. If halfcycle is the only one listed, the cursor starts on *Continue without using this MCP server*; choose to use the server instead, or Halfcycle will not connect. If several new servers are listed together, halfcycle is already checked — leave it that way.
|
|
197
193
|
|
|
198
194
|
You will get a board link on install. That is where the work becomes visible to anyone who is not you.
|
|
199
195
|
|
package/bin/bin.bundle.mjs
CHANGED
|
@@ -14664,58 +14664,70 @@ var GUARD_RUNNER_REL = ".claude/hooks/guard-runner.sh";
|
|
|
14664
14664
|
var USER_PROMPT_REMINDER_REL = ".claude/hooks/user-prompt-reminder.sh";
|
|
14665
14665
|
var SESSION_END_MARKER_REL = ".claude/hooks/session-end-marker.sh";
|
|
14666
14666
|
var WRITE_CAPABLE_TOOLS = ["Edit", "MultiEdit", "NotebookEdit", "Write"];
|
|
14667
|
+
function hookCommand(script, verb) {
|
|
14668
|
+
const invocation = `bash "$CLAUDE_PROJECT_DIR"/${script}`;
|
|
14669
|
+
return verb === void 0 ? invocation : `${invocation} ${verb}`;
|
|
14670
|
+
}
|
|
14667
14671
|
var HALFCYCLE_HOOK_WIRING = [
|
|
14668
14672
|
{
|
|
14669
14673
|
event: "SessionStart",
|
|
14670
14674
|
matcher: null,
|
|
14671
14675
|
timeout: 5,
|
|
14672
|
-
command:
|
|
14676
|
+
command: hookCommand(SESSION_START_MARKER_REL),
|
|
14673
14677
|
script: SESSION_START_MARKER_REL
|
|
14674
14678
|
},
|
|
14675
14679
|
{
|
|
14676
14680
|
event: "SessionStart",
|
|
14677
14681
|
matcher: null,
|
|
14678
14682
|
timeout: 10,
|
|
14679
|
-
command:
|
|
14683
|
+
command: hookCommand(GUARD_RUNNER_REL, "refresh-credential"),
|
|
14680
14684
|
script: GUARD_RUNNER_REL
|
|
14681
14685
|
},
|
|
14682
14686
|
{
|
|
14683
14687
|
event: "UserPromptSubmit",
|
|
14684
14688
|
matcher: null,
|
|
14685
14689
|
timeout: 5,
|
|
14686
|
-
command:
|
|
14690
|
+
command: hookCommand(USER_PROMPT_REMINDER_REL),
|
|
14687
14691
|
script: USER_PROMPT_REMINDER_REL
|
|
14688
14692
|
},
|
|
14689
14693
|
{
|
|
14690
14694
|
event: "PostToolUse",
|
|
14691
14695
|
matcher: WRITE_CAPABLE_TOOLS.join("|"),
|
|
14692
14696
|
timeout: 60,
|
|
14693
|
-
command:
|
|
14697
|
+
command: hookCommand(GUARD_RUNNER_REL, "hook post-tool-use"),
|
|
14694
14698
|
script: GUARD_RUNNER_REL
|
|
14695
14699
|
},
|
|
14696
14700
|
{
|
|
14697
14701
|
event: "Stop",
|
|
14698
14702
|
matcher: null,
|
|
14699
14703
|
timeout: 60,
|
|
14700
|
-
command:
|
|
14704
|
+
command: hookCommand(GUARD_RUNNER_REL, "hook stop"),
|
|
14701
14705
|
script: GUARD_RUNNER_REL
|
|
14702
14706
|
},
|
|
14703
14707
|
{
|
|
14704
14708
|
event: "SubagentStop",
|
|
14705
14709
|
matcher: null,
|
|
14706
14710
|
timeout: 60,
|
|
14707
|
-
command:
|
|
14711
|
+
command: hookCommand(GUARD_RUNNER_REL, "hook subagent-stop"),
|
|
14708
14712
|
script: GUARD_RUNNER_REL
|
|
14709
14713
|
},
|
|
14710
14714
|
{
|
|
14711
14715
|
event: "SessionEnd",
|
|
14712
14716
|
matcher: null,
|
|
14713
14717
|
timeout: 5,
|
|
14714
|
-
command:
|
|
14718
|
+
command: hookCommand(SESSION_END_MARKER_REL),
|
|
14715
14719
|
script: SESSION_END_MARKER_REL
|
|
14716
14720
|
}
|
|
14717
14721
|
];
|
|
14718
|
-
var RETIRED_HOOK_COMMANDS = [
|
|
14722
|
+
var RETIRED_HOOK_COMMANDS = [
|
|
14723
|
+
"bash ./.claude/hooks/session-start-marker.sh",
|
|
14724
|
+
"bash ./.claude/hooks/guard-runner.sh refresh-credential",
|
|
14725
|
+
"bash ./.claude/hooks/user-prompt-reminder.sh",
|
|
14726
|
+
"bash ./.claude/hooks/guard-runner.sh hook post-tool-use",
|
|
14727
|
+
"bash ./.claude/hooks/guard-runner.sh hook stop",
|
|
14728
|
+
"bash ./.claude/hooks/guard-runner.sh hook subagent-stop",
|
|
14729
|
+
"bash ./.claude/hooks/session-end-marker.sh"
|
|
14730
|
+
];
|
|
14719
14731
|
var HALFCYCLE_HOOK_COMMANDS = /* @__PURE__ */ new Set([
|
|
14720
14732
|
...HALFCYCLE_HOOK_WIRING.map((entry) => entry.command),
|
|
14721
14733
|
...RETIRED_HOOK_COMMANDS
|
|
@@ -15738,15 +15750,18 @@ function getSingleFileDiff(absFilePath, repoRoot) {
|
|
|
15738
15750
|
return "";
|
|
15739
15751
|
}
|
|
15740
15752
|
}
|
|
15753
|
+
function workingTreeBase(repoRoot) {
|
|
15754
|
+
return refExists("HEAD", repoRoot) ? "HEAD" : EMPTY_TREE_SHA;
|
|
15755
|
+
}
|
|
15741
15756
|
function getWorkingTreeChangedFiles(repoRoot) {
|
|
15742
|
-
const result = git(["diff",
|
|
15757
|
+
const result = git(["diff", workingTreeBase(repoRoot), "--name-only", "--diff-filter=d"], repoRoot);
|
|
15743
15758
|
if (!result.ok)
|
|
15744
15759
|
return result;
|
|
15745
15760
|
const files = result.value.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
15746
15761
|
return { ok: true, value: files };
|
|
15747
15762
|
}
|
|
15748
15763
|
function getWorkingTreeFileDiff(relPath, repoRoot) {
|
|
15749
|
-
const result = git(["diff",
|
|
15764
|
+
const result = git(["diff", workingTreeBase(repoRoot), "--", relPath], repoRoot);
|
|
15750
15765
|
if (result.ok && result.value.trim())
|
|
15751
15766
|
return result.value;
|
|
15752
15767
|
return "";
|
|
@@ -17450,10 +17465,143 @@ async function evaluateWithRefresh(opts) {
|
|
|
17450
17465
|
return { result: retry, token: refreshed.token, remedy: HOOK_REMEDY };
|
|
17451
17466
|
}
|
|
17452
17467
|
|
|
17468
|
+
// dist/hooks/marker.js
|
|
17469
|
+
import { createHash } from "node:crypto";
|
|
17470
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync } from "node:fs";
|
|
17471
|
+
import { tmpdir } from "node:os";
|
|
17472
|
+
import { join as join6 } from "node:path";
|
|
17473
|
+
var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
17474
|
+
var MARKER_PREFIX = "halfcycle-session-";
|
|
17475
|
+
var MARKER_SUFFIX = ".ref";
|
|
17476
|
+
var DEDUPE_SUFFIX = ".eval";
|
|
17477
|
+
function markerDir() {
|
|
17478
|
+
const raw = process.env["TMPDIR"] ?? tmpdir();
|
|
17479
|
+
return raw.replace(/\/+$/, "");
|
|
17480
|
+
}
|
|
17481
|
+
function repoHash(repoRoot) {
|
|
17482
|
+
return createHash("sha256").update(repoRoot).digest("hex").slice(0, 12);
|
|
17483
|
+
}
|
|
17484
|
+
function markerFileName(repoRoot, sessionId) {
|
|
17485
|
+
const hash2 = repoHash(repoRoot);
|
|
17486
|
+
const key = sessionId && sessionId.length > 0 ? `${hash2}-${sessionId}` : hash2;
|
|
17487
|
+
return `${MARKER_PREFIX}${key}${MARKER_SUFFIX}`;
|
|
17488
|
+
}
|
|
17489
|
+
function markerPath(repoRoot, sessionId) {
|
|
17490
|
+
return join6(markerDir(), markerFileName(repoRoot, sessionId));
|
|
17491
|
+
}
|
|
17492
|
+
function dedupePath(repoRoot, sessionId) {
|
|
17493
|
+
const hash2 = repoHash(repoRoot);
|
|
17494
|
+
const key = sessionId && sessionId.length > 0 ? `${hash2}-${sessionId}` : hash2;
|
|
17495
|
+
return join6(markerDir(), `${MARKER_PREFIX}${key}${DEDUPE_SUFFIX}`);
|
|
17496
|
+
}
|
|
17497
|
+
function readMarker(repoRoot, sessionId) {
|
|
17498
|
+
const path = markerPath(repoRoot, sessionId);
|
|
17499
|
+
if (!existsSync3(path))
|
|
17500
|
+
return null;
|
|
17501
|
+
try {
|
|
17502
|
+
return readFileSync6(path, "utf-8").trim() || null;
|
|
17503
|
+
} catch {
|
|
17504
|
+
return null;
|
|
17505
|
+
}
|
|
17506
|
+
}
|
|
17507
|
+
function deleteMarker(repoRoot, sessionId) {
|
|
17508
|
+
for (const path of [markerPath(repoRoot, sessionId), dedupePath(repoRoot, sessionId)]) {
|
|
17509
|
+
try {
|
|
17510
|
+
if (existsSync3(path))
|
|
17511
|
+
unlinkSync3(path);
|
|
17512
|
+
} catch {
|
|
17513
|
+
}
|
|
17514
|
+
}
|
|
17515
|
+
}
|
|
17516
|
+
var NO_REPO_NOTICE_SUFFIX = ".norepo";
|
|
17517
|
+
function firstNoRepoNoticeThisSession(folder, sessionId) {
|
|
17518
|
+
if (!sessionId)
|
|
17519
|
+
return true;
|
|
17520
|
+
const path = join6(markerDir(), `${MARKER_PREFIX}${repoHash(folder)}-${sessionId}${NO_REPO_NOTICE_SUFFIX}`);
|
|
17521
|
+
try {
|
|
17522
|
+
writeFileSync3(path, "", { encoding: "utf-8", flag: "wx" });
|
|
17523
|
+
return true;
|
|
17524
|
+
} catch (err) {
|
|
17525
|
+
return err.code !== "EEXIST";
|
|
17526
|
+
}
|
|
17527
|
+
}
|
|
17528
|
+
function readDedupe(repoRoot, sessionId) {
|
|
17529
|
+
const path = dedupePath(repoRoot, sessionId);
|
|
17530
|
+
if (!existsSync3(path))
|
|
17531
|
+
return null;
|
|
17532
|
+
try {
|
|
17533
|
+
const parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
17534
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed.base === "string" && typeof parsed.digest === "string") {
|
|
17535
|
+
return parsed;
|
|
17536
|
+
}
|
|
17537
|
+
return null;
|
|
17538
|
+
} catch {
|
|
17539
|
+
return null;
|
|
17540
|
+
}
|
|
17541
|
+
}
|
|
17542
|
+
function writeDedupe(repoRoot, sessionId, state) {
|
|
17543
|
+
try {
|
|
17544
|
+
writeFileSync3(dedupePath(repoRoot, sessionId), JSON.stringify(state), "utf-8");
|
|
17545
|
+
} catch {
|
|
17546
|
+
}
|
|
17547
|
+
}
|
|
17548
|
+
function digestChangeState(base, entries) {
|
|
17549
|
+
const h = createHash("sha256");
|
|
17550
|
+
h.update(base);
|
|
17551
|
+
h.update("\0");
|
|
17552
|
+
for (const e of [...entries].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
17553
|
+
h.update(e.path);
|
|
17554
|
+
h.update("\0");
|
|
17555
|
+
h.update(e.diffContent);
|
|
17556
|
+
h.update("\0");
|
|
17557
|
+
}
|
|
17558
|
+
return h.digest("hex");
|
|
17559
|
+
}
|
|
17560
|
+
function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
|
|
17561
|
+
const dir = markerDir();
|
|
17562
|
+
const hash2 = repoHash(repoRoot);
|
|
17563
|
+
const keepName = keepSessionId ? markerFileName(repoRoot, keepSessionId) : markerFileName(repoRoot);
|
|
17564
|
+
let names;
|
|
17565
|
+
try {
|
|
17566
|
+
names = readdirSync3(dir);
|
|
17567
|
+
} catch {
|
|
17568
|
+
return;
|
|
17569
|
+
}
|
|
17570
|
+
for (const name of names) {
|
|
17571
|
+
if (!name.startsWith(`${MARKER_PREFIX}${hash2}`) || !name.endsWith(MARKER_SUFFIX))
|
|
17572
|
+
continue;
|
|
17573
|
+
if (name === keepName)
|
|
17574
|
+
continue;
|
|
17575
|
+
const full = join6(dir, name);
|
|
17576
|
+
let unusable = false;
|
|
17577
|
+
try {
|
|
17578
|
+
const ref = readFileSync6(full, "utf-8").trim();
|
|
17579
|
+
if (!ref || !refKnown(ref))
|
|
17580
|
+
unusable = true;
|
|
17581
|
+
} catch {
|
|
17582
|
+
unusable = true;
|
|
17583
|
+
}
|
|
17584
|
+
if (!unusable) {
|
|
17585
|
+
try {
|
|
17586
|
+
const age = now - statSync(full).mtimeMs;
|
|
17587
|
+
if (age > MARKER_TTL_MS)
|
|
17588
|
+
unusable = true;
|
|
17589
|
+
} catch {
|
|
17590
|
+
unusable = true;
|
|
17591
|
+
}
|
|
17592
|
+
}
|
|
17593
|
+
if (unusable) {
|
|
17594
|
+
const mid = name.slice(`${MARKER_PREFIX}${hash2}`.length, -MARKER_SUFFIX.length);
|
|
17595
|
+
const sid = mid.startsWith("-") ? mid.slice(1) : void 0;
|
|
17596
|
+
deleteMarker(repoRoot, sid);
|
|
17597
|
+
}
|
|
17598
|
+
}
|
|
17599
|
+
}
|
|
17600
|
+
|
|
17453
17601
|
// dist/seat-configuration.js
|
|
17454
|
-
import { existsSync as
|
|
17602
|
+
import { existsSync as existsSync4, readFileSync as readFileSync7, realpathSync as realpathSync2 } from "node:fs";
|
|
17455
17603
|
import { homedir as homedir3 } from "node:os";
|
|
17456
|
-
import { join as
|
|
17604
|
+
import { join as join7, relative as relative3, resolve } from "node:path";
|
|
17457
17605
|
function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
|
|
17458
17606
|
try {
|
|
17459
17607
|
const facts = read(repoRoot);
|
|
@@ -17488,7 +17636,7 @@ function isPlainObject2(value) {
|
|
|
17488
17636
|
function readSettingsFile(projectDir, rel) {
|
|
17489
17637
|
let raw;
|
|
17490
17638
|
try {
|
|
17491
|
-
raw =
|
|
17639
|
+
raw = readFileSync7(join7(projectDir, rel), "utf-8");
|
|
17492
17640
|
} catch (err) {
|
|
17493
17641
|
const code = err.code;
|
|
17494
17642
|
const state = code === "ENOENT" || code === "ENOTDIR" ? "absent" : "unreadable";
|
|
@@ -17560,7 +17708,7 @@ function readPathStates(projectDir, repoRoot) {
|
|
|
17560
17708
|
const status = getWiringStatus(HALFCYCLE_HOOK_WIRING_PATHS, projectDir);
|
|
17561
17709
|
if (!status.ok)
|
|
17562
17710
|
return unknown2();
|
|
17563
|
-
const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) =>
|
|
17711
|
+
const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) => existsSync4(join7(projectDir, rel)));
|
|
17564
17712
|
return mapped ?? unknown2();
|
|
17565
17713
|
}
|
|
17566
17714
|
function mapWiringStatus(output, prefix, paths, onDisk) {
|
|
@@ -17625,7 +17773,9 @@ async function evaluateEdit(reply, readSeat) {
|
|
|
17625
17773
|
return;
|
|
17626
17774
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
17627
17775
|
if (!repoRootResult.ok) {
|
|
17628
|
-
|
|
17776
|
+
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
17777
|
+
reply.notice(localFaultText(`Could not find git repo root: ${repoRootResult.message}`));
|
|
17778
|
+
}
|
|
17629
17779
|
return;
|
|
17630
17780
|
}
|
|
17631
17781
|
const repoRoot = repoRootResult.value;
|
|
@@ -17740,127 +17890,6 @@ function readStdin() {
|
|
|
17740
17890
|
});
|
|
17741
17891
|
}
|
|
17742
17892
|
|
|
17743
|
-
// dist/hooks/marker.js
|
|
17744
|
-
import { createHash } from "node:crypto";
|
|
17745
|
-
import { existsSync as existsSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync } from "node:fs";
|
|
17746
|
-
import { tmpdir } from "node:os";
|
|
17747
|
-
import { join as join7 } from "node:path";
|
|
17748
|
-
var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
17749
|
-
var MARKER_PREFIX = "halfcycle-session-";
|
|
17750
|
-
var MARKER_SUFFIX = ".ref";
|
|
17751
|
-
var DEDUPE_SUFFIX = ".eval";
|
|
17752
|
-
function markerDir() {
|
|
17753
|
-
const raw = process.env["TMPDIR"] ?? tmpdir();
|
|
17754
|
-
return raw.replace(/\/+$/, "");
|
|
17755
|
-
}
|
|
17756
|
-
function repoHash(repoRoot) {
|
|
17757
|
-
return createHash("sha256").update(repoRoot).digest("hex").slice(0, 12);
|
|
17758
|
-
}
|
|
17759
|
-
function markerFileName(repoRoot, sessionId) {
|
|
17760
|
-
const hash2 = repoHash(repoRoot);
|
|
17761
|
-
const key = sessionId && sessionId.length > 0 ? `${hash2}-${sessionId}` : hash2;
|
|
17762
|
-
return `${MARKER_PREFIX}${key}${MARKER_SUFFIX}`;
|
|
17763
|
-
}
|
|
17764
|
-
function markerPath(repoRoot, sessionId) {
|
|
17765
|
-
return join7(markerDir(), markerFileName(repoRoot, sessionId));
|
|
17766
|
-
}
|
|
17767
|
-
function dedupePath(repoRoot, sessionId) {
|
|
17768
|
-
const hash2 = repoHash(repoRoot);
|
|
17769
|
-
const key = sessionId && sessionId.length > 0 ? `${hash2}-${sessionId}` : hash2;
|
|
17770
|
-
return join7(markerDir(), `${MARKER_PREFIX}${key}${DEDUPE_SUFFIX}`);
|
|
17771
|
-
}
|
|
17772
|
-
function readMarker(repoRoot, sessionId) {
|
|
17773
|
-
const path = markerPath(repoRoot, sessionId);
|
|
17774
|
-
if (!existsSync4(path))
|
|
17775
|
-
return null;
|
|
17776
|
-
try {
|
|
17777
|
-
return readFileSync7(path, "utf-8").trim() || null;
|
|
17778
|
-
} catch {
|
|
17779
|
-
return null;
|
|
17780
|
-
}
|
|
17781
|
-
}
|
|
17782
|
-
function deleteMarker(repoRoot, sessionId) {
|
|
17783
|
-
for (const path of [markerPath(repoRoot, sessionId), dedupePath(repoRoot, sessionId)]) {
|
|
17784
|
-
try {
|
|
17785
|
-
if (existsSync4(path))
|
|
17786
|
-
unlinkSync3(path);
|
|
17787
|
-
} catch {
|
|
17788
|
-
}
|
|
17789
|
-
}
|
|
17790
|
-
}
|
|
17791
|
-
function readDedupe(repoRoot, sessionId) {
|
|
17792
|
-
const path = dedupePath(repoRoot, sessionId);
|
|
17793
|
-
if (!existsSync4(path))
|
|
17794
|
-
return null;
|
|
17795
|
-
try {
|
|
17796
|
-
const parsed = JSON.parse(readFileSync7(path, "utf-8"));
|
|
17797
|
-
if (typeof parsed === "object" && parsed !== null && typeof parsed.base === "string" && typeof parsed.digest === "string") {
|
|
17798
|
-
return parsed;
|
|
17799
|
-
}
|
|
17800
|
-
return null;
|
|
17801
|
-
} catch {
|
|
17802
|
-
return null;
|
|
17803
|
-
}
|
|
17804
|
-
}
|
|
17805
|
-
function writeDedupe(repoRoot, sessionId, state) {
|
|
17806
|
-
try {
|
|
17807
|
-
writeFileSync3(dedupePath(repoRoot, sessionId), JSON.stringify(state), "utf-8");
|
|
17808
|
-
} catch {
|
|
17809
|
-
}
|
|
17810
|
-
}
|
|
17811
|
-
function digestChangeState(base, entries) {
|
|
17812
|
-
const h = createHash("sha256");
|
|
17813
|
-
h.update(base);
|
|
17814
|
-
h.update("\0");
|
|
17815
|
-
for (const e of [...entries].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
17816
|
-
h.update(e.path);
|
|
17817
|
-
h.update("\0");
|
|
17818
|
-
h.update(e.diffContent);
|
|
17819
|
-
h.update("\0");
|
|
17820
|
-
}
|
|
17821
|
-
return h.digest("hex");
|
|
17822
|
-
}
|
|
17823
|
-
function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
|
|
17824
|
-
const dir = markerDir();
|
|
17825
|
-
const hash2 = repoHash(repoRoot);
|
|
17826
|
-
const keepName = keepSessionId ? markerFileName(repoRoot, keepSessionId) : markerFileName(repoRoot);
|
|
17827
|
-
let names;
|
|
17828
|
-
try {
|
|
17829
|
-
names = readdirSync3(dir);
|
|
17830
|
-
} catch {
|
|
17831
|
-
return;
|
|
17832
|
-
}
|
|
17833
|
-
for (const name of names) {
|
|
17834
|
-
if (!name.startsWith(`${MARKER_PREFIX}${hash2}`) || !name.endsWith(MARKER_SUFFIX))
|
|
17835
|
-
continue;
|
|
17836
|
-
if (name === keepName)
|
|
17837
|
-
continue;
|
|
17838
|
-
const full = join7(dir, name);
|
|
17839
|
-
let unusable = false;
|
|
17840
|
-
try {
|
|
17841
|
-
const ref = readFileSync7(full, "utf-8").trim();
|
|
17842
|
-
if (!ref || !refKnown(ref))
|
|
17843
|
-
unusable = true;
|
|
17844
|
-
} catch {
|
|
17845
|
-
unusable = true;
|
|
17846
|
-
}
|
|
17847
|
-
if (!unusable) {
|
|
17848
|
-
try {
|
|
17849
|
-
const age = now - statSync(full).mtimeMs;
|
|
17850
|
-
if (age > MARKER_TTL_MS)
|
|
17851
|
-
unusable = true;
|
|
17852
|
-
} catch {
|
|
17853
|
-
unusable = true;
|
|
17854
|
-
}
|
|
17855
|
-
}
|
|
17856
|
-
if (unusable) {
|
|
17857
|
-
const mid = name.slice(`${MARKER_PREFIX}${hash2}`.length, -MARKER_SUFFIX.length);
|
|
17858
|
-
const sid = mid.startsWith("-") ? mid.slice(1) : void 0;
|
|
17859
|
-
deleteMarker(repoRoot, sid);
|
|
17860
|
-
}
|
|
17861
|
-
}
|
|
17862
|
-
}
|
|
17863
|
-
|
|
17864
17893
|
// dist/hooks/session-diff.js
|
|
17865
17894
|
var STOP_HOOK_EVENT = {
|
|
17866
17895
|
stop: "Stop",
|
|
@@ -17904,7 +17933,9 @@ async function runSessionDiff(input, kind, readSeat = readSeatConfiguration) {
|
|
|
17904
17933
|
async function evaluateSession(input, kind, reply, readSeat) {
|
|
17905
17934
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
17906
17935
|
if (!repoRootResult.ok) {
|
|
17907
|
-
|
|
17936
|
+
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
17937
|
+
reply.notice(localFaultText(`Could not find git repo root: ${repoRootResult.message}`));
|
|
17938
|
+
}
|
|
17908
17939
|
return;
|
|
17909
17940
|
}
|
|
17910
17941
|
const repoRoot = repoRootResult.value;
|
|
@@ -18376,6 +18407,7 @@ async function refreshCredentialToken(opts) {
|
|
|
18376
18407
|
return { token: refreshed.token };
|
|
18377
18408
|
}
|
|
18378
18409
|
var CHECKED_SOURCES = /* @__PURE__ */ new Set(["startup", "resume"]);
|
|
18410
|
+
var CONNECTED_SENTENCE = "Halfcycle connected.";
|
|
18379
18411
|
var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} });
|
|
18380
18412
|
function registeredMcpUrl(projectRoot) {
|
|
18381
18413
|
let parsed;
|
|
@@ -18461,7 +18493,8 @@ async function checkConnection(renewedToken, opts) {
|
|
|
18461
18493
|
if (CREDENTIAL_PLACEHOLDERS.includes(bearer)) {
|
|
18462
18494
|
return PLACEHOLDER_SENTENCES[bearer];
|
|
18463
18495
|
}
|
|
18464
|
-
|
|
18496
|
+
const outcome = await probeDelivery(url2, bearer, opts.fetchFn ?? globalThis.fetch, opts.checkDeadlineMs ?? CONNECTION_CHECK_DEADLINE_MS);
|
|
18497
|
+
return outcome ?? CONNECTED_SENTENCE;
|
|
18465
18498
|
}
|
|
18466
18499
|
function defaultWriteErr2(text) {
|
|
18467
18500
|
process.stderr.write(text);
|
|
@@ -4,8 +4,8 @@ description: Set up a repository for a Halfcycle engagement
|
|
|
4
4
|
|
|
5
5
|
Resolve `/halfcycle-setup` for this engagement's current state, execute what comes back, report that the step ran, and resolve again — repeating until the response says there is nothing left (`hasNextStep` false); pausing for a human answer is not the end of the run. Do not improvise the procedure. Say where things stand at the boundaries of the run — when it starts, when it moves to a new layer, and when it ends — rather than on every turn, and pause only where a step asks you to. At one of those boundaries, and at most once per run, point them at this project's own page on the web and say what is there: how the project is going, the agents working on it once there are any, and their other projects alongside it. Use the address this install already records for the project's page rather than building one from an example, and give them the address itself — a page they are told about but cannot reach is worse than not mentioning it. Some steps of the Halfcycle method are judged by Halfcycle rather than by this session, or need a person to do something outside this session, so a quiet run is not a claim that nothing was due — say so when a run ends. Everything you say to the person is in plain words. Name the Halfcycle method the first time and say what it is in the same breath — the process that will guide the two of you through building this — because bare "the method" means nothing to someone who has not met it. After that, talk about their product and never about how any of this works underneath: no paths or files of ours, no hooks, settings, commands, layers or step names, and never Halfcycle's own machinery as the subject of a sentence. Say what now exists, what it is for, and what you need from them, in their terms. The one thing you never soften and never leave out is what did NOT happen — say that plainly too. If you are asked to jump ahead: when that is what comes next anyway, just do it; when Halfcycle refuses, relay its reason word for word and invent no procedure of your own; when it can be served but skips something, do it and say what is being skipped and what that costs. Coming back later is fine — a fresh session re-orients from what this project has recorded, so ask to carry on in plain words and you get the right next step and where things stand.
|
|
6
6
|
|
|
7
|
-
If resolution fails, report
|
|
7
|
+
If resolution fails, report whichever applies and stop:
|
|
8
8
|
|
|
9
9
|
> Halfcycle didn't answer for `/halfcycle-setup`. **Nothing has run.**
|
|
10
|
-
>
|
|
11
|
-
> it there and accept the workspace-trust prompt
|
|
10
|
+
> Service error — Halfcycle's service is down, try again shortly. Tool declined — run `/mcp` and reconnect it, then run this again.
|
|
11
|
+
> Folder not yet trusted — Claude Code does not trust this folder yet, open it there and accept the workspace-trust prompt, then run this again.
|