halfcycle 0.3.30 → 0.3.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +377 -65
- package/dist/bin.js +212 -108
- package/dist/bin.js.map +3 -3
- package/dist/identity.d.ts +39 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/index.js +196 -102
- package/dist/index.js.map +3 -3
- package/dist/install.d.ts +60 -40
- package/dist/install.d.ts.map +1 -1
- package/package.json +1 -1
package/bin/bin.bundle.mjs
CHANGED
|
@@ -14652,6 +14652,75 @@ function parseAccountStoreText(raw) {
|
|
|
14652
14652
|
// ../core/dist/ci-audience.js
|
|
14653
14653
|
var HALFCYCLE_OIDC_AUDIENCE = "https://halfcycle.ai";
|
|
14654
14654
|
|
|
14655
|
+
// ../core/dist/hook-wiring.js
|
|
14656
|
+
var SETTINGS_REL = ".claude/settings.json";
|
|
14657
|
+
var LOCAL_SETTINGS_REL = ".claude/settings.local.json";
|
|
14658
|
+
var VENDORED_BIN_REL = ".halfcycle/bin/bin.bundle.mjs";
|
|
14659
|
+
var SESSION_START_MARKER_REL = ".claude/hooks/session-start-marker.sh";
|
|
14660
|
+
var GUARD_RUNNER_REL = ".claude/hooks/guard-runner.sh";
|
|
14661
|
+
var USER_PROMPT_REMINDER_REL = ".claude/hooks/user-prompt-reminder.sh";
|
|
14662
|
+
var SESSION_END_MARKER_REL = ".claude/hooks/session-end-marker.sh";
|
|
14663
|
+
var WRITE_CAPABLE_TOOLS = ["Edit", "MultiEdit", "NotebookEdit", "Write"];
|
|
14664
|
+
var HALFCYCLE_HOOK_WIRING = [
|
|
14665
|
+
{
|
|
14666
|
+
event: "SessionStart",
|
|
14667
|
+
matcher: null,
|
|
14668
|
+
timeout: 5,
|
|
14669
|
+
command: `bash ./${SESSION_START_MARKER_REL}`,
|
|
14670
|
+
script: SESSION_START_MARKER_REL
|
|
14671
|
+
},
|
|
14672
|
+
{
|
|
14673
|
+
event: "SessionStart",
|
|
14674
|
+
matcher: null,
|
|
14675
|
+
timeout: 10,
|
|
14676
|
+
command: `bash ./${GUARD_RUNNER_REL} refresh-credential`,
|
|
14677
|
+
script: GUARD_RUNNER_REL
|
|
14678
|
+
},
|
|
14679
|
+
{
|
|
14680
|
+
event: "UserPromptSubmit",
|
|
14681
|
+
matcher: null,
|
|
14682
|
+
timeout: 5,
|
|
14683
|
+
command: `bash ./${USER_PROMPT_REMINDER_REL}`,
|
|
14684
|
+
script: USER_PROMPT_REMINDER_REL
|
|
14685
|
+
},
|
|
14686
|
+
{
|
|
14687
|
+
event: "PostToolUse",
|
|
14688
|
+
matcher: WRITE_CAPABLE_TOOLS.join("|"),
|
|
14689
|
+
timeout: 60,
|
|
14690
|
+
command: `bash ./${GUARD_RUNNER_REL} hook post-tool-use`,
|
|
14691
|
+
script: GUARD_RUNNER_REL
|
|
14692
|
+
},
|
|
14693
|
+
{
|
|
14694
|
+
event: "Stop",
|
|
14695
|
+
matcher: null,
|
|
14696
|
+
timeout: 60,
|
|
14697
|
+
command: `bash ./${GUARD_RUNNER_REL} hook stop`,
|
|
14698
|
+
script: GUARD_RUNNER_REL
|
|
14699
|
+
},
|
|
14700
|
+
{
|
|
14701
|
+
event: "SubagentStop",
|
|
14702
|
+
matcher: null,
|
|
14703
|
+
timeout: 60,
|
|
14704
|
+
command: `bash ./${GUARD_RUNNER_REL} hook subagent-stop`,
|
|
14705
|
+
script: GUARD_RUNNER_REL
|
|
14706
|
+
},
|
|
14707
|
+
{
|
|
14708
|
+
event: "SessionEnd",
|
|
14709
|
+
matcher: null,
|
|
14710
|
+
timeout: 5,
|
|
14711
|
+
command: `bash ./${SESSION_END_MARKER_REL}`,
|
|
14712
|
+
script: SESSION_END_MARKER_REL
|
|
14713
|
+
}
|
|
14714
|
+
];
|
|
14715
|
+
var RETIRED_HOOK_COMMANDS = [];
|
|
14716
|
+
var HALFCYCLE_HOOK_COMMANDS = /* @__PURE__ */ new Set([
|
|
14717
|
+
...HALFCYCLE_HOOK_WIRING.map((entry) => entry.command),
|
|
14718
|
+
...RETIRED_HOOK_COMMANDS
|
|
14719
|
+
]);
|
|
14720
|
+
var HALFCYCLE_HOOK_WIRING_PATHS = [
|
|
14721
|
+
.../* @__PURE__ */ new Set([SETTINGS_REL, ...HALFCYCLE_HOOK_WIRING.map((entry) => entry.script), VENDORED_BIN_REL])
|
|
14722
|
+
];
|
|
14723
|
+
|
|
14655
14724
|
// dist/control-origin.js
|
|
14656
14725
|
var DEFAULT_CONTROL_ORIGIN = "https://control.halfcycle.ai";
|
|
14657
14726
|
function resolveControlOrigin(env) {
|
|
@@ -14809,6 +14878,25 @@ var CLIENT_CAPABILITY_VOCABULARY = Object.keys(diffMetadataSchema.shape).filter(
|
|
|
14809
14878
|
function isClientCapability(value) {
|
|
14810
14879
|
return CLIENT_CAPABILITY_VOCABULARY.includes(value);
|
|
14811
14880
|
}
|
|
14881
|
+
var seatHookSchema = external_exports.object({
|
|
14882
|
+
event: external_exports.string(),
|
|
14883
|
+
matcher: external_exports.string().nullable(),
|
|
14884
|
+
command: external_exports.string(),
|
|
14885
|
+
timeout: external_exports.number().nullable()
|
|
14886
|
+
}).strict();
|
|
14887
|
+
var seatSettingsFileSchema = external_exports.object({
|
|
14888
|
+
path: external_exports.string(),
|
|
14889
|
+
state: external_exports.enum(["read", "absent", "unreadable"]),
|
|
14890
|
+
hooks: external_exports.array(seatHookSchema)
|
|
14891
|
+
}).strict();
|
|
14892
|
+
var seatPathSchema = external_exports.object({
|
|
14893
|
+
path: external_exports.string(),
|
|
14894
|
+
state: external_exports.enum(["clean", "modified", "added", "deleted", "untracked", "ignored", "absent", "unknown"])
|
|
14895
|
+
}).strict();
|
|
14896
|
+
var seatConfigurationSchema = external_exports.object({
|
|
14897
|
+
settingsFiles: external_exports.array(seatSettingsFileSchema),
|
|
14898
|
+
paths: external_exports.array(seatPathSchema)
|
|
14899
|
+
}).strict();
|
|
14812
14900
|
var evaluationRequestSchema = external_exports.object({
|
|
14813
14901
|
engagementId: external_exports.string(),
|
|
14814
14902
|
changeSet: changeSetSchema,
|
|
@@ -14829,7 +14917,17 @@ var evaluationRequestSchema = external_exports.object({
|
|
|
14829
14917
|
*
|
|
14830
14918
|
* Omitting the field means "unknown": every check runs as it always has.
|
|
14831
14919
|
*/
|
|
14832
|
-
repoHasDeployFiles: external_exports.boolean().optional()
|
|
14920
|
+
repoHasDeployFiles: external_exports.boolean().optional(),
|
|
14921
|
+
/**
|
|
14922
|
+
* The hooks this tool installed that this project's own settings files
|
|
14923
|
+
* register, each with the event, matcher and timeout it is registered
|
|
14924
|
+
* under, and whether each file those hooks rely on is committed and
|
|
14925
|
+
* unchanged. Sent as found: the service decides what it means, and
|
|
14926
|
+
* nothing about it is sent back.
|
|
14927
|
+
*
|
|
14928
|
+
* Omitting the field means "not observed", never "set up".
|
|
14929
|
+
*/
|
|
14930
|
+
seatConfiguration: seatConfigurationSchema.optional()
|
|
14833
14931
|
}).strict();
|
|
14834
14932
|
|
|
14835
14933
|
// ../events/dist/result.js
|
|
@@ -15147,21 +15245,49 @@ function emitBlock(reason) {
|
|
|
15147
15245
|
process.stdout.write(JSON.stringify({ decision: "block", reason }) + "\n");
|
|
15148
15246
|
process.exit(0);
|
|
15149
15247
|
}
|
|
15150
|
-
function
|
|
15151
|
-
|
|
15248
|
+
function nonBlockingReply(event, text) {
|
|
15249
|
+
if (event === "PostToolUse") {
|
|
15250
|
+
return JSON.stringify({
|
|
15251
|
+
systemMessage: text,
|
|
15252
|
+
hookSpecificOutput: { hookEventName: event, additionalContext: text }
|
|
15253
|
+
});
|
|
15254
|
+
}
|
|
15255
|
+
return JSON.stringify({ systemMessage: text });
|
|
15152
15256
|
}
|
|
15153
|
-
function
|
|
15154
|
-
|
|
15257
|
+
function hookReply(event) {
|
|
15258
|
+
const queued = [];
|
|
15259
|
+
return {
|
|
15260
|
+
notice(text) {
|
|
15261
|
+
queued.push(text);
|
|
15262
|
+
},
|
|
15263
|
+
block(reason) {
|
|
15264
|
+
const carried = queued.splice(0);
|
|
15265
|
+
return emitBlock(carried.length === 0 ? reason : [reason, ...carried].join("\n"));
|
|
15266
|
+
},
|
|
15267
|
+
flush() {
|
|
15268
|
+
if (queued.length === 0)
|
|
15269
|
+
return;
|
|
15270
|
+
const text = queued.splice(0).join("\n");
|
|
15271
|
+
const code = process.exitCode;
|
|
15272
|
+
if (code !== void 0 && code !== null && Number(code) !== 0) {
|
|
15273
|
+
writeSync(2, `${text}
|
|
15155
15274
|
`);
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15275
|
+
return;
|
|
15276
|
+
}
|
|
15277
|
+
process.stdout.write(`${nonBlockingReply(event, text)}
|
|
15159
15278
|
`);
|
|
15279
|
+
}
|
|
15280
|
+
};
|
|
15160
15281
|
}
|
|
15161
|
-
function
|
|
15162
|
-
|
|
15163
|
-
|
|
15164
|
-
|
|
15282
|
+
function infraWarningText(message) {
|
|
15283
|
+
return `[Halfcycle] WARNING: Guard service unavailable \u2014 ${message}. Session continues (fail-open).`;
|
|
15284
|
+
}
|
|
15285
|
+
function localFaultText(message) {
|
|
15286
|
+
return `[Halfcycle] WARNING: Local fault \u2014 ${message}. The guard service was not contacted. Session continues (fail-open).`;
|
|
15287
|
+
}
|
|
15288
|
+
function contractFaultText(message) {
|
|
15289
|
+
return `[Halfcycle] WARNING: Guard response could not be understood \u2014 ${message}
|
|
15290
|
+
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).`;
|
|
15165
15291
|
}
|
|
15166
15292
|
var UNCONFIGURED_EXIT_TOOL_USE = 2;
|
|
15167
15293
|
var UNCONFIGURED_EXIT_STOP_FAMILY = 1;
|
|
@@ -15480,8 +15606,12 @@ async function attempt(url2, token, request, fetchFn, retry) {
|
|
|
15480
15606
|
import { spawnSync } from "node:child_process";
|
|
15481
15607
|
import { readFileSync } from "node:fs";
|
|
15482
15608
|
import { relative } from "node:path";
|
|
15483
|
-
function git(args2, cwd) {
|
|
15484
|
-
const result = spawnSync("git", args2, {
|
|
15609
|
+
function git(args2, cwd, timeoutMs) {
|
|
15610
|
+
const result = spawnSync("git", args2, {
|
|
15611
|
+
cwd,
|
|
15612
|
+
encoding: "utf-8",
|
|
15613
|
+
...timeoutMs === void 0 ? {} : { timeout: timeoutMs }
|
|
15614
|
+
});
|
|
15485
15615
|
if (result.error) {
|
|
15486
15616
|
return { ok: false, message: `git error: ${result.error.message}` };
|
|
15487
15617
|
}
|
|
@@ -15625,6 +15755,20 @@ function refExists(ref, repoRoot) {
|
|
|
15625
15755
|
const result = git(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], repoRoot);
|
|
15626
15756
|
return result.ok && result.value.trim().length > 0;
|
|
15627
15757
|
}
|
|
15758
|
+
var WIRING_STATUS_TIMEOUT_MS = 2e3;
|
|
15759
|
+
function getWiringStatus(pathspecs, cwd) {
|
|
15760
|
+
return git([
|
|
15761
|
+
"--no-optional-locks",
|
|
15762
|
+
"status",
|
|
15763
|
+
"--porcelain=v1",
|
|
15764
|
+
"-z",
|
|
15765
|
+
"--no-renames",
|
|
15766
|
+
"--ignored=traditional",
|
|
15767
|
+
"--untracked-files=all",
|
|
15768
|
+
"--",
|
|
15769
|
+
...pathspecs
|
|
15770
|
+
], cwd, WIRING_STATUS_TIMEOUT_MS);
|
|
15771
|
+
}
|
|
15628
15772
|
|
|
15629
15773
|
// dist/screen/secret-shape.js
|
|
15630
15774
|
var SECRET_SHAPE_PATTERNS = [
|
|
@@ -16586,13 +16730,14 @@ function deriveClientCapabilities(changeSet) {
|
|
|
16586
16730
|
}
|
|
16587
16731
|
return [...seen];
|
|
16588
16732
|
}
|
|
16589
|
-
function buildEvaluationRequest(engagementId, changeSet, context) {
|
|
16733
|
+
function buildEvaluationRequest(engagementId, changeSet, context, seatConfiguration) {
|
|
16590
16734
|
const hasDeployFiles = repoHasDeployFiles(context);
|
|
16591
16735
|
return {
|
|
16592
16736
|
engagementId,
|
|
16593
16737
|
changeSet,
|
|
16594
16738
|
clientCapabilities: deriveClientCapabilities(changeSet),
|
|
16595
|
-
...hasDeployFiles === void 0 ? {} : { repoHasDeployFiles: hasDeployFiles }
|
|
16739
|
+
...hasDeployFiles === void 0 ? {} : { repoHasDeployFiles: hasDeployFiles },
|
|
16740
|
+
...seatConfiguration === void 0 ? {} : { seatConfiguration }
|
|
16596
16741
|
};
|
|
16597
16742
|
}
|
|
16598
16743
|
function produceExcerpts(diffContent, descriptors, caps) {
|
|
@@ -17209,9 +17354,165 @@ async function evaluateWithRefresh(opts) {
|
|
|
17209
17354
|
return { result: retry, token: refreshed.token, remedy: HOOK_REMEDY };
|
|
17210
17355
|
}
|
|
17211
17356
|
|
|
17357
|
+
// dist/seat-configuration.js
|
|
17358
|
+
import { existsSync as existsSync2, readFileSync as readFileSync6, realpathSync as realpathSync2 } from "node:fs";
|
|
17359
|
+
import { homedir as homedir3 } from "node:os";
|
|
17360
|
+
import { join as join6, relative as relative3, resolve } from "node:path";
|
|
17361
|
+
function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
|
|
17362
|
+
try {
|
|
17363
|
+
const facts = read(repoRoot);
|
|
17364
|
+
if (facts === void 0)
|
|
17365
|
+
return void 0;
|
|
17366
|
+
const parsed = seatConfigurationSchema.safeParse(facts);
|
|
17367
|
+
return parsed.success ? parsed.data : void 0;
|
|
17368
|
+
} catch {
|
|
17369
|
+
return void 0;
|
|
17370
|
+
}
|
|
17371
|
+
}
|
|
17372
|
+
function readSeatConfiguration(repoRoot) {
|
|
17373
|
+
const given = resolve(process.env["CLAUDE_PROJECT_DIR"] || process.cwd());
|
|
17374
|
+
const projectDir = realpathSync2.native(given);
|
|
17375
|
+
const home = homedir3();
|
|
17376
|
+
let realHome = home;
|
|
17377
|
+
try {
|
|
17378
|
+
realHome = realpathSync2.native(home);
|
|
17379
|
+
} catch {
|
|
17380
|
+
realHome = home;
|
|
17381
|
+
}
|
|
17382
|
+
if (projectDir === home || given === home || projectDir === realHome)
|
|
17383
|
+
return void 0;
|
|
17384
|
+
return {
|
|
17385
|
+
settingsFiles: [readSettingsFile(projectDir, SETTINGS_REL), readSettingsFile(projectDir, LOCAL_SETTINGS_REL)],
|
|
17386
|
+
paths: readPathStates(projectDir, repoRoot)
|
|
17387
|
+
};
|
|
17388
|
+
}
|
|
17389
|
+
function isPlainObject2(value) {
|
|
17390
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17391
|
+
}
|
|
17392
|
+
function readSettingsFile(projectDir, rel) {
|
|
17393
|
+
let raw;
|
|
17394
|
+
try {
|
|
17395
|
+
raw = readFileSync6(join6(projectDir, rel), "utf-8");
|
|
17396
|
+
} catch (err) {
|
|
17397
|
+
const code = err.code;
|
|
17398
|
+
const state = code === "ENOENT" || code === "ENOTDIR" ? "absent" : "unreadable";
|
|
17399
|
+
return { path: rel, state, hooks: [] };
|
|
17400
|
+
}
|
|
17401
|
+
let parsed;
|
|
17402
|
+
try {
|
|
17403
|
+
parsed = JSON.parse(raw);
|
|
17404
|
+
} catch {
|
|
17405
|
+
return { path: rel, state: "unreadable", hooks: [] };
|
|
17406
|
+
}
|
|
17407
|
+
if (!isPlainObject2(parsed))
|
|
17408
|
+
return { path: rel, state: "unreadable", hooks: [] };
|
|
17409
|
+
return { path: rel, state: "read", hooks: ourHooks(parsed["hooks"]) };
|
|
17410
|
+
}
|
|
17411
|
+
function ourHooks(hooks) {
|
|
17412
|
+
if (!isPlainObject2(hooks))
|
|
17413
|
+
return [];
|
|
17414
|
+
const rows = [];
|
|
17415
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17416
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
17417
|
+
if (!Array.isArray(entries))
|
|
17418
|
+
continue;
|
|
17419
|
+
for (const entry of entries) {
|
|
17420
|
+
if (!isPlainObject2(entry))
|
|
17421
|
+
continue;
|
|
17422
|
+
let matcher = null;
|
|
17423
|
+
if (Object.hasOwn(entry, "matcher")) {
|
|
17424
|
+
if (typeof entry["matcher"] !== "string")
|
|
17425
|
+
continue;
|
|
17426
|
+
matcher = entry["matcher"];
|
|
17427
|
+
}
|
|
17428
|
+
const commands = entry["hooks"];
|
|
17429
|
+
if (!Array.isArray(commands))
|
|
17430
|
+
continue;
|
|
17431
|
+
for (const hook of commands) {
|
|
17432
|
+
if (!isPlainObject2(hook))
|
|
17433
|
+
continue;
|
|
17434
|
+
const command = hook["command"];
|
|
17435
|
+
if (typeof command !== "string" || !HALFCYCLE_HOOK_COMMANDS.has(command))
|
|
17436
|
+
continue;
|
|
17437
|
+
let timeout = null;
|
|
17438
|
+
if (Object.hasOwn(hook, "timeout")) {
|
|
17439
|
+
const value = hook["timeout"];
|
|
17440
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
17441
|
+
continue;
|
|
17442
|
+
timeout = value;
|
|
17443
|
+
}
|
|
17444
|
+
const key = JSON.stringify([event, matcher, command, timeout]);
|
|
17445
|
+
if (seen.has(key))
|
|
17446
|
+
continue;
|
|
17447
|
+
seen.add(key);
|
|
17448
|
+
rows.push({ event, matcher, command, timeout });
|
|
17449
|
+
}
|
|
17450
|
+
}
|
|
17451
|
+
}
|
|
17452
|
+
return rows;
|
|
17453
|
+
}
|
|
17454
|
+
function readPathStates(projectDir, repoRoot) {
|
|
17455
|
+
const unknown2 = () => HALFCYCLE_HOOK_WIRING_PATHS.map((path) => ({ path, state: "unknown" }));
|
|
17456
|
+
let projectTop = projectDir;
|
|
17457
|
+
if (repoRoot !== projectDir) {
|
|
17458
|
+
const top = getRepoRoot(projectDir);
|
|
17459
|
+
if (!top.ok)
|
|
17460
|
+
return unknown2();
|
|
17461
|
+
projectTop = top.value;
|
|
17462
|
+
}
|
|
17463
|
+
const prefix = relative3(projectTop, projectDir);
|
|
17464
|
+
const status = getWiringStatus(HALFCYCLE_HOOK_WIRING_PATHS, projectDir);
|
|
17465
|
+
if (!status.ok)
|
|
17466
|
+
return unknown2();
|
|
17467
|
+
const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) => existsSync2(join6(projectDir, rel)));
|
|
17468
|
+
return mapped ?? unknown2();
|
|
17469
|
+
}
|
|
17470
|
+
function mapWiringStatus(output, prefix, paths, onDisk) {
|
|
17471
|
+
const byGitPath = /* @__PURE__ */ new Map();
|
|
17472
|
+
for (const rel of paths)
|
|
17473
|
+
byGitPath.set(prefix === "" ? rel : `${prefix}/${rel}`, rel);
|
|
17474
|
+
const codes = /* @__PURE__ */ new Map();
|
|
17475
|
+
if (output !== "") {
|
|
17476
|
+
if (!output.endsWith("\0"))
|
|
17477
|
+
return null;
|
|
17478
|
+
for (const record2 of output.slice(0, -1).split("\0")) {
|
|
17479
|
+
if (record2.length < 4 || record2[2] !== " ")
|
|
17480
|
+
return null;
|
|
17481
|
+
const rel = byGitPath.get(record2.slice(3));
|
|
17482
|
+
if (rel === void 0)
|
|
17483
|
+
return null;
|
|
17484
|
+
const xy = record2.slice(0, 2);
|
|
17485
|
+
const existing = codes.get(rel);
|
|
17486
|
+
if (existing)
|
|
17487
|
+
existing.push(xy);
|
|
17488
|
+
else
|
|
17489
|
+
codes.set(rel, [xy]);
|
|
17490
|
+
}
|
|
17491
|
+
}
|
|
17492
|
+
return paths.map((path) => {
|
|
17493
|
+
const xys = codes.get(path);
|
|
17494
|
+
if (!xys)
|
|
17495
|
+
return { path, state: onDisk(path) ? "clean" : "absent" };
|
|
17496
|
+
if (xys.some((xy) => xy.includes("D")))
|
|
17497
|
+
return { path, state: "deleted" };
|
|
17498
|
+
if (xys.includes("??"))
|
|
17499
|
+
return { path, state: "untracked" };
|
|
17500
|
+
if (xys.includes("!!"))
|
|
17501
|
+
return { path, state: "ignored" };
|
|
17502
|
+
if (xys.some((xy) => xy[0] === "A"))
|
|
17503
|
+
return { path, state: "added" };
|
|
17504
|
+
return { path, state: "modified" };
|
|
17505
|
+
});
|
|
17506
|
+
}
|
|
17507
|
+
|
|
17212
17508
|
// dist/hooks/post-tool-use.js
|
|
17213
17509
|
var HANDLED_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
17214
|
-
async function runPostToolUse() {
|
|
17510
|
+
async function runPostToolUse(readSeat = readSeatConfiguration) {
|
|
17511
|
+
const reply = hookReply("PostToolUse");
|
|
17512
|
+
await evaluateEdit(reply, readSeat);
|
|
17513
|
+
reply.flush();
|
|
17514
|
+
}
|
|
17515
|
+
async function evaluateEdit(reply, readSeat) {
|
|
17215
17516
|
let input;
|
|
17216
17517
|
try {
|
|
17217
17518
|
const raw = await readStdin();
|
|
@@ -17228,7 +17529,7 @@ async function runPostToolUse() {
|
|
|
17228
17529
|
return;
|
|
17229
17530
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
17230
17531
|
if (!repoRootResult.ok) {
|
|
17231
|
-
|
|
17532
|
+
reply.notice(localFaultText(`Could not find git repo root: ${repoRootResult.message}`));
|
|
17232
17533
|
return;
|
|
17233
17534
|
}
|
|
17234
17535
|
const repoRoot = repoRootResult.value;
|
|
@@ -17249,11 +17550,12 @@ async function runPostToolUse() {
|
|
|
17249
17550
|
return;
|
|
17250
17551
|
}
|
|
17251
17552
|
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
17553
|
+
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat);
|
|
17252
17554
|
const evaluation = await evaluateWithRefresh({
|
|
17253
17555
|
guardServiceUrl,
|
|
17254
17556
|
token: guardServiceToken,
|
|
17255
17557
|
engagementId: guardEngagementId,
|
|
17256
|
-
request: buildEvaluationRequest(guardEngagementId, changeSet, context)
|
|
17558
|
+
request: buildEvaluationRequest(guardEngagementId, changeSet, context, seatConfiguration)
|
|
17257
17559
|
});
|
|
17258
17560
|
const clientResult = evaluation.result;
|
|
17259
17561
|
if (!clientResult.ok) {
|
|
@@ -17280,10 +17582,10 @@ async function runPostToolUse() {
|
|
|
17280
17582
|
return;
|
|
17281
17583
|
}
|
|
17282
17584
|
if (failure.kind === "contract") {
|
|
17283
|
-
|
|
17585
|
+
reply.notice(contractFaultText(failure.message));
|
|
17284
17586
|
return;
|
|
17285
17587
|
}
|
|
17286
|
-
|
|
17588
|
+
reply.notice(infraWarningText(failure.reason));
|
|
17287
17589
|
return;
|
|
17288
17590
|
}
|
|
17289
17591
|
const telemetryConfig = readTelemetryConfig();
|
|
@@ -17311,42 +17613,42 @@ async function runPostToolUse() {
|
|
|
17311
17613
|
}, {
|
|
17312
17614
|
warn: (msg) => process.stderr.write(msg + "\n")
|
|
17313
17615
|
});
|
|
17314
|
-
actOnEnvelope2(clientResult.envelope);
|
|
17616
|
+
actOnEnvelope2(clientResult.envelope, reply);
|
|
17315
17617
|
}
|
|
17316
|
-
function actOnEnvelope2(envelope) {
|
|
17618
|
+
function actOnEnvelope2(envelope, reply) {
|
|
17317
17619
|
const notRun = notRunReport(envelope.notRun);
|
|
17318
17620
|
const notRunBlock = notRun ? `
|
|
17319
17621
|
${notRun}` : "";
|
|
17320
17622
|
if (envelope.guardsFired.length === 0) {
|
|
17321
17623
|
if (notRun)
|
|
17322
|
-
|
|
17624
|
+
reply.notice(notRun);
|
|
17323
17625
|
return;
|
|
17324
17626
|
}
|
|
17325
17627
|
if (envelope.blocking) {
|
|
17326
17628
|
const guardLines = envelope.guardsFired.map((g) => ` [${g.severity.toUpperCase()}] ${g.patternRef}: ${g.explanation}`).join("\n");
|
|
17327
17629
|
const reason = `Guard blocked: ${envelope.explanation}
|
|
17328
17630
|
${guardLines}${notRunBlock}`;
|
|
17329
|
-
|
|
17631
|
+
reply.block(reason);
|
|
17330
17632
|
} else {
|
|
17331
17633
|
const guardLines = envelope.guardsFired.map((g) => ` [${g.severity.toUpperCase()}] ${g.patternRef}: ${g.explanation}`).join("\n");
|
|
17332
|
-
|
|
17634
|
+
reply.notice(`[Halfcycle] Guard warning: ${envelope.explanation}
|
|
17333
17635
|
${guardLines}${notRunBlock}`);
|
|
17334
17636
|
}
|
|
17335
17637
|
}
|
|
17336
17638
|
function readStdin() {
|
|
17337
|
-
return new Promise((
|
|
17639
|
+
return new Promise((resolve2, reject) => {
|
|
17338
17640
|
const chunks = [];
|
|
17339
17641
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
17340
|
-
process.stdin.on("end", () =>
|
|
17642
|
+
process.stdin.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
|
|
17341
17643
|
process.stdin.on("error", reject);
|
|
17342
17644
|
});
|
|
17343
17645
|
}
|
|
17344
17646
|
|
|
17345
17647
|
// dist/hooks/marker.js
|
|
17346
17648
|
import { createHash } from "node:crypto";
|
|
17347
|
-
import { existsSync as
|
|
17649
|
+
import { existsSync as existsSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync } from "node:fs";
|
|
17348
17650
|
import { tmpdir } from "node:os";
|
|
17349
|
-
import { join as
|
|
17651
|
+
import { join as join7 } from "node:path";
|
|
17350
17652
|
var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
17351
17653
|
var MARKER_PREFIX = "halfcycle-session-";
|
|
17352
17654
|
var MARKER_SUFFIX = ".ref";
|
|
@@ -17364,19 +17666,19 @@ function markerFileName(repoRoot, sessionId) {
|
|
|
17364
17666
|
return `${MARKER_PREFIX}${key}${MARKER_SUFFIX}`;
|
|
17365
17667
|
}
|
|
17366
17668
|
function markerPath(repoRoot, sessionId) {
|
|
17367
|
-
return
|
|
17669
|
+
return join7(markerDir(), markerFileName(repoRoot, sessionId));
|
|
17368
17670
|
}
|
|
17369
17671
|
function dedupePath(repoRoot, sessionId) {
|
|
17370
17672
|
const hash2 = repoHash(repoRoot);
|
|
17371
17673
|
const key = sessionId && sessionId.length > 0 ? `${hash2}-${sessionId}` : hash2;
|
|
17372
|
-
return
|
|
17674
|
+
return join7(markerDir(), `${MARKER_PREFIX}${key}${DEDUPE_SUFFIX}`);
|
|
17373
17675
|
}
|
|
17374
17676
|
function readMarker(repoRoot, sessionId) {
|
|
17375
17677
|
const path = markerPath(repoRoot, sessionId);
|
|
17376
|
-
if (!
|
|
17678
|
+
if (!existsSync3(path))
|
|
17377
17679
|
return null;
|
|
17378
17680
|
try {
|
|
17379
|
-
return
|
|
17681
|
+
return readFileSync7(path, "utf-8").trim() || null;
|
|
17380
17682
|
} catch {
|
|
17381
17683
|
return null;
|
|
17382
17684
|
}
|
|
@@ -17384,7 +17686,7 @@ function readMarker(repoRoot, sessionId) {
|
|
|
17384
17686
|
function deleteMarker(repoRoot, sessionId) {
|
|
17385
17687
|
for (const path of [markerPath(repoRoot, sessionId), dedupePath(repoRoot, sessionId)]) {
|
|
17386
17688
|
try {
|
|
17387
|
-
if (
|
|
17689
|
+
if (existsSync3(path))
|
|
17388
17690
|
unlinkSync3(path);
|
|
17389
17691
|
} catch {
|
|
17390
17692
|
}
|
|
@@ -17392,10 +17694,10 @@ function deleteMarker(repoRoot, sessionId) {
|
|
|
17392
17694
|
}
|
|
17393
17695
|
function readDedupe(repoRoot, sessionId) {
|
|
17394
17696
|
const path = dedupePath(repoRoot, sessionId);
|
|
17395
|
-
if (!
|
|
17697
|
+
if (!existsSync3(path))
|
|
17396
17698
|
return null;
|
|
17397
17699
|
try {
|
|
17398
|
-
const parsed = JSON.parse(
|
|
17700
|
+
const parsed = JSON.parse(readFileSync7(path, "utf-8"));
|
|
17399
17701
|
if (typeof parsed === "object" && parsed !== null && typeof parsed.base === "string" && typeof parsed.digest === "string") {
|
|
17400
17702
|
return parsed;
|
|
17401
17703
|
}
|
|
@@ -17437,10 +17739,10 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
|
|
|
17437
17739
|
continue;
|
|
17438
17740
|
if (name === keepName)
|
|
17439
17741
|
continue;
|
|
17440
|
-
const full =
|
|
17742
|
+
const full = join7(dir, name);
|
|
17441
17743
|
let unusable = false;
|
|
17442
17744
|
try {
|
|
17443
|
-
const ref =
|
|
17745
|
+
const ref = readFileSync7(full, "utf-8").trim();
|
|
17444
17746
|
if (!ref || !refKnown(ref))
|
|
17445
17747
|
unusable = true;
|
|
17446
17748
|
} catch {
|
|
@@ -17464,6 +17766,10 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
|
|
|
17464
17766
|
}
|
|
17465
17767
|
|
|
17466
17768
|
// dist/hooks/session-diff.js
|
|
17769
|
+
var STOP_HOOK_EVENT = {
|
|
17770
|
+
stop: "Stop",
|
|
17771
|
+
"subagent-stop": "SubagentStop"
|
|
17772
|
+
};
|
|
17467
17773
|
function resolveDiffBase2(repoRoot, sessionId) {
|
|
17468
17774
|
const ref = readMarker(repoRoot, sessionId);
|
|
17469
17775
|
if (ref === null)
|
|
@@ -17494,10 +17800,15 @@ function collectDiff(repoRoot, base) {
|
|
|
17494
17800
|
getDiff: (relPath) => getWorkingTreeFileDiff(relPath, repoRoot)
|
|
17495
17801
|
};
|
|
17496
17802
|
}
|
|
17497
|
-
async function runSessionDiff(input, kind) {
|
|
17803
|
+
async function runSessionDiff(input, kind, readSeat = readSeatConfiguration) {
|
|
17804
|
+
const reply = hookReply(STOP_HOOK_EVENT[kind]);
|
|
17805
|
+
await evaluateSession(input, kind, reply, readSeat);
|
|
17806
|
+
reply.flush();
|
|
17807
|
+
}
|
|
17808
|
+
async function evaluateSession(input, kind, reply, readSeat) {
|
|
17498
17809
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
17499
17810
|
if (!repoRootResult.ok) {
|
|
17500
|
-
|
|
17811
|
+
reply.notice(localFaultText(`Could not find git repo root: ${repoRootResult.message}`));
|
|
17501
17812
|
return;
|
|
17502
17813
|
}
|
|
17503
17814
|
const repoRoot = repoRootResult.value;
|
|
@@ -17507,7 +17818,7 @@ async function runSessionDiff(input, kind) {
|
|
|
17507
17818
|
const diffBase = base.kind;
|
|
17508
17819
|
const collected = collectDiff(repoRoot, base);
|
|
17509
17820
|
if (!collected.ok) {
|
|
17510
|
-
|
|
17821
|
+
reply.notice(localFaultText(`Could not get session diff: ${collected.message}`));
|
|
17511
17822
|
return;
|
|
17512
17823
|
}
|
|
17513
17824
|
const { getDiff } = collected;
|
|
@@ -17534,11 +17845,12 @@ async function runSessionDiff(input, kind) {
|
|
|
17534
17845
|
return;
|
|
17535
17846
|
}
|
|
17536
17847
|
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
17848
|
+
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat);
|
|
17537
17849
|
const evaluation = await evaluateWithRefresh({
|
|
17538
17850
|
guardServiceUrl,
|
|
17539
17851
|
token: guardServiceToken,
|
|
17540
17852
|
engagementId: guardEngagementId,
|
|
17541
|
-
request: buildEvaluationRequest(guardEngagementId, changeSet, context)
|
|
17853
|
+
request: buildEvaluationRequest(guardEngagementId, changeSet, context, seatConfiguration)
|
|
17542
17854
|
});
|
|
17543
17855
|
const clientResult = evaluation.result;
|
|
17544
17856
|
if (!clientResult.ok) {
|
|
@@ -17566,10 +17878,10 @@ async function runSessionDiff(input, kind) {
|
|
|
17566
17878
|
return;
|
|
17567
17879
|
}
|
|
17568
17880
|
if (failure.kind === "contract") {
|
|
17569
|
-
|
|
17881
|
+
reply.notice(contractFaultText(failure.message));
|
|
17570
17882
|
return;
|
|
17571
17883
|
}
|
|
17572
|
-
|
|
17884
|
+
reply.notice(infraWarningText(failure.reason));
|
|
17573
17885
|
return;
|
|
17574
17886
|
}
|
|
17575
17887
|
const telemetryConfig = readTelemetryConfig();
|
|
@@ -17595,28 +17907,28 @@ async function runSessionDiff(input, kind) {
|
|
|
17595
17907
|
warn: (msg) => process.stderr.write(msg + "\n")
|
|
17596
17908
|
});
|
|
17597
17909
|
writeDedupe(repoRoot, sessionId, { base: baseKey, digest });
|
|
17598
|
-
actOnEnvelope3(clientResult.envelope, kind, input.stop_hook_active === true);
|
|
17910
|
+
actOnEnvelope3(clientResult.envelope, kind, input.stop_hook_active === true, reply);
|
|
17599
17911
|
}
|
|
17600
|
-
function actOnEnvelope3(envelope, kind, neverBlock) {
|
|
17912
|
+
function actOnEnvelope3(envelope, kind, neverBlock, reply) {
|
|
17601
17913
|
const notRun = notRunReport(envelope.notRun);
|
|
17602
17914
|
const notRunBlock = notRun ? `
|
|
17603
17915
|
${notRun}` : "";
|
|
17604
17916
|
if (envelope.guardsFired.length === 0) {
|
|
17605
17917
|
if (notRun)
|
|
17606
|
-
|
|
17918
|
+
reply.notice(notRun);
|
|
17607
17919
|
return;
|
|
17608
17920
|
}
|
|
17609
17921
|
const where = kind === "stop" ? "session stop" : "session stop";
|
|
17610
17922
|
const guardLines = envelope.guardsFired.map((g) => ` [${g.severity.toUpperCase()}] ${g.patternRef}: ${g.explanation}`).join("\n");
|
|
17611
17923
|
if (envelope.blocking && !neverBlock) {
|
|
17612
|
-
|
|
17924
|
+
reply.block(`Guard blocked at ${where}: ${envelope.explanation}
|
|
17613
17925
|
${guardLines}${notRunBlock}`);
|
|
17614
17926
|
} else if (envelope.blocking && neverBlock) {
|
|
17615
|
-
|
|
17927
|
+
reply.notice(`[Halfcycle] Guard would block at ${where} but the stop was already continued (stop_hook_active) \u2014 surfacing as a warning:
|
|
17616
17928
|
${envelope.explanation}
|
|
17617
17929
|
${guardLines}${notRunBlock}`);
|
|
17618
17930
|
} else {
|
|
17619
|
-
|
|
17931
|
+
reply.notice(`[Halfcycle] Guard warning at ${where}: ${envelope.explanation}
|
|
17620
17932
|
${guardLines}${notRunBlock}`);
|
|
17621
17933
|
}
|
|
17622
17934
|
}
|
|
@@ -17636,9 +17948,9 @@ async function readHookStdin() {
|
|
|
17636
17948
|
}
|
|
17637
17949
|
}
|
|
17638
17950
|
function readStdin2() {
|
|
17639
|
-
return new Promise((
|
|
17951
|
+
return new Promise((resolve2) => {
|
|
17640
17952
|
if (process.stdin.isTTY) {
|
|
17641
|
-
|
|
17953
|
+
resolve2("");
|
|
17642
17954
|
return;
|
|
17643
17955
|
}
|
|
17644
17956
|
const chunks = [];
|
|
@@ -17648,7 +17960,7 @@ function readStdin2() {
|
|
|
17648
17960
|
return;
|
|
17649
17961
|
settled = true;
|
|
17650
17962
|
clearTimeout(timer);
|
|
17651
|
-
|
|
17963
|
+
resolve2(Buffer.concat(chunks).toString("utf-8"));
|
|
17652
17964
|
};
|
|
17653
17965
|
const timer = setTimeout(done, 2e3);
|
|
17654
17966
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -17670,17 +17982,17 @@ async function runStop() {
|
|
|
17670
17982
|
}
|
|
17671
17983
|
|
|
17672
17984
|
// dist/resolve-engagement.js
|
|
17673
|
-
import { existsSync as
|
|
17674
|
-
import { join as
|
|
17675
|
-
import { homedir as
|
|
17985
|
+
import { existsSync as existsSync4, readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
|
|
17986
|
+
import { join as join8, sep as sep2 } from "node:path";
|
|
17987
|
+
import { homedir as homedir4 } from "node:os";
|
|
17676
17988
|
function defaultRegistryPath() {
|
|
17677
|
-
return
|
|
17989
|
+
return join8(homedir4(), ".halfcycle", "registry.json");
|
|
17678
17990
|
}
|
|
17679
17991
|
function readRegistry(registryPath = defaultRegistryPath()) {
|
|
17680
|
-
if (!
|
|
17992
|
+
if (!existsSync4(registryPath))
|
|
17681
17993
|
return {};
|
|
17682
17994
|
try {
|
|
17683
|
-
const parsed = JSON.parse(
|
|
17995
|
+
const parsed = JSON.parse(readFileSync8(registryPath, "utf-8"));
|
|
17684
17996
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
17685
17997
|
return {};
|
|
17686
17998
|
const out = {};
|
|
@@ -17695,7 +18007,7 @@ function readRegistry(registryPath = defaultRegistryPath()) {
|
|
|
17695
18007
|
}
|
|
17696
18008
|
function canonicalise(p) {
|
|
17697
18009
|
try {
|
|
17698
|
-
return
|
|
18010
|
+
return realpathSync3(p);
|
|
17699
18011
|
} catch {
|
|
17700
18012
|
return p;
|
|
17701
18013
|
}
|
|
@@ -17760,7 +18072,7 @@ async function runRefreshCredential(opts) {
|
|
|
17760
18072
|
}
|
|
17761
18073
|
|
|
17762
18074
|
// dist/capture.js
|
|
17763
|
-
import { readFileSync as
|
|
18075
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "node:fs";
|
|
17764
18076
|
import { dirname as dirname4 } from "node:path";
|
|
17765
18077
|
function runCapture(provider, endpoint, opts = {}) {
|
|
17766
18078
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
@@ -17828,7 +18140,7 @@ function readInputFile(fromFile) {
|
|
|
17828
18140
|
if (!fromFile)
|
|
17829
18141
|
return void 0;
|
|
17830
18142
|
try {
|
|
17831
|
-
return
|
|
18143
|
+
return readFileSync9(fromFile, "utf-8");
|
|
17832
18144
|
} catch {
|
|
17833
18145
|
return void 0;
|
|
17834
18146
|
}
|
|
@@ -17923,10 +18235,10 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
17923
18235
|
process.exit(1);
|
|
17924
18236
|
}
|
|
17925
18237
|
function readStdin3() {
|
|
17926
|
-
return new Promise((
|
|
18238
|
+
return new Promise((resolve2, reject) => {
|
|
17927
18239
|
const chunks = [];
|
|
17928
18240
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
17929
|
-
process.stdin.on("end", () =>
|
|
18241
|
+
process.stdin.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
|
|
17930
18242
|
process.stdin.on("error", reject);
|
|
17931
18243
|
});
|
|
17932
18244
|
}
|