@scheduler-systems/gal-run 0.0.378 → 0.0.380
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/dist/index.cjs +181 -46
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3970,7 +3970,7 @@ var cliVersion, defaultApiUrl, BUILD_CONSTANTS, constants_default;
|
|
|
3970
3970
|
var init_constants = __esm({
|
|
3971
3971
|
"src/constants.ts"() {
|
|
3972
3972
|
"use strict";
|
|
3973
|
-
cliVersion = true ? "0.0.
|
|
3973
|
+
cliVersion = true ? "0.0.380" : "0.0.0-dev";
|
|
3974
3974
|
defaultApiUrl = true ? "https://api.gal.run" : "http://localhost:3000";
|
|
3975
3975
|
BUILD_CONSTANTS = Object.freeze([cliVersion, defaultApiUrl]);
|
|
3976
3976
|
constants_default = BUILD_CONSTANTS;
|
|
@@ -4880,7 +4880,7 @@ function detectEnvironment() {
|
|
|
4880
4880
|
return "dev";
|
|
4881
4881
|
}
|
|
4882
4882
|
try {
|
|
4883
|
-
const version2 = true ? "0.0.
|
|
4883
|
+
const version2 = true ? "0.0.380" : void 0;
|
|
4884
4884
|
if (version2 && version2.includes("-local")) {
|
|
4885
4885
|
return "dev";
|
|
4886
4886
|
}
|
|
@@ -5249,7 +5249,7 @@ function getId() {
|
|
|
5249
5249
|
}
|
|
5250
5250
|
function getCliVersion() {
|
|
5251
5251
|
try {
|
|
5252
|
-
return true ? "0.0.
|
|
5252
|
+
return true ? "0.0.380" : "0.0.0-dev";
|
|
5253
5253
|
} catch {
|
|
5254
5254
|
return "0.0.0-dev";
|
|
5255
5255
|
}
|
|
@@ -54045,7 +54045,8 @@ function generateSdlcPrePushHook(apiUrl, orgName) {
|
|
|
54045
54045
|
# GAL SDLC Phase-Gate Pre-Push Hook
|
|
54046
54046
|
# Installed by: gal enforce install --sdlc
|
|
54047
54047
|
# Blocks pushes when the SDLC phase gate for the branch is not met.
|
|
54048
|
-
#
|
|
54048
|
+
# Uses the local GAL CLI for auth and API access.
|
|
54049
|
+
# Fail-open: if GAL or the API is unavailable the push is allowed.
|
|
54049
54050
|
# To skip: git push --no-verify
|
|
54050
54051
|
|
|
54051
54052
|
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
@@ -54058,25 +54059,15 @@ esac
|
|
|
54058
54059
|
API_URL="${apiUrl}"
|
|
54059
54060
|
ORG_NAME="${orgName}"
|
|
54060
54061
|
|
|
54061
|
-
RESPONSE=$(
|
|
54062
|
-
|
|
54063
|
-
2>/dev/null) || {
|
|
54064
|
-
# API unreachable \u2014 fail open
|
|
54062
|
+
RESPONSE=$(gal enforce sdlc gate --branch "$BRANCH" --json 2>/dev/null) || {
|
|
54063
|
+
# GAL unavailable or API unreachable \u2014 fail open
|
|
54065
54064
|
exit 0
|
|
54066
54065
|
}
|
|
54067
54066
|
|
|
54068
|
-
|
|
54069
|
-
BODY=$(echo "$RESPONSE" | sed '$ d')
|
|
54070
|
-
|
|
54071
|
-
if [ "$HTTP_CODE" != "200" ]; then
|
|
54072
|
-
# Non-200 \u2014 fail open
|
|
54073
|
-
exit 0
|
|
54074
|
-
fi
|
|
54075
|
-
|
|
54076
|
-
ALLOWED=$(echo "$BODY" | grep -o '"allowed"\\s*:\\s*\\(true\\|false\\)' | grep -o '\\(true\\|false\\)')
|
|
54067
|
+
ALLOWED=$(echo "$RESPONSE" | grep -o '"allowed"\\s*:\\s*\\(true\\|false\\)' | grep -o '\\(true\\|false\\)')
|
|
54077
54068
|
|
|
54078
54069
|
if [ "$ALLOWED" = "false" ]; then
|
|
54079
|
-
MESSAGE=$(echo "$
|
|
54070
|
+
MESSAGE=$(echo "$RESPONSE" | grep -o '"message"\\s*:\\s*"[^"]*"' | head -1 | sed 's/"message"\\s*:\\s*"//;s/"$//')
|
|
54080
54071
|
echo ""
|
|
54081
54072
|
echo "\\033[31m[GAL] SDLC phase gate blocked this push.\\033[0m"
|
|
54082
54073
|
echo "\\033[33m Branch: $BRANCH\\033[0m"
|
|
@@ -54092,31 +54083,106 @@ exit 0
|
|
|
54092
54083
|
`;
|
|
54093
54084
|
}
|
|
54094
54085
|
function generateSdlcAgentHook(apiUrl) {
|
|
54095
|
-
|
|
54096
|
-
|
|
54097
|
-
|
|
54098
|
-
|
|
54099
|
-
|
|
54100
|
-
|
|
54101
|
-
|
|
54102
|
-
|
|
54103
|
-
|
|
54104
|
-
|
|
54105
|
-
|
|
54106
|
-
|
|
54107
|
-
|
|
54108
|
-
|
|
54109
|
-
|
|
54110
|
-
|
|
54111
|
-
|
|
54112
|
-
|
|
54113
|
-
|
|
54114
|
-
|
|
54115
|
-
|
|
54116
|
-
|
|
54117
|
-
|
|
54118
|
-
|
|
54119
|
-
|
|
54086
|
+
return `#!/usr/bin/env python3
|
|
54087
|
+
"""
|
|
54088
|
+
GAL SDLC phase-gate hook.
|
|
54089
|
+
Installed by: gal enforce install --sdlc
|
|
54090
|
+
Fail-open: if GAL or the API is unavailable the tool call is allowed.
|
|
54091
|
+
API URL: ${apiUrl}
|
|
54092
|
+
"""
|
|
54093
|
+
import json
|
|
54094
|
+
import subprocess
|
|
54095
|
+
import sys
|
|
54096
|
+
|
|
54097
|
+
PROTECTED_PREFIXES = (
|
|
54098
|
+
".github/workflows/",
|
|
54099
|
+
".claude/settings.json",
|
|
54100
|
+
)
|
|
54101
|
+
|
|
54102
|
+
|
|
54103
|
+
def read_hook_input():
|
|
54104
|
+
try:
|
|
54105
|
+
return json.load(sys.stdin)
|
|
54106
|
+
except Exception:
|
|
54107
|
+
return {}
|
|
54108
|
+
|
|
54109
|
+
|
|
54110
|
+
def current_branch() -> str:
|
|
54111
|
+
try:
|
|
54112
|
+
return subprocess.check_output(
|
|
54113
|
+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
54114
|
+
stderr=subprocess.DEVNULL,
|
|
54115
|
+
text=True,
|
|
54116
|
+
timeout=5,
|
|
54117
|
+
).strip()
|
|
54118
|
+
except Exception:
|
|
54119
|
+
return ""
|
|
54120
|
+
|
|
54121
|
+
|
|
54122
|
+
def should_check(tool_name: str, tool_input: dict) -> bool:
|
|
54123
|
+
if tool_name == "Bash":
|
|
54124
|
+
command = str(tool_input.get("command", ""))
|
|
54125
|
+
return "git push" in command
|
|
54126
|
+
|
|
54127
|
+
if tool_name in {"Edit", "Write", "MultiEdit"}:
|
|
54128
|
+
file_path = str(tool_input.get("file_path", ""))
|
|
54129
|
+
return any(file_path.startswith(prefix) for prefix in PROTECTED_PREFIXES)
|
|
54130
|
+
|
|
54131
|
+
return False
|
|
54132
|
+
|
|
54133
|
+
|
|
54134
|
+
def query_gate(branch: str) -> dict:
|
|
54135
|
+
if not branch or branch in {"main", "master"}:
|
|
54136
|
+
return {"allowed": True}
|
|
54137
|
+
|
|
54138
|
+
try:
|
|
54139
|
+
result = subprocess.run(
|
|
54140
|
+
["gal", "enforce", "sdlc", "gate", "--branch", branch, "--json"],
|
|
54141
|
+
capture_output=True,
|
|
54142
|
+
text=True,
|
|
54143
|
+
timeout=10,
|
|
54144
|
+
check=False,
|
|
54145
|
+
)
|
|
54146
|
+
except Exception:
|
|
54147
|
+
return {"allowed": True}
|
|
54148
|
+
|
|
54149
|
+
if result.returncode != 0:
|
|
54150
|
+
return {"allowed": True}
|
|
54151
|
+
|
|
54152
|
+
try:
|
|
54153
|
+
payload = json.loads(result.stdout or "{}")
|
|
54154
|
+
return payload if isinstance(payload, dict) else {"allowed": True}
|
|
54155
|
+
except Exception:
|
|
54156
|
+
return {"allowed": True}
|
|
54157
|
+
|
|
54158
|
+
|
|
54159
|
+
def main():
|
|
54160
|
+
hook_input = read_hook_input()
|
|
54161
|
+
tool_name = str(hook_input.get("tool_name", ""))
|
|
54162
|
+
tool_input = hook_input.get("tool_input", {}) or {}
|
|
54163
|
+
if not isinstance(tool_input, dict):
|
|
54164
|
+
tool_input = {}
|
|
54165
|
+
|
|
54166
|
+
if not should_check(tool_name, tool_input):
|
|
54167
|
+
print(json.dumps({"decision": "allow"}))
|
|
54168
|
+
return
|
|
54169
|
+
|
|
54170
|
+
branch = current_branch()
|
|
54171
|
+
gate = query_gate(branch)
|
|
54172
|
+
if gate.get("allowed", True):
|
|
54173
|
+
print(json.dumps({"decision": "allow"}))
|
|
54174
|
+
return
|
|
54175
|
+
|
|
54176
|
+
message = gate.get("message") or "Complete the required SDLC phase before continuing."
|
|
54177
|
+
print(json.dumps({
|
|
54178
|
+
"decision": "block",
|
|
54179
|
+
"reason": f"SDLC phase gate blocked this operation on branch '{branch}': {message}",
|
|
54180
|
+
}))
|
|
54181
|
+
|
|
54182
|
+
|
|
54183
|
+
if __name__ == "__main__":
|
|
54184
|
+
main()
|
|
54185
|
+
`;
|
|
54120
54186
|
}
|
|
54121
54187
|
var init_hook_generator = __esm({
|
|
54122
54188
|
"src/enforcement/hook-generator.ts"() {
|
|
@@ -56832,7 +56898,7 @@ Installing hooks for: ${platforms.join(", ")}`));
|
|
|
56832
56898
|
if (!options.dryRun) {
|
|
56833
56899
|
await installSdlcAgentHook(basePath, sdlcApiUrl, options.force);
|
|
56834
56900
|
}
|
|
56835
|
-
spinner.succeed(`Installed SDLC agent hook \u2192 ${source_default.dim(".claude/hooks/sdlc-phase-gate.
|
|
56901
|
+
spinner.succeed(`Installed SDLC agent hook \u2192 ${source_default.dim(".claude/hooks/sdlc-phase-gate.py")}`);
|
|
56836
56902
|
} catch (err) {
|
|
56837
56903
|
const msg = err instanceof Error ? err.message : String(err);
|
|
56838
56904
|
spinner.warn(`SDLC agent hook: ${msg}`);
|
|
@@ -57435,6 +57501,48 @@ Installing hooks for: ${platforms.join(", ")}`));
|
|
|
57435
57501
|
process.exit(1);
|
|
57436
57502
|
}
|
|
57437
57503
|
});
|
|
57504
|
+
sdlcCommand.command("gate").description("Evaluate the native SDLC gate for a tracked branch").requiredOption("--branch <branch>", "Git branch to evaluate").option("--json", "Output as JSON").action(async (options) => {
|
|
57505
|
+
const { authToken, orgName, apiUrl } = getAuthAndOrg2();
|
|
57506
|
+
const spinner = options.json ? null : ora(`Evaluating SDLC gate for ${options.branch}...`).start();
|
|
57507
|
+
try {
|
|
57508
|
+
const branch = options.branch.trim();
|
|
57509
|
+
const res = await fetchApi3(
|
|
57510
|
+
`${apiUrl}/organizations/${encodeURIComponent(orgName)}/enforcement/sdlc/gate?branch=${encodeURIComponent(branch)}`,
|
|
57511
|
+
authToken
|
|
57512
|
+
);
|
|
57513
|
+
if (!res.ok) {
|
|
57514
|
+
const data2 = await res.json().catch(() => ({}));
|
|
57515
|
+
throw new Error(data2.error || `HTTP ${res.status}`);
|
|
57516
|
+
}
|
|
57517
|
+
const data = await res.json();
|
|
57518
|
+
spinner?.stop();
|
|
57519
|
+
if (options.json) {
|
|
57520
|
+
console.log(JSON.stringify(data, null, 2));
|
|
57521
|
+
return;
|
|
57522
|
+
}
|
|
57523
|
+
const allowed = data.allowed !== false;
|
|
57524
|
+
const message = typeof data.message === "string" ? data.message : void 0;
|
|
57525
|
+
const source = typeof data.source === "string" ? data.source : "unknown";
|
|
57526
|
+
console.log(source_default.bold("\nNative SDLC Branch Gate\n"));
|
|
57527
|
+
console.log(` Organization: ${source_default.cyan(orgName)}`);
|
|
57528
|
+
console.log(` Branch: ${branch}`);
|
|
57529
|
+
console.log(` Allowed: ${allowed ? source_default.green("yes") : source_default.red("no")}`);
|
|
57530
|
+
console.log(` Source: ${source_default.dim(source)}`);
|
|
57531
|
+
if (message) {
|
|
57532
|
+
console.log(` Message: ${message}`);
|
|
57533
|
+
}
|
|
57534
|
+
console.log();
|
|
57535
|
+
} catch (error3) {
|
|
57536
|
+
spinner?.stop();
|
|
57537
|
+
const msg = error3 instanceof Error ? error3.message : String(error3);
|
|
57538
|
+
if (options.json) {
|
|
57539
|
+
console.log(JSON.stringify({ error: msg }));
|
|
57540
|
+
} else {
|
|
57541
|
+
console.error(source_default.red("Error evaluating SDLC branch gate:"), msg);
|
|
57542
|
+
}
|
|
57543
|
+
process.exit(1);
|
|
57544
|
+
}
|
|
57545
|
+
});
|
|
57438
57546
|
sdlcCommand.command("set <level>").description("Set native SDLC enforcement mode (off, warn, block)").option("--reason <reason>", "Reason to include in the audit trail").option("--json", "Output as JSON").action(async (level, options) => {
|
|
57439
57547
|
if (!["off", "warn", "block"].includes(level)) {
|
|
57440
57548
|
console.error(source_default.red("Invalid level. Use one of: off, warn, block"));
|
|
@@ -57805,13 +57913,40 @@ async function installSdlcPrePushHook(basePath, apiUrl, orgName, force) {
|
|
|
57805
57913
|
}
|
|
57806
57914
|
async function installSdlcAgentHook(basePath, apiUrl, force) {
|
|
57807
57915
|
const claudeHooksDir = (0, import_node_path3.join)(basePath, ".claude", "hooks");
|
|
57808
|
-
const hookPath = (0, import_node_path3.join)(claudeHooksDir, "sdlc-phase-gate.
|
|
57916
|
+
const hookPath = (0, import_node_path3.join)(claudeHooksDir, "sdlc-phase-gate.py");
|
|
57809
57917
|
if ((0, import_node_fs2.existsSync)(hookPath) && !force) {
|
|
57810
57918
|
throw new Error("SDLC agent hook already installed. Use --force to overwrite.");
|
|
57811
57919
|
}
|
|
57812
57920
|
await (0, import_promises4.mkdir)(claudeHooksDir, { recursive: true });
|
|
57813
57921
|
const hookContent = generateSdlcAgentHook(apiUrl);
|
|
57814
57922
|
await (0, import_promises4.writeFile)(hookPath, hookContent, "utf-8");
|
|
57923
|
+
await (0, import_promises4.chmod)(hookPath, 493);
|
|
57924
|
+
const claudeDir = (0, import_node_path3.join)(basePath, ".claude");
|
|
57925
|
+
await (0, import_promises4.mkdir)(claudeDir, { recursive: true });
|
|
57926
|
+
const settingsPath = (0, import_node_path3.join)(claudeDir, "settings.json");
|
|
57927
|
+
let settings = {};
|
|
57928
|
+
try {
|
|
57929
|
+
const existingSettings = await (0, import_promises4.readFile)(settingsPath, "utf-8");
|
|
57930
|
+
settings = JSON.parse(existingSettings);
|
|
57931
|
+
} catch {
|
|
57932
|
+
settings = {};
|
|
57933
|
+
}
|
|
57934
|
+
const hooks = settings.hooks && typeof settings.hooks === "object" ? { ...settings.hooks } : {};
|
|
57935
|
+
const existingPreToolUse = Array.isArray(hooks.PreToolUse) ? [...hooks.PreToolUse] : [];
|
|
57936
|
+
const filteredPreToolUse = existingPreToolUse.filter((entry) => {
|
|
57937
|
+
if (!entry || typeof entry !== "object") {
|
|
57938
|
+
return true;
|
|
57939
|
+
}
|
|
57940
|
+
return entry.command !== "python3 .claude/hooks/sdlc-phase-gate.py";
|
|
57941
|
+
});
|
|
57942
|
+
filteredPreToolUse.push({
|
|
57943
|
+
type: "command",
|
|
57944
|
+
command: "python3 .claude/hooks/sdlc-phase-gate.py",
|
|
57945
|
+
timeout: 5e3
|
|
57946
|
+
});
|
|
57947
|
+
hooks.PreToolUse = filteredPreToolUse;
|
|
57948
|
+
settings.hooks = hooks;
|
|
57949
|
+
await (0, import_promises4.writeFile)(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
57815
57950
|
}
|
|
57816
57951
|
var import_promises4, import_node_fs2, import_node_path3, import_node_crypto, defaultApiUrl7, PLATFORM_DIRS3, SCAN_PLATFORM_DIRS, ENFORCEMENT_HOOK_SUPPORT, GAL_PRECOMMIT_MARKER, GAL_PRECOMMIT_CONTENT, GAL_SDLC_PREPUSH_MARKER;
|
|
57817
57952
|
var init_enforce = __esm({
|
|
@@ -70904,7 +71039,7 @@ var init_index = __esm({
|
|
|
70904
71039
|
});
|
|
70905
71040
|
|
|
70906
71041
|
// src/bootstrap.ts
|
|
70907
|
-
var cliVersion10 = true ? "0.0.
|
|
71042
|
+
var cliVersion10 = true ? "0.0.380" : "0.0.0-dev";
|
|
70908
71043
|
var args = process.argv.slice(2);
|
|
70909
71044
|
var requestedGlobalHelp = args.length === 1 && (args[0] === "--help" || args[0] === "-h");
|
|
70910
71045
|
var requestedVersion = args.length === 1 && (args[0] === "--version" || args[0] === "-V");
|
package/package.json
CHANGED