halfcycle 0.3.31 → 0.3.33
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 +355 -65
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +1336 -873
- package/dist/bin.js.map +3 -3
- package/dist/build-record/index.d.ts +14 -36
- package/dist/build-record/index.d.ts.map +1 -1
- package/dist/build-record/obtain.d.ts +81 -0
- package/dist/build-record/obtain.d.ts.map +1 -0
- package/dist/build-record/render-v2.d.ts +47 -0
- package/dist/build-record/render-v2.d.ts.map +1 -0
- package/dist/build-record/write.d.ts +77 -60
- package/dist/build-record/write.d.ts.map +1 -1
- package/dist/close-phase.d.ts +59 -16
- package/dist/close-phase.d.ts.map +1 -1
- package/dist/identity.d.ts +39 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/index.js +993 -671
- 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 +3 -1
- package/dist/build-record/assemble.d.ts +0 -37
- package/dist/build-record/assemble.d.ts.map +0 -1
- package/dist/build-record/close-record.d.ts +0 -124
- package/dist/build-record/close-record.d.ts.map +0 -1
- package/dist/build-record/sources.d.ts +0 -111
- package/dist/build-record/sources.d.ts.map +0 -1
- package/dist/build-record/template.d.ts +0 -29
- package/dist/build-record/template.d.ts.map +0 -1
- package/dist/build-record/types.d.ts +0 -200
- package/dist/build-record/types.d.ts.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halfcycle",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.33",
|
|
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/bin/bin.bundle.mjs
CHANGED
|
@@ -14652,6 +14652,100 @@ 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
|
+
|
|
14724
|
+
// ../core/dist/secret-shape.js
|
|
14725
|
+
var SECRET_SHAPE_PATTERNS = [
|
|
14726
|
+
// Assignment to a credential-named identifier: `apiKey = "..."`, `secret: '...'`,
|
|
14727
|
+
// `password=...`, `token = ...`, `access_key: ...` — the value need not be quoted.
|
|
14728
|
+
/\b(?:api[_-]?key|secret|password|passwd|token|access[_-]?key|private[_-]?key|client[_-]?secret|auth[_-]?token|bearer)\b\s*[:=]\s*\S+/i,
|
|
14729
|
+
// Connection strings that embed credentials: `scheme://user:pass@host`.
|
|
14730
|
+
/\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:[^\s:/@]+@/i,
|
|
14731
|
+
// Bearer / authorization header values.
|
|
14732
|
+
/\bauthorization\b\s*[:=]\s*['"]?\s*bearer\s+\S+/i,
|
|
14733
|
+
// PEM / private-key block headers.
|
|
14734
|
+
/-----BEGIN\s+(?:RSA\s+|EC\s+|OPENSSH\s+|DSA\s+)?PRIVATE\s+KEY-----/,
|
|
14735
|
+
// JWT shape: three base64url segments separated by dots.
|
|
14736
|
+
/\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/,
|
|
14737
|
+
// A long high-entropy hex/base64 run adjacent to a credential-ish word on the
|
|
14738
|
+
// same line — a raw key literal. Kept last (broadest, most likely false-drop).
|
|
14739
|
+
/\b(?:key|token|secret|credential)\b[\s\S]{0,40}[A-Za-z0-9+/_-]{32,}/i
|
|
14740
|
+
];
|
|
14741
|
+
function looksLikeSecret(text) {
|
|
14742
|
+
for (const pattern of SECRET_SHAPE_PATTERNS) {
|
|
14743
|
+
if (pattern.test(text))
|
|
14744
|
+
return true;
|
|
14745
|
+
}
|
|
14746
|
+
return false;
|
|
14747
|
+
}
|
|
14748
|
+
|
|
14655
14749
|
// dist/control-origin.js
|
|
14656
14750
|
var DEFAULT_CONTROL_ORIGIN = "https://control.halfcycle.ai";
|
|
14657
14751
|
function resolveControlOrigin(env) {
|
|
@@ -14809,6 +14903,25 @@ var CLIENT_CAPABILITY_VOCABULARY = Object.keys(diffMetadataSchema.shape).filter(
|
|
|
14809
14903
|
function isClientCapability(value) {
|
|
14810
14904
|
return CLIENT_CAPABILITY_VOCABULARY.includes(value);
|
|
14811
14905
|
}
|
|
14906
|
+
var seatHookSchema = external_exports.object({
|
|
14907
|
+
event: external_exports.string(),
|
|
14908
|
+
matcher: external_exports.string().nullable(),
|
|
14909
|
+
command: external_exports.string(),
|
|
14910
|
+
timeout: external_exports.number().nullable()
|
|
14911
|
+
}).strict();
|
|
14912
|
+
var seatSettingsFileSchema = external_exports.object({
|
|
14913
|
+
path: external_exports.string(),
|
|
14914
|
+
state: external_exports.enum(["read", "absent", "unreadable"]),
|
|
14915
|
+
hooks: external_exports.array(seatHookSchema)
|
|
14916
|
+
}).strict();
|
|
14917
|
+
var seatPathSchema = external_exports.object({
|
|
14918
|
+
path: external_exports.string(),
|
|
14919
|
+
state: external_exports.enum(["clean", "modified", "added", "deleted", "untracked", "ignored", "absent", "unknown"])
|
|
14920
|
+
}).strict();
|
|
14921
|
+
var seatConfigurationSchema = external_exports.object({
|
|
14922
|
+
settingsFiles: external_exports.array(seatSettingsFileSchema),
|
|
14923
|
+
paths: external_exports.array(seatPathSchema)
|
|
14924
|
+
}).strict();
|
|
14812
14925
|
var evaluationRequestSchema = external_exports.object({
|
|
14813
14926
|
engagementId: external_exports.string(),
|
|
14814
14927
|
changeSet: changeSetSchema,
|
|
@@ -14829,7 +14942,17 @@ var evaluationRequestSchema = external_exports.object({
|
|
|
14829
14942
|
*
|
|
14830
14943
|
* Omitting the field means "unknown": every check runs as it always has.
|
|
14831
14944
|
*/
|
|
14832
|
-
repoHasDeployFiles: external_exports.boolean().optional()
|
|
14945
|
+
repoHasDeployFiles: external_exports.boolean().optional(),
|
|
14946
|
+
/**
|
|
14947
|
+
* The hooks this tool installed that this project's own settings files
|
|
14948
|
+
* register, each with the event, matcher and timeout it is registered
|
|
14949
|
+
* under, and whether each file those hooks rely on is committed and
|
|
14950
|
+
* unchanged. Sent as found: the service decides what it means, and
|
|
14951
|
+
* nothing about it is sent back.
|
|
14952
|
+
*
|
|
14953
|
+
* Omitting the field means "not observed", never "set up".
|
|
14954
|
+
*/
|
|
14955
|
+
seatConfiguration: seatConfigurationSchema.optional()
|
|
14833
14956
|
}).strict();
|
|
14834
14957
|
|
|
14835
14958
|
// ../events/dist/result.js
|
|
@@ -15071,8 +15194,16 @@ var stateRecordSchema = external_exports.discriminatedUnion("recordKind", [
|
|
|
15071
15194
|
interventionRecordSchema
|
|
15072
15195
|
]);
|
|
15073
15196
|
|
|
15197
|
+
// ../events/dist/control-characters.js
|
|
15198
|
+
var CONTROL_CHARACTER_CLASS = "\\p{Cc}";
|
|
15199
|
+
var CONTROL_CHARACTER = new RegExp(CONTROL_CHARACTER_CLASS, "u");
|
|
15200
|
+
function hasControlCharacter(text) {
|
|
15201
|
+
return CONTROL_CHARACTER.test(text);
|
|
15202
|
+
}
|
|
15203
|
+
|
|
15074
15204
|
// ../events/dist/phase-identity.js
|
|
15075
15205
|
var PHASE_SEGMENT_PREFIX = "phase-";
|
|
15206
|
+
var PHASE_NAME_MAX_BYTES = 200;
|
|
15076
15207
|
function isValidPhaseIdentity(identity) {
|
|
15077
15208
|
if (typeof identity === "number")
|
|
15078
15209
|
return Number.isInteger(identity) && identity >= 0;
|
|
@@ -15084,12 +15215,22 @@ function isValidPhaseIdentity(identity) {
|
|
|
15084
15215
|
return false;
|
|
15085
15216
|
if (identity.includes("\\"))
|
|
15086
15217
|
return false;
|
|
15087
|
-
if (identity.includes("\0"))
|
|
15088
|
-
return false;
|
|
15089
15218
|
if (identity.startsWith("."))
|
|
15090
15219
|
return false;
|
|
15220
|
+
if (hasControlCharacter(identity))
|
|
15221
|
+
return false;
|
|
15222
|
+
if (utf8ByteLength(identity) > PHASE_NAME_MAX_BYTES)
|
|
15223
|
+
return false;
|
|
15091
15224
|
return true;
|
|
15092
15225
|
}
|
|
15226
|
+
function utf8ByteLength(text) {
|
|
15227
|
+
let bytes = 0;
|
|
15228
|
+
for (const ch of text) {
|
|
15229
|
+
const cp = ch.codePointAt(0);
|
|
15230
|
+
bytes += cp < 128 ? 1 : cp < 2048 ? 2 : cp < 65536 ? 3 : 4;
|
|
15231
|
+
}
|
|
15232
|
+
return bytes;
|
|
15233
|
+
}
|
|
15093
15234
|
var InvalidPhaseIdentityError = class extends Error {
|
|
15094
15235
|
/** The value that was refused, exactly as supplied. */
|
|
15095
15236
|
identity;
|
|
@@ -15508,8 +15649,12 @@ async function attempt(url2, token, request, fetchFn, retry) {
|
|
|
15508
15649
|
import { spawnSync } from "node:child_process";
|
|
15509
15650
|
import { readFileSync } from "node:fs";
|
|
15510
15651
|
import { relative } from "node:path";
|
|
15511
|
-
function git(args2, cwd) {
|
|
15512
|
-
const result = spawnSync("git", args2, {
|
|
15652
|
+
function git(args2, cwd, timeoutMs) {
|
|
15653
|
+
const result = spawnSync("git", args2, {
|
|
15654
|
+
cwd,
|
|
15655
|
+
encoding: "utf-8",
|
|
15656
|
+
...timeoutMs === void 0 ? {} : { timeout: timeoutMs }
|
|
15657
|
+
});
|
|
15513
15658
|
if (result.error) {
|
|
15514
15659
|
return { ok: false, message: `git error: ${result.error.message}` };
|
|
15515
15660
|
}
|
|
@@ -15653,31 +15798,22 @@ function refExists(ref, repoRoot) {
|
|
|
15653
15798
|
const result = git(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], repoRoot);
|
|
15654
15799
|
return result.ok && result.value.trim().length > 0;
|
|
15655
15800
|
}
|
|
15801
|
+
var WIRING_STATUS_TIMEOUT_MS = 2e3;
|
|
15802
|
+
function getWiringStatus(pathspecs, cwd) {
|
|
15803
|
+
return git([
|
|
15804
|
+
"--no-optional-locks",
|
|
15805
|
+
"status",
|
|
15806
|
+
"--porcelain=v1",
|
|
15807
|
+
"-z",
|
|
15808
|
+
"--no-renames",
|
|
15809
|
+
"--ignored=traditional",
|
|
15810
|
+
"--untracked-files=all",
|
|
15811
|
+
"--",
|
|
15812
|
+
...pathspecs
|
|
15813
|
+
], cwd, WIRING_STATUS_TIMEOUT_MS);
|
|
15814
|
+
}
|
|
15656
15815
|
|
|
15657
15816
|
// dist/screen/secret-shape.js
|
|
15658
|
-
var SECRET_SHAPE_PATTERNS = [
|
|
15659
|
-
// Assignment to a credential-named identifier: `apiKey = "..."`, `secret: '...'`,
|
|
15660
|
-
// `password=...`, `token = ...`, `access_key: ...` — the value need not be quoted.
|
|
15661
|
-
/\b(?:api[_-]?key|secret|password|passwd|token|access[_-]?key|private[_-]?key|client[_-]?secret|auth[_-]?token|bearer)\b\s*[:=]\s*\S+/i,
|
|
15662
|
-
// Connection strings that embed credentials: `scheme://user:pass@host`.
|
|
15663
|
-
/\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:[^\s:/@]+@/i,
|
|
15664
|
-
// Bearer / authorization header values.
|
|
15665
|
-
/\bauthorization\b\s*[:=]\s*['"]?\s*bearer\s+\S+/i,
|
|
15666
|
-
// PEM / private-key block headers.
|
|
15667
|
-
/-----BEGIN\s+(?:RSA\s+|EC\s+|OPENSSH\s+|DSA\s+)?PRIVATE\s+KEY-----/,
|
|
15668
|
-
// JWT shape: three base64url segments separated by dots.
|
|
15669
|
-
/\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/,
|
|
15670
|
-
// A long high-entropy hex/base64 run adjacent to a credential-ish word on the
|
|
15671
|
-
// same line — a raw key literal. Kept last (broadest, most likely false-drop).
|
|
15672
|
-
/\b(?:key|token|secret|credential)\b[\s\S]{0,40}[A-Za-z0-9+/_-]{32,}/i
|
|
15673
|
-
];
|
|
15674
|
-
function looksLikeSecret(text) {
|
|
15675
|
-
for (const pattern of SECRET_SHAPE_PATTERNS) {
|
|
15676
|
-
if (pattern.test(text))
|
|
15677
|
-
return true;
|
|
15678
|
-
}
|
|
15679
|
-
return false;
|
|
15680
|
-
}
|
|
15681
15817
|
function screenExcerpts(excerpts) {
|
|
15682
15818
|
return excerpts.filter((e) => !looksLikeSecret(e.text));
|
|
15683
15819
|
}
|
|
@@ -16614,13 +16750,14 @@ function deriveClientCapabilities(changeSet) {
|
|
|
16614
16750
|
}
|
|
16615
16751
|
return [...seen];
|
|
16616
16752
|
}
|
|
16617
|
-
function buildEvaluationRequest(engagementId, changeSet, context) {
|
|
16753
|
+
function buildEvaluationRequest(engagementId, changeSet, context, seatConfiguration) {
|
|
16618
16754
|
const hasDeployFiles = repoHasDeployFiles(context);
|
|
16619
16755
|
return {
|
|
16620
16756
|
engagementId,
|
|
16621
16757
|
changeSet,
|
|
16622
16758
|
clientCapabilities: deriveClientCapabilities(changeSet),
|
|
16623
|
-
...hasDeployFiles === void 0 ? {} : { repoHasDeployFiles: hasDeployFiles }
|
|
16759
|
+
...hasDeployFiles === void 0 ? {} : { repoHasDeployFiles: hasDeployFiles },
|
|
16760
|
+
...seatConfiguration === void 0 ? {} : { seatConfiguration }
|
|
16624
16761
|
};
|
|
16625
16762
|
}
|
|
16626
16763
|
function produceExcerpts(diffContent, descriptors, caps) {
|
|
@@ -17237,14 +17374,165 @@ async function evaluateWithRefresh(opts) {
|
|
|
17237
17374
|
return { result: retry, token: refreshed.token, remedy: HOOK_REMEDY };
|
|
17238
17375
|
}
|
|
17239
17376
|
|
|
17377
|
+
// dist/seat-configuration.js
|
|
17378
|
+
import { existsSync as existsSync2, readFileSync as readFileSync6, realpathSync as realpathSync2 } from "node:fs";
|
|
17379
|
+
import { homedir as homedir3 } from "node:os";
|
|
17380
|
+
import { join as join6, relative as relative3, resolve } from "node:path";
|
|
17381
|
+
function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
|
|
17382
|
+
try {
|
|
17383
|
+
const facts = read(repoRoot);
|
|
17384
|
+
if (facts === void 0)
|
|
17385
|
+
return void 0;
|
|
17386
|
+
const parsed = seatConfigurationSchema.safeParse(facts);
|
|
17387
|
+
return parsed.success ? parsed.data : void 0;
|
|
17388
|
+
} catch {
|
|
17389
|
+
return void 0;
|
|
17390
|
+
}
|
|
17391
|
+
}
|
|
17392
|
+
function readSeatConfiguration(repoRoot) {
|
|
17393
|
+
const given = resolve(process.env["CLAUDE_PROJECT_DIR"] || process.cwd());
|
|
17394
|
+
const projectDir = realpathSync2.native(given);
|
|
17395
|
+
const home = homedir3();
|
|
17396
|
+
let realHome = home;
|
|
17397
|
+
try {
|
|
17398
|
+
realHome = realpathSync2.native(home);
|
|
17399
|
+
} catch {
|
|
17400
|
+
realHome = home;
|
|
17401
|
+
}
|
|
17402
|
+
if (projectDir === home || given === home || projectDir === realHome)
|
|
17403
|
+
return void 0;
|
|
17404
|
+
return {
|
|
17405
|
+
settingsFiles: [readSettingsFile(projectDir, SETTINGS_REL), readSettingsFile(projectDir, LOCAL_SETTINGS_REL)],
|
|
17406
|
+
paths: readPathStates(projectDir, repoRoot)
|
|
17407
|
+
};
|
|
17408
|
+
}
|
|
17409
|
+
function isPlainObject2(value) {
|
|
17410
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17411
|
+
}
|
|
17412
|
+
function readSettingsFile(projectDir, rel) {
|
|
17413
|
+
let raw;
|
|
17414
|
+
try {
|
|
17415
|
+
raw = readFileSync6(join6(projectDir, rel), "utf-8");
|
|
17416
|
+
} catch (err) {
|
|
17417
|
+
const code = err.code;
|
|
17418
|
+
const state = code === "ENOENT" || code === "ENOTDIR" ? "absent" : "unreadable";
|
|
17419
|
+
return { path: rel, state, hooks: [] };
|
|
17420
|
+
}
|
|
17421
|
+
let parsed;
|
|
17422
|
+
try {
|
|
17423
|
+
parsed = JSON.parse(raw);
|
|
17424
|
+
} catch {
|
|
17425
|
+
return { path: rel, state: "unreadable", hooks: [] };
|
|
17426
|
+
}
|
|
17427
|
+
if (!isPlainObject2(parsed))
|
|
17428
|
+
return { path: rel, state: "unreadable", hooks: [] };
|
|
17429
|
+
return { path: rel, state: "read", hooks: ourHooks(parsed["hooks"]) };
|
|
17430
|
+
}
|
|
17431
|
+
function ourHooks(hooks) {
|
|
17432
|
+
if (!isPlainObject2(hooks))
|
|
17433
|
+
return [];
|
|
17434
|
+
const rows = [];
|
|
17435
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17436
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
17437
|
+
if (!Array.isArray(entries))
|
|
17438
|
+
continue;
|
|
17439
|
+
for (const entry of entries) {
|
|
17440
|
+
if (!isPlainObject2(entry))
|
|
17441
|
+
continue;
|
|
17442
|
+
let matcher = null;
|
|
17443
|
+
if (Object.hasOwn(entry, "matcher")) {
|
|
17444
|
+
if (typeof entry["matcher"] !== "string")
|
|
17445
|
+
continue;
|
|
17446
|
+
matcher = entry["matcher"];
|
|
17447
|
+
}
|
|
17448
|
+
const commands = entry["hooks"];
|
|
17449
|
+
if (!Array.isArray(commands))
|
|
17450
|
+
continue;
|
|
17451
|
+
for (const hook of commands) {
|
|
17452
|
+
if (!isPlainObject2(hook))
|
|
17453
|
+
continue;
|
|
17454
|
+
const command = hook["command"];
|
|
17455
|
+
if (typeof command !== "string" || !HALFCYCLE_HOOK_COMMANDS.has(command))
|
|
17456
|
+
continue;
|
|
17457
|
+
let timeout = null;
|
|
17458
|
+
if (Object.hasOwn(hook, "timeout")) {
|
|
17459
|
+
const value = hook["timeout"];
|
|
17460
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
17461
|
+
continue;
|
|
17462
|
+
timeout = value;
|
|
17463
|
+
}
|
|
17464
|
+
const key = JSON.stringify([event, matcher, command, timeout]);
|
|
17465
|
+
if (seen.has(key))
|
|
17466
|
+
continue;
|
|
17467
|
+
seen.add(key);
|
|
17468
|
+
rows.push({ event, matcher, command, timeout });
|
|
17469
|
+
}
|
|
17470
|
+
}
|
|
17471
|
+
}
|
|
17472
|
+
return rows;
|
|
17473
|
+
}
|
|
17474
|
+
function readPathStates(projectDir, repoRoot) {
|
|
17475
|
+
const unknown2 = () => HALFCYCLE_HOOK_WIRING_PATHS.map((path) => ({ path, state: "unknown" }));
|
|
17476
|
+
let projectTop = projectDir;
|
|
17477
|
+
if (repoRoot !== projectDir) {
|
|
17478
|
+
const top = getRepoRoot(projectDir);
|
|
17479
|
+
if (!top.ok)
|
|
17480
|
+
return unknown2();
|
|
17481
|
+
projectTop = top.value;
|
|
17482
|
+
}
|
|
17483
|
+
const prefix = relative3(projectTop, projectDir);
|
|
17484
|
+
const status = getWiringStatus(HALFCYCLE_HOOK_WIRING_PATHS, projectDir);
|
|
17485
|
+
if (!status.ok)
|
|
17486
|
+
return unknown2();
|
|
17487
|
+
const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) => existsSync2(join6(projectDir, rel)));
|
|
17488
|
+
return mapped ?? unknown2();
|
|
17489
|
+
}
|
|
17490
|
+
function mapWiringStatus(output, prefix, paths, onDisk) {
|
|
17491
|
+
const byGitPath = /* @__PURE__ */ new Map();
|
|
17492
|
+
for (const rel of paths)
|
|
17493
|
+
byGitPath.set(prefix === "" ? rel : `${prefix}/${rel}`, rel);
|
|
17494
|
+
const codes = /* @__PURE__ */ new Map();
|
|
17495
|
+
if (output !== "") {
|
|
17496
|
+
if (!output.endsWith("\0"))
|
|
17497
|
+
return null;
|
|
17498
|
+
for (const record2 of output.slice(0, -1).split("\0")) {
|
|
17499
|
+
if (record2.length < 4 || record2[2] !== " ")
|
|
17500
|
+
return null;
|
|
17501
|
+
const rel = byGitPath.get(record2.slice(3));
|
|
17502
|
+
if (rel === void 0)
|
|
17503
|
+
return null;
|
|
17504
|
+
const xy = record2.slice(0, 2);
|
|
17505
|
+
const existing = codes.get(rel);
|
|
17506
|
+
if (existing)
|
|
17507
|
+
existing.push(xy);
|
|
17508
|
+
else
|
|
17509
|
+
codes.set(rel, [xy]);
|
|
17510
|
+
}
|
|
17511
|
+
}
|
|
17512
|
+
return paths.map((path) => {
|
|
17513
|
+
const xys = codes.get(path);
|
|
17514
|
+
if (!xys)
|
|
17515
|
+
return { path, state: onDisk(path) ? "clean" : "absent" };
|
|
17516
|
+
if (xys.some((xy) => xy.includes("D")))
|
|
17517
|
+
return { path, state: "deleted" };
|
|
17518
|
+
if (xys.includes("??"))
|
|
17519
|
+
return { path, state: "untracked" };
|
|
17520
|
+
if (xys.includes("!!"))
|
|
17521
|
+
return { path, state: "ignored" };
|
|
17522
|
+
if (xys.some((xy) => xy[0] === "A"))
|
|
17523
|
+
return { path, state: "added" };
|
|
17524
|
+
return { path, state: "modified" };
|
|
17525
|
+
});
|
|
17526
|
+
}
|
|
17527
|
+
|
|
17240
17528
|
// dist/hooks/post-tool-use.js
|
|
17241
17529
|
var HANDLED_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
17242
|
-
async function runPostToolUse() {
|
|
17530
|
+
async function runPostToolUse(readSeat = readSeatConfiguration) {
|
|
17243
17531
|
const reply = hookReply("PostToolUse");
|
|
17244
|
-
await evaluateEdit(reply);
|
|
17532
|
+
await evaluateEdit(reply, readSeat);
|
|
17245
17533
|
reply.flush();
|
|
17246
17534
|
}
|
|
17247
|
-
async function evaluateEdit(reply) {
|
|
17535
|
+
async function evaluateEdit(reply, readSeat) {
|
|
17248
17536
|
let input;
|
|
17249
17537
|
try {
|
|
17250
17538
|
const raw = await readStdin();
|
|
@@ -17282,11 +17570,12 @@ async function evaluateEdit(reply) {
|
|
|
17282
17570
|
return;
|
|
17283
17571
|
}
|
|
17284
17572
|
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
17573
|
+
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat);
|
|
17285
17574
|
const evaluation = await evaluateWithRefresh({
|
|
17286
17575
|
guardServiceUrl,
|
|
17287
17576
|
token: guardServiceToken,
|
|
17288
17577
|
engagementId: guardEngagementId,
|
|
17289
|
-
request: buildEvaluationRequest(guardEngagementId, changeSet, context)
|
|
17578
|
+
request: buildEvaluationRequest(guardEngagementId, changeSet, context, seatConfiguration)
|
|
17290
17579
|
});
|
|
17291
17580
|
const clientResult = evaluation.result;
|
|
17292
17581
|
if (!clientResult.ok) {
|
|
@@ -17367,19 +17656,19 @@ ${guardLines}${notRunBlock}`);
|
|
|
17367
17656
|
}
|
|
17368
17657
|
}
|
|
17369
17658
|
function readStdin() {
|
|
17370
|
-
return new Promise((
|
|
17659
|
+
return new Promise((resolve2, reject) => {
|
|
17371
17660
|
const chunks = [];
|
|
17372
17661
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
17373
|
-
process.stdin.on("end", () =>
|
|
17662
|
+
process.stdin.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
|
|
17374
17663
|
process.stdin.on("error", reject);
|
|
17375
17664
|
});
|
|
17376
17665
|
}
|
|
17377
17666
|
|
|
17378
17667
|
// dist/hooks/marker.js
|
|
17379
17668
|
import { createHash } from "node:crypto";
|
|
17380
|
-
import { existsSync as
|
|
17669
|
+
import { existsSync as existsSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync } from "node:fs";
|
|
17381
17670
|
import { tmpdir } from "node:os";
|
|
17382
|
-
import { join as
|
|
17671
|
+
import { join as join7 } from "node:path";
|
|
17383
17672
|
var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
17384
17673
|
var MARKER_PREFIX = "halfcycle-session-";
|
|
17385
17674
|
var MARKER_SUFFIX = ".ref";
|
|
@@ -17397,19 +17686,19 @@ function markerFileName(repoRoot, sessionId) {
|
|
|
17397
17686
|
return `${MARKER_PREFIX}${key}${MARKER_SUFFIX}`;
|
|
17398
17687
|
}
|
|
17399
17688
|
function markerPath(repoRoot, sessionId) {
|
|
17400
|
-
return
|
|
17689
|
+
return join7(markerDir(), markerFileName(repoRoot, sessionId));
|
|
17401
17690
|
}
|
|
17402
17691
|
function dedupePath(repoRoot, sessionId) {
|
|
17403
17692
|
const hash2 = repoHash(repoRoot);
|
|
17404
17693
|
const key = sessionId && sessionId.length > 0 ? `${hash2}-${sessionId}` : hash2;
|
|
17405
|
-
return
|
|
17694
|
+
return join7(markerDir(), `${MARKER_PREFIX}${key}${DEDUPE_SUFFIX}`);
|
|
17406
17695
|
}
|
|
17407
17696
|
function readMarker(repoRoot, sessionId) {
|
|
17408
17697
|
const path = markerPath(repoRoot, sessionId);
|
|
17409
|
-
if (!
|
|
17698
|
+
if (!existsSync3(path))
|
|
17410
17699
|
return null;
|
|
17411
17700
|
try {
|
|
17412
|
-
return
|
|
17701
|
+
return readFileSync7(path, "utf-8").trim() || null;
|
|
17413
17702
|
} catch {
|
|
17414
17703
|
return null;
|
|
17415
17704
|
}
|
|
@@ -17417,7 +17706,7 @@ function readMarker(repoRoot, sessionId) {
|
|
|
17417
17706
|
function deleteMarker(repoRoot, sessionId) {
|
|
17418
17707
|
for (const path of [markerPath(repoRoot, sessionId), dedupePath(repoRoot, sessionId)]) {
|
|
17419
17708
|
try {
|
|
17420
|
-
if (
|
|
17709
|
+
if (existsSync3(path))
|
|
17421
17710
|
unlinkSync3(path);
|
|
17422
17711
|
} catch {
|
|
17423
17712
|
}
|
|
@@ -17425,10 +17714,10 @@ function deleteMarker(repoRoot, sessionId) {
|
|
|
17425
17714
|
}
|
|
17426
17715
|
function readDedupe(repoRoot, sessionId) {
|
|
17427
17716
|
const path = dedupePath(repoRoot, sessionId);
|
|
17428
|
-
if (!
|
|
17717
|
+
if (!existsSync3(path))
|
|
17429
17718
|
return null;
|
|
17430
17719
|
try {
|
|
17431
|
-
const parsed = JSON.parse(
|
|
17720
|
+
const parsed = JSON.parse(readFileSync7(path, "utf-8"));
|
|
17432
17721
|
if (typeof parsed === "object" && parsed !== null && typeof parsed.base === "string" && typeof parsed.digest === "string") {
|
|
17433
17722
|
return parsed;
|
|
17434
17723
|
}
|
|
@@ -17470,10 +17759,10 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
|
|
|
17470
17759
|
continue;
|
|
17471
17760
|
if (name === keepName)
|
|
17472
17761
|
continue;
|
|
17473
|
-
const full =
|
|
17762
|
+
const full = join7(dir, name);
|
|
17474
17763
|
let unusable = false;
|
|
17475
17764
|
try {
|
|
17476
|
-
const ref =
|
|
17765
|
+
const ref = readFileSync7(full, "utf-8").trim();
|
|
17477
17766
|
if (!ref || !refKnown(ref))
|
|
17478
17767
|
unusable = true;
|
|
17479
17768
|
} catch {
|
|
@@ -17531,12 +17820,12 @@ function collectDiff(repoRoot, base) {
|
|
|
17531
17820
|
getDiff: (relPath) => getWorkingTreeFileDiff(relPath, repoRoot)
|
|
17532
17821
|
};
|
|
17533
17822
|
}
|
|
17534
|
-
async function runSessionDiff(input, kind) {
|
|
17823
|
+
async function runSessionDiff(input, kind, readSeat = readSeatConfiguration) {
|
|
17535
17824
|
const reply = hookReply(STOP_HOOK_EVENT[kind]);
|
|
17536
|
-
await evaluateSession(input, kind, reply);
|
|
17825
|
+
await evaluateSession(input, kind, reply, readSeat);
|
|
17537
17826
|
reply.flush();
|
|
17538
17827
|
}
|
|
17539
|
-
async function evaluateSession(input, kind, reply) {
|
|
17828
|
+
async function evaluateSession(input, kind, reply, readSeat) {
|
|
17540
17829
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
17541
17830
|
if (!repoRootResult.ok) {
|
|
17542
17831
|
reply.notice(localFaultText(`Could not find git repo root: ${repoRootResult.message}`));
|
|
@@ -17576,11 +17865,12 @@ async function evaluateSession(input, kind, reply) {
|
|
|
17576
17865
|
return;
|
|
17577
17866
|
}
|
|
17578
17867
|
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
17868
|
+
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat);
|
|
17579
17869
|
const evaluation = await evaluateWithRefresh({
|
|
17580
17870
|
guardServiceUrl,
|
|
17581
17871
|
token: guardServiceToken,
|
|
17582
17872
|
engagementId: guardEngagementId,
|
|
17583
|
-
request: buildEvaluationRequest(guardEngagementId, changeSet, context)
|
|
17873
|
+
request: buildEvaluationRequest(guardEngagementId, changeSet, context, seatConfiguration)
|
|
17584
17874
|
});
|
|
17585
17875
|
const clientResult = evaluation.result;
|
|
17586
17876
|
if (!clientResult.ok) {
|
|
@@ -17678,9 +17968,9 @@ async function readHookStdin() {
|
|
|
17678
17968
|
}
|
|
17679
17969
|
}
|
|
17680
17970
|
function readStdin2() {
|
|
17681
|
-
return new Promise((
|
|
17971
|
+
return new Promise((resolve2) => {
|
|
17682
17972
|
if (process.stdin.isTTY) {
|
|
17683
|
-
|
|
17973
|
+
resolve2("");
|
|
17684
17974
|
return;
|
|
17685
17975
|
}
|
|
17686
17976
|
const chunks = [];
|
|
@@ -17690,7 +17980,7 @@ function readStdin2() {
|
|
|
17690
17980
|
return;
|
|
17691
17981
|
settled = true;
|
|
17692
17982
|
clearTimeout(timer);
|
|
17693
|
-
|
|
17983
|
+
resolve2(Buffer.concat(chunks).toString("utf-8"));
|
|
17694
17984
|
};
|
|
17695
17985
|
const timer = setTimeout(done, 2e3);
|
|
17696
17986
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -17712,17 +18002,17 @@ async function runStop() {
|
|
|
17712
18002
|
}
|
|
17713
18003
|
|
|
17714
18004
|
// dist/resolve-engagement.js
|
|
17715
|
-
import { existsSync as
|
|
17716
|
-
import { join as
|
|
17717
|
-
import { homedir as
|
|
18005
|
+
import { existsSync as existsSync4, readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
|
|
18006
|
+
import { join as join8, sep as sep2 } from "node:path";
|
|
18007
|
+
import { homedir as homedir4 } from "node:os";
|
|
17718
18008
|
function defaultRegistryPath() {
|
|
17719
|
-
return
|
|
18009
|
+
return join8(homedir4(), ".halfcycle", "registry.json");
|
|
17720
18010
|
}
|
|
17721
18011
|
function readRegistry(registryPath = defaultRegistryPath()) {
|
|
17722
|
-
if (!
|
|
18012
|
+
if (!existsSync4(registryPath))
|
|
17723
18013
|
return {};
|
|
17724
18014
|
try {
|
|
17725
|
-
const parsed = JSON.parse(
|
|
18015
|
+
const parsed = JSON.parse(readFileSync8(registryPath, "utf-8"));
|
|
17726
18016
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
17727
18017
|
return {};
|
|
17728
18018
|
const out = {};
|
|
@@ -17737,7 +18027,7 @@ function readRegistry(registryPath = defaultRegistryPath()) {
|
|
|
17737
18027
|
}
|
|
17738
18028
|
function canonicalise(p) {
|
|
17739
18029
|
try {
|
|
17740
|
-
return
|
|
18030
|
+
return realpathSync3(p);
|
|
17741
18031
|
} catch {
|
|
17742
18032
|
return p;
|
|
17743
18033
|
}
|
|
@@ -17802,7 +18092,7 @@ async function runRefreshCredential(opts) {
|
|
|
17802
18092
|
}
|
|
17803
18093
|
|
|
17804
18094
|
// dist/capture.js
|
|
17805
|
-
import { readFileSync as
|
|
18095
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "node:fs";
|
|
17806
18096
|
import { dirname as dirname4 } from "node:path";
|
|
17807
18097
|
function runCapture(provider, endpoint, opts = {}) {
|
|
17808
18098
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
@@ -17870,7 +18160,7 @@ function readInputFile(fromFile) {
|
|
|
17870
18160
|
if (!fromFile)
|
|
17871
18161
|
return void 0;
|
|
17872
18162
|
try {
|
|
17873
|
-
return
|
|
18163
|
+
return readFileSync9(fromFile, "utf-8");
|
|
17874
18164
|
} catch {
|
|
17875
18165
|
return void 0;
|
|
17876
18166
|
}
|
|
@@ -17965,10 +18255,10 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
17965
18255
|
process.exit(1);
|
|
17966
18256
|
}
|
|
17967
18257
|
function readStdin3() {
|
|
17968
|
-
return new Promise((
|
|
18258
|
+
return new Promise((resolve2, reject) => {
|
|
17969
18259
|
const chunks = [];
|
|
17970
18260
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
17971
|
-
process.stdin.on("end", () =>
|
|
18261
|
+
process.stdin.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
|
|
17972
18262
|
process.stdin.on("error", reject);
|
|
17973
18263
|
});
|
|
17974
18264
|
}
|
package/dist/bin.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Subcommands:
|
|
9
9
|
* halfcycle [install] [target-repo] — install into target (default: cwd)
|
|
10
10
|
* halfcycle check-drift <target> — report bundle drift for the re-sync gate
|
|
11
|
-
* halfcycle build-record <phase> —
|
|
11
|
+
* halfcycle build-record <phase> — fetch a closed phase's Build Record and write it
|
|
12
12
|
* halfcycle open-phase <phase> — open a phase, with its entry decision
|
|
13
13
|
* halfcycle close-phase <phase> — record a phase's acceptance and close it
|
|
14
14
|
* halfcycle uninstall [--remove-credential] — take back what the installer wrote here
|