halfcycle 0.4.0 → 0.4.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -9
- package/bin/bin.bundle.mjs +188 -148
- 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 +7188 -0
- package/dist/cli.js.map +6 -0
- package/dist/confirm-identity.d.ts +28 -18
- package/dist/confirm-identity.d.ts.map +1 -1
- package/dist/create-engagement.d.ts +32 -0
- package/dist/create-engagement.d.ts.map +1 -1
- package/dist/device-signin.d.ts +35 -2
- package/dist/device-signin.d.ts.map +1 -1
- package/dist/identity.d.ts +57 -13
- package/dist/identity.d.ts.map +1 -1
- package/dist/index.js +228 -123
- package/dist/index.js.map +3 -3
- package/dist/install.d.ts +78 -2
- 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 +88 -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.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/README.md
CHANGED
|
@@ -165,13 +165,9 @@ Nothing you already had is changed. Existing settings are preserved, and a name
|
|
|
165
165
|
## Requirements
|
|
166
166
|
|
|
167
167
|
- Claude Code **2.1.118+**
|
|
168
|
-
- Node
|
|
168
|
+
- Node 18+
|
|
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
|
|
@@ -15364,6 +15376,9 @@ function infraWarningText() {
|
|
|
15364
15376
|
function localFaultText(message) {
|
|
15365
15377
|
return `[Halfcycle] WARNING: Local fault \u2014 ${message}. The guard service was not contacted. Session continues (fail-open).`;
|
|
15366
15378
|
}
|
|
15379
|
+
function noRepositoryText(failure) {
|
|
15380
|
+
return failure.gitMissing ? "[Halfcycle] WARNING: Git is not installed on this machine, and Halfcycle's checks need it, so nothing will be checked in this session. Install Git, then start a new session." : localFaultText(`Could not find git repo root: ${failure.message}`);
|
|
15381
|
+
}
|
|
15367
15382
|
function contractFaultText(message) {
|
|
15368
15383
|
return `[Halfcycle] WARNING: Guard response could not be understood \u2014 ${message}
|
|
15369
15384
|
This is not a service outage: the guard service answered. The suspect is this repository's own installed halfcycle runner, which is out of date with what the service now sends. Update it: run "npx halfcycle" in this repository, then retry. Session continues (fail-open).`;
|
|
@@ -15699,7 +15714,7 @@ async function attempt(url2, token, request, fetchFn, retry) {
|
|
|
15699
15714
|
|
|
15700
15715
|
// dist/git.js
|
|
15701
15716
|
import { spawnSync } from "node:child_process";
|
|
15702
|
-
import { readFileSync } from "node:fs";
|
|
15717
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15703
15718
|
import { relative } from "node:path";
|
|
15704
15719
|
var COLOR_AWARE_SUBCOMMANDS = /* @__PURE__ */ new Set(["diff", "show", "log"]);
|
|
15705
15720
|
function git(args2, cwd, timeoutMs) {
|
|
@@ -15710,7 +15725,8 @@ function git(args2, cwd, timeoutMs) {
|
|
|
15710
15725
|
...timeoutMs === void 0 ? {} : { timeout: timeoutMs }
|
|
15711
15726
|
});
|
|
15712
15727
|
if (result.error) {
|
|
15713
|
-
|
|
15728
|
+
const missing = result.error.code === "ENOENT" && existsSync(cwd);
|
|
15729
|
+
return { ok: false, message: `git error: ${result.error.message}`, ...missing ? { gitMissing: true } : {} };
|
|
15714
15730
|
}
|
|
15715
15731
|
if (result.status !== 0) {
|
|
15716
15732
|
const stderr = result.stderr?.trim() ?? "";
|
|
@@ -15738,15 +15754,18 @@ function getSingleFileDiff(absFilePath, repoRoot) {
|
|
|
15738
15754
|
return "";
|
|
15739
15755
|
}
|
|
15740
15756
|
}
|
|
15757
|
+
function workingTreeBase(repoRoot) {
|
|
15758
|
+
return refExists("HEAD", repoRoot) ? "HEAD" : EMPTY_TREE_SHA;
|
|
15759
|
+
}
|
|
15741
15760
|
function getWorkingTreeChangedFiles(repoRoot) {
|
|
15742
|
-
const result = git(["diff",
|
|
15761
|
+
const result = git(["diff", workingTreeBase(repoRoot), "--name-only", "--diff-filter=d"], repoRoot);
|
|
15743
15762
|
if (!result.ok)
|
|
15744
15763
|
return result;
|
|
15745
15764
|
const files = result.value.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
15746
15765
|
return { ok: true, value: files };
|
|
15747
15766
|
}
|
|
15748
15767
|
function getWorkingTreeFileDiff(relPath, repoRoot) {
|
|
15749
|
-
const result = git(["diff",
|
|
15768
|
+
const result = git(["diff", workingTreeBase(repoRoot), "--", relPath], repoRoot);
|
|
15750
15769
|
if (result.ok && result.value.trim())
|
|
15751
15770
|
return result.value;
|
|
15752
15771
|
return "";
|
|
@@ -16590,7 +16609,7 @@ function resolveWireFields(lines, decls) {
|
|
|
16590
16609
|
}
|
|
16591
16610
|
|
|
16592
16611
|
// dist/fixtures-manifest.js
|
|
16593
|
-
import { existsSync, mkdirSync, readFileSync as readFileSync3, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16612
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16594
16613
|
import { dirname, join as join2 } from "node:path";
|
|
16595
16614
|
function manifestPath(repoRoot) {
|
|
16596
16615
|
return join2(repoRoot, "test", "fixtures", "captured", "manifest.json");
|
|
@@ -16629,7 +16648,7 @@ function upsertManifest(repoRoot, entry) {
|
|
|
16629
16648
|
}
|
|
16630
16649
|
function removeFixtureIfExists(path) {
|
|
16631
16650
|
try {
|
|
16632
|
-
if (
|
|
16651
|
+
if (existsSync2(path))
|
|
16633
16652
|
unlinkSync(path);
|
|
16634
16653
|
} catch {
|
|
16635
16654
|
}
|
|
@@ -17251,7 +17270,7 @@ function cleanRunSummary(verdict, scope) {
|
|
|
17251
17270
|
}
|
|
17252
17271
|
|
|
17253
17272
|
// dist/credential-store.js
|
|
17254
|
-
import { chmodSync, existsSync as
|
|
17273
|
+
import { chmodSync, existsSync as existsSync3, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2, realpathSync } from "node:fs";
|
|
17255
17274
|
import { homedir as homedir2, platform } from "node:os";
|
|
17256
17275
|
import { dirname as dirname3, join as join5 } from "node:path";
|
|
17257
17276
|
var REFRESHED_TOKEN_KEYS = ["HALFCYCLE_TOKEN", "GUARD_SERVICE_TOKEN"];
|
|
@@ -17260,7 +17279,7 @@ function composeEngagementEnvPath(engagementId, home) {
|
|
|
17260
17279
|
return join5(home ?? homedir2(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, ENGAGEMENT_ENV_FILENAME);
|
|
17261
17280
|
}
|
|
17262
17281
|
function hasNotThisAccountMarker(engagementId, home) {
|
|
17263
|
-
return
|
|
17282
|
+
return existsSync3(join5(home ?? homedir2(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, NOT_THIS_ACCOUNT_MARKER_FILENAME));
|
|
17264
17283
|
}
|
|
17265
17284
|
function readAccountCredential(controlOrigin, home) {
|
|
17266
17285
|
let raw;
|
|
@@ -17450,10 +17469,143 @@ async function evaluateWithRefresh(opts) {
|
|
|
17450
17469
|
return { result: retry, token: refreshed.token, remedy: HOOK_REMEDY };
|
|
17451
17470
|
}
|
|
17452
17471
|
|
|
17472
|
+
// dist/hooks/marker.js
|
|
17473
|
+
import { createHash } from "node:crypto";
|
|
17474
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync } from "node:fs";
|
|
17475
|
+
import { tmpdir } from "node:os";
|
|
17476
|
+
import { join as join6 } from "node:path";
|
|
17477
|
+
var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
17478
|
+
var MARKER_PREFIX = "halfcycle-session-";
|
|
17479
|
+
var MARKER_SUFFIX = ".ref";
|
|
17480
|
+
var DEDUPE_SUFFIX = ".eval";
|
|
17481
|
+
function markerDir() {
|
|
17482
|
+
const raw = process.env["TMPDIR"] ?? tmpdir();
|
|
17483
|
+
return raw.replace(/\/+$/, "");
|
|
17484
|
+
}
|
|
17485
|
+
function repoHash(repoRoot) {
|
|
17486
|
+
return createHash("sha256").update(repoRoot).digest("hex").slice(0, 12);
|
|
17487
|
+
}
|
|
17488
|
+
function markerFileName(repoRoot, sessionId) {
|
|
17489
|
+
const hash2 = repoHash(repoRoot);
|
|
17490
|
+
const key = sessionId && sessionId.length > 0 ? `${hash2}-${sessionId}` : hash2;
|
|
17491
|
+
return `${MARKER_PREFIX}${key}${MARKER_SUFFIX}`;
|
|
17492
|
+
}
|
|
17493
|
+
function markerPath(repoRoot, sessionId) {
|
|
17494
|
+
return join6(markerDir(), markerFileName(repoRoot, sessionId));
|
|
17495
|
+
}
|
|
17496
|
+
function dedupePath(repoRoot, sessionId) {
|
|
17497
|
+
const hash2 = repoHash(repoRoot);
|
|
17498
|
+
const key = sessionId && sessionId.length > 0 ? `${hash2}-${sessionId}` : hash2;
|
|
17499
|
+
return join6(markerDir(), `${MARKER_PREFIX}${key}${DEDUPE_SUFFIX}`);
|
|
17500
|
+
}
|
|
17501
|
+
function readMarker(repoRoot, sessionId) {
|
|
17502
|
+
const path = markerPath(repoRoot, sessionId);
|
|
17503
|
+
if (!existsSync4(path))
|
|
17504
|
+
return null;
|
|
17505
|
+
try {
|
|
17506
|
+
return readFileSync6(path, "utf-8").trim() || null;
|
|
17507
|
+
} catch {
|
|
17508
|
+
return null;
|
|
17509
|
+
}
|
|
17510
|
+
}
|
|
17511
|
+
function deleteMarker(repoRoot, sessionId) {
|
|
17512
|
+
for (const path of [markerPath(repoRoot, sessionId), dedupePath(repoRoot, sessionId)]) {
|
|
17513
|
+
try {
|
|
17514
|
+
if (existsSync4(path))
|
|
17515
|
+
unlinkSync3(path);
|
|
17516
|
+
} catch {
|
|
17517
|
+
}
|
|
17518
|
+
}
|
|
17519
|
+
}
|
|
17520
|
+
var NO_REPO_NOTICE_SUFFIX = ".norepo";
|
|
17521
|
+
function firstNoRepoNoticeThisSession(folder, sessionId) {
|
|
17522
|
+
if (!sessionId)
|
|
17523
|
+
return true;
|
|
17524
|
+
const path = join6(markerDir(), `${MARKER_PREFIX}${repoHash(folder)}-${sessionId}${NO_REPO_NOTICE_SUFFIX}`);
|
|
17525
|
+
try {
|
|
17526
|
+
writeFileSync3(path, "", { encoding: "utf-8", flag: "wx" });
|
|
17527
|
+
return true;
|
|
17528
|
+
} catch (err) {
|
|
17529
|
+
return err.code !== "EEXIST";
|
|
17530
|
+
}
|
|
17531
|
+
}
|
|
17532
|
+
function readDedupe(repoRoot, sessionId) {
|
|
17533
|
+
const path = dedupePath(repoRoot, sessionId);
|
|
17534
|
+
if (!existsSync4(path))
|
|
17535
|
+
return null;
|
|
17536
|
+
try {
|
|
17537
|
+
const parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
17538
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed.base === "string" && typeof parsed.digest === "string") {
|
|
17539
|
+
return parsed;
|
|
17540
|
+
}
|
|
17541
|
+
return null;
|
|
17542
|
+
} catch {
|
|
17543
|
+
return null;
|
|
17544
|
+
}
|
|
17545
|
+
}
|
|
17546
|
+
function writeDedupe(repoRoot, sessionId, state) {
|
|
17547
|
+
try {
|
|
17548
|
+
writeFileSync3(dedupePath(repoRoot, sessionId), JSON.stringify(state), "utf-8");
|
|
17549
|
+
} catch {
|
|
17550
|
+
}
|
|
17551
|
+
}
|
|
17552
|
+
function digestChangeState(base, entries) {
|
|
17553
|
+
const h = createHash("sha256");
|
|
17554
|
+
h.update(base);
|
|
17555
|
+
h.update("\0");
|
|
17556
|
+
for (const e of [...entries].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
17557
|
+
h.update(e.path);
|
|
17558
|
+
h.update("\0");
|
|
17559
|
+
h.update(e.diffContent);
|
|
17560
|
+
h.update("\0");
|
|
17561
|
+
}
|
|
17562
|
+
return h.digest("hex");
|
|
17563
|
+
}
|
|
17564
|
+
function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
|
|
17565
|
+
const dir = markerDir();
|
|
17566
|
+
const hash2 = repoHash(repoRoot);
|
|
17567
|
+
const keepName = keepSessionId ? markerFileName(repoRoot, keepSessionId) : markerFileName(repoRoot);
|
|
17568
|
+
let names;
|
|
17569
|
+
try {
|
|
17570
|
+
names = readdirSync3(dir);
|
|
17571
|
+
} catch {
|
|
17572
|
+
return;
|
|
17573
|
+
}
|
|
17574
|
+
for (const name of names) {
|
|
17575
|
+
if (!name.startsWith(`${MARKER_PREFIX}${hash2}`) || !name.endsWith(MARKER_SUFFIX))
|
|
17576
|
+
continue;
|
|
17577
|
+
if (name === keepName)
|
|
17578
|
+
continue;
|
|
17579
|
+
const full = join6(dir, name);
|
|
17580
|
+
let unusable = false;
|
|
17581
|
+
try {
|
|
17582
|
+
const ref = readFileSync6(full, "utf-8").trim();
|
|
17583
|
+
if (!ref || !refKnown(ref))
|
|
17584
|
+
unusable = true;
|
|
17585
|
+
} catch {
|
|
17586
|
+
unusable = true;
|
|
17587
|
+
}
|
|
17588
|
+
if (!unusable) {
|
|
17589
|
+
try {
|
|
17590
|
+
const age = now - statSync(full).mtimeMs;
|
|
17591
|
+
if (age > MARKER_TTL_MS)
|
|
17592
|
+
unusable = true;
|
|
17593
|
+
} catch {
|
|
17594
|
+
unusable = true;
|
|
17595
|
+
}
|
|
17596
|
+
}
|
|
17597
|
+
if (unusable) {
|
|
17598
|
+
const mid = name.slice(`${MARKER_PREFIX}${hash2}`.length, -MARKER_SUFFIX.length);
|
|
17599
|
+
const sid = mid.startsWith("-") ? mid.slice(1) : void 0;
|
|
17600
|
+
deleteMarker(repoRoot, sid);
|
|
17601
|
+
}
|
|
17602
|
+
}
|
|
17603
|
+
}
|
|
17604
|
+
|
|
17453
17605
|
// dist/seat-configuration.js
|
|
17454
|
-
import { existsSync as
|
|
17606
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7, realpathSync as realpathSync2 } from "node:fs";
|
|
17455
17607
|
import { homedir as homedir3 } from "node:os";
|
|
17456
|
-
import { join as
|
|
17608
|
+
import { join as join7, relative as relative3, resolve } from "node:path";
|
|
17457
17609
|
function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
|
|
17458
17610
|
try {
|
|
17459
17611
|
const facts = read(repoRoot);
|
|
@@ -17488,7 +17640,7 @@ function isPlainObject2(value) {
|
|
|
17488
17640
|
function readSettingsFile(projectDir, rel) {
|
|
17489
17641
|
let raw;
|
|
17490
17642
|
try {
|
|
17491
|
-
raw =
|
|
17643
|
+
raw = readFileSync7(join7(projectDir, rel), "utf-8");
|
|
17492
17644
|
} catch (err) {
|
|
17493
17645
|
const code = err.code;
|
|
17494
17646
|
const state = code === "ENOENT" || code === "ENOTDIR" ? "absent" : "unreadable";
|
|
@@ -17560,7 +17712,7 @@ function readPathStates(projectDir, repoRoot) {
|
|
|
17560
17712
|
const status = getWiringStatus(HALFCYCLE_HOOK_WIRING_PATHS, projectDir);
|
|
17561
17713
|
if (!status.ok)
|
|
17562
17714
|
return unknown2();
|
|
17563
|
-
const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) =>
|
|
17715
|
+
const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) => existsSync5(join7(projectDir, rel)));
|
|
17564
17716
|
return mapped ?? unknown2();
|
|
17565
17717
|
}
|
|
17566
17718
|
function mapWiringStatus(output, prefix, paths, onDisk) {
|
|
@@ -17625,7 +17777,9 @@ async function evaluateEdit(reply, readSeat) {
|
|
|
17625
17777
|
return;
|
|
17626
17778
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
17627
17779
|
if (!repoRootResult.ok) {
|
|
17628
|
-
|
|
17780
|
+
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
17781
|
+
reply.notice(noRepositoryText(repoRootResult));
|
|
17782
|
+
}
|
|
17629
17783
|
return;
|
|
17630
17784
|
}
|
|
17631
17785
|
const repoRoot = repoRootResult.value;
|
|
@@ -17740,127 +17894,6 @@ function readStdin() {
|
|
|
17740
17894
|
});
|
|
17741
17895
|
}
|
|
17742
17896
|
|
|
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
17897
|
// dist/hooks/session-diff.js
|
|
17865
17898
|
var STOP_HOOK_EVENT = {
|
|
17866
17899
|
stop: "Stop",
|
|
@@ -17904,7 +17937,9 @@ async function runSessionDiff(input, kind, readSeat = readSeatConfiguration) {
|
|
|
17904
17937
|
async function evaluateSession(input, kind, reply, readSeat) {
|
|
17905
17938
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
17906
17939
|
if (!repoRootResult.ok) {
|
|
17907
|
-
|
|
17940
|
+
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
17941
|
+
reply.notice(noRepositoryText(repoRootResult));
|
|
17942
|
+
}
|
|
17908
17943
|
return;
|
|
17909
17944
|
}
|
|
17910
17945
|
const repoRoot = repoRootResult.value;
|
|
@@ -18079,14 +18114,14 @@ async function runStop() {
|
|
|
18079
18114
|
}
|
|
18080
18115
|
|
|
18081
18116
|
// dist/resolve-engagement.js
|
|
18082
|
-
import { existsSync as
|
|
18117
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
|
|
18083
18118
|
import { join as join8, sep as sep2 } from "node:path";
|
|
18084
18119
|
import { homedir as homedir4 } from "node:os";
|
|
18085
18120
|
function defaultRegistryPath() {
|
|
18086
18121
|
return join8(homedir4(), ".halfcycle", "registry.json");
|
|
18087
18122
|
}
|
|
18088
18123
|
function readRegistry(registryPath = defaultRegistryPath()) {
|
|
18089
|
-
if (!
|
|
18124
|
+
if (!existsSync6(registryPath))
|
|
18090
18125
|
return {};
|
|
18091
18126
|
try {
|
|
18092
18127
|
const parsed = JSON.parse(readFileSync8(registryPath, "utf-8"));
|
|
@@ -18376,6 +18411,7 @@ async function refreshCredentialToken(opts) {
|
|
|
18376
18411
|
return { token: refreshed.token };
|
|
18377
18412
|
}
|
|
18378
18413
|
var CHECKED_SOURCES = /* @__PURE__ */ new Set(["startup", "resume"]);
|
|
18414
|
+
var CONNECTED_SENTENCE = `Halfcycle's sign-in works and its service answered. If Halfcycle's tools are missing, turn on ${MCP_SERVER_KEY} in /mcp; if /mcp does not list it, run "claude mcp reset-project-choices" in this folder and restart Claude Code.`;
|
|
18379
18415
|
var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} });
|
|
18380
18416
|
function registeredMcpUrl(projectRoot) {
|
|
18381
18417
|
let parsed;
|
|
@@ -18461,7 +18497,8 @@ async function checkConnection(renewedToken, opts) {
|
|
|
18461
18497
|
if (CREDENTIAL_PLACEHOLDERS.includes(bearer)) {
|
|
18462
18498
|
return PLACEHOLDER_SENTENCES[bearer];
|
|
18463
18499
|
}
|
|
18464
|
-
|
|
18500
|
+
const outcome = await probeDelivery(url2, bearer, opts.fetchFn ?? globalThis.fetch, opts.checkDeadlineMs ?? CONNECTION_CHECK_DEADLINE_MS);
|
|
18501
|
+
return outcome ?? CONNECTED_SENTENCE;
|
|
18465
18502
|
}
|
|
18466
18503
|
function defaultWriteErr2(text) {
|
|
18467
18504
|
process.stderr.write(text);
|
|
@@ -18489,8 +18526,11 @@ async function runRefreshCredential(opts) {
|
|
|
18489
18526
|
const remedySaid = parts.some(namesTheSignInCommand);
|
|
18490
18527
|
parts.push(noCoverageStatement(readNeverConfiguredReason(), remedySaid));
|
|
18491
18528
|
}
|
|
18492
|
-
for (const said of renewalSaid)
|
|
18529
|
+
for (const said of renewalSaid) {
|
|
18530
|
+
if (namesTheSignInCommand(said) && parts.some(namesTheSignInCommand))
|
|
18531
|
+
continue;
|
|
18493
18532
|
parts.push(said.trimEnd());
|
|
18533
|
+
}
|
|
18494
18534
|
return parts.length === 0 ? "" : `${nonBlockingReply("SessionStart", parts.join("\n"))}
|
|
18495
18535
|
`;
|
|
18496
18536
|
} catch (err) {
|
|
@@ -18508,7 +18548,7 @@ async function runRefreshCredential(opts) {
|
|
|
18508
18548
|
import { fileURLToPath } from "node:url";
|
|
18509
18549
|
|
|
18510
18550
|
// dist/capture.js
|
|
18511
|
-
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as
|
|
18551
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "node:fs";
|
|
18512
18552
|
import { dirname as dirname5 } from "node:path";
|
|
18513
18553
|
function runCapture(provider, endpoint, opts = {}) {
|
|
18514
18554
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
@@ -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.
|