@aaac/contracts 0.1.12 → 0.1.13
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/cli/index.js +117 -17
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3818,6 +3818,7 @@ var GOVERNANCE_LOCK_PATH = ".agent-logs/governance-manifest.lock";
|
|
|
3818
3818
|
var DEFAULT_RECORDER = ".cursor/hooks/observ-record.sh";
|
|
3819
3819
|
var DEFAULT_CURSOR_HOOKS_JSON = ".cursor/hooks.json";
|
|
3820
3820
|
var DEFAULT_BINARY_DEST = "bin/aaac.js";
|
|
3821
|
+
var CURSOR_HOOKS_VERSION = 1;
|
|
3821
3822
|
var EnvironmentsSchema = z.object({
|
|
3822
3823
|
git: z.object({ hooks: z.array(z.string()).optional() }).optional(),
|
|
3823
3824
|
cursor: z.object({ hooks_json: z.string().optional() }).optional()
|
|
@@ -3827,7 +3828,8 @@ var BindingsObservabilitySchema = z.object({
|
|
|
3827
3828
|
recorder: z.string().optional()
|
|
3828
3829
|
}).optional();
|
|
3829
3830
|
var BindingsSchema = z.object({
|
|
3830
|
-
observability: BindingsObservabilitySchema
|
|
3831
|
+
observability: BindingsObservabilitySchema,
|
|
3832
|
+
hooks: z.array(z.string()).optional()
|
|
3831
3833
|
}).optional();
|
|
3832
3834
|
var InitProjectConfigSchema = z.object({
|
|
3833
3835
|
environments: EnvironmentsSchema,
|
|
@@ -3839,6 +3841,33 @@ async function loadInitConfig(configPath) {
|
|
|
3839
3841
|
const parsed = parseYaml(raw);
|
|
3840
3842
|
return InitProjectConfigSchema.parse(parsed ?? {});
|
|
3841
3843
|
}
|
|
3844
|
+
var HookCommandSchema = z.object({
|
|
3845
|
+
command: z.string(),
|
|
3846
|
+
description: z.string().optional()
|
|
3847
|
+
});
|
|
3848
|
+
var HookBindingSchema = z.object({
|
|
3849
|
+
hooks: z.record(z.string(), z.array(HookCommandSchema)).optional()
|
|
3850
|
+
});
|
|
3851
|
+
async function loadHookBindings(projectRoot, bindingPaths) {
|
|
3852
|
+
const result = {};
|
|
3853
|
+
if (!bindingPaths?.length) return result;
|
|
3854
|
+
const root = resolve(projectRoot);
|
|
3855
|
+
for (const bindingPath of bindingPaths) {
|
|
3856
|
+
const absPath = resolve(root, bindingPath);
|
|
3857
|
+
if (!existsSync(absPath)) continue;
|
|
3858
|
+
const raw = await readFile(absPath, "utf8");
|
|
3859
|
+
const parsed = HookBindingSchema.parse(parseYaml(raw) ?? {});
|
|
3860
|
+
if (!parsed.hooks) continue;
|
|
3861
|
+
for (const [hookName, commands] of Object.entries(parsed.hooks)) {
|
|
3862
|
+
const list = result[hookName] ?? [];
|
|
3863
|
+
for (const { command } of commands) {
|
|
3864
|
+
if (!list.includes(command)) list.push(command);
|
|
3865
|
+
}
|
|
3866
|
+
result[hookName] = list;
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
return result;
|
|
3870
|
+
}
|
|
3842
3871
|
function escapeRegex(value) {
|
|
3843
3872
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3844
3873
|
}
|
|
@@ -3882,15 +3911,77 @@ function removeManagedBlock(existingContent) {
|
|
|
3882
3911
|
}
|
|
3883
3912
|
return withoutBlock.replace(/\n{3,}/g, "\n\n").replace(/\s+$/, "\n");
|
|
3884
3913
|
}
|
|
3914
|
+
function parseCursorHooksFile(raw) {
|
|
3915
|
+
if (!raw || !raw.trim()) {
|
|
3916
|
+
return { version: CURSOR_HOOKS_VERSION, hooks: {} };
|
|
3917
|
+
}
|
|
3918
|
+
const parsed = JSON.parse(raw);
|
|
3919
|
+
if (Array.isArray(parsed)) {
|
|
3920
|
+
return flatEntriesToFile(
|
|
3921
|
+
parsed.filter(
|
|
3922
|
+
(entry) => entry !== null && typeof entry === "object" && typeof entry.event === "string" && typeof entry.command === "string"
|
|
3923
|
+
)
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
if (parsed && typeof parsed === "object") {
|
|
3927
|
+
const obj = parsed;
|
|
3928
|
+
const version = typeof obj.version === "number" ? obj.version : CURSOR_HOOKS_VERSION;
|
|
3929
|
+
const hooks = {};
|
|
3930
|
+
const rawHooks = obj.hooks;
|
|
3931
|
+
if (rawHooks && typeof rawHooks === "object" && !Array.isArray(rawHooks)) {
|
|
3932
|
+
for (const [event, commands] of Object.entries(rawHooks)) {
|
|
3933
|
+
if (Array.isArray(commands)) {
|
|
3934
|
+
hooks[event] = commands.filter(
|
|
3935
|
+
(c) => c !== null && typeof c === "object" && typeof c.command === "string"
|
|
3936
|
+
);
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
return { version, hooks };
|
|
3941
|
+
}
|
|
3942
|
+
return { version: CURSOR_HOOKS_VERSION, hooks: {} };
|
|
3943
|
+
}
|
|
3944
|
+
function serializeCursorHooksFile(file) {
|
|
3945
|
+
return JSON.stringify(file, null, 2) + "\n";
|
|
3946
|
+
}
|
|
3947
|
+
function flatEntriesToFile(entries) {
|
|
3948
|
+
return mergeCursorHooks({ version: CURSOR_HOOKS_VERSION, hooks: {} }, entries);
|
|
3949
|
+
}
|
|
3885
3950
|
function mergeCursorHooks(existing, newEntries) {
|
|
3886
|
-
const
|
|
3887
|
-
for (const
|
|
3888
|
-
|
|
3889
|
-
|
|
3951
|
+
const hooks = {};
|
|
3952
|
+
for (const [event, commands] of Object.entries(existing.hooks)) {
|
|
3953
|
+
hooks[event] = commands.map((c) => ({ ...c }));
|
|
3954
|
+
}
|
|
3955
|
+
for (const entry of newEntries) {
|
|
3956
|
+
const { event, ...command } = entry;
|
|
3957
|
+
const list = hooks[event] ?? [];
|
|
3958
|
+
const idx = list.findIndex((c) => c.command === command.command);
|
|
3959
|
+
if (idx >= 0) {
|
|
3960
|
+
list[idx] = command;
|
|
3961
|
+
} else {
|
|
3962
|
+
list.push(command);
|
|
3963
|
+
}
|
|
3964
|
+
hooks[event] = list;
|
|
3965
|
+
}
|
|
3966
|
+
return { version: existing.version ?? CURSOR_HOOKS_VERSION, hooks };
|
|
3890
3967
|
}
|
|
3891
3968
|
function removeCursorHooks(existing, commands) {
|
|
3892
3969
|
const removeSet = new Set(commands);
|
|
3893
|
-
|
|
3970
|
+
const hooks = {};
|
|
3971
|
+
for (const [event, list] of Object.entries(existing.hooks)) {
|
|
3972
|
+
const filtered = list.filter((c) => !removeSet.has(c.command));
|
|
3973
|
+
if (filtered.length > 0) {
|
|
3974
|
+
hooks[event] = filtered;
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
return { version: existing.version ?? CURSOR_HOOKS_VERSION, hooks };
|
|
3978
|
+
}
|
|
3979
|
+
function buildGitHookBlock(hookName, recorder, gateCommands = []) {
|
|
3980
|
+
const lines = [`${recorder} ${hookName} "$@" || true`];
|
|
3981
|
+
for (const command of gateCommands) {
|
|
3982
|
+
lines.push(`${command} || exit 1`);
|
|
3983
|
+
}
|
|
3984
|
+
return lines.join("\n");
|
|
3894
3985
|
}
|
|
3895
3986
|
function buildDefaultCursorHooks(recorder) {
|
|
3896
3987
|
return [
|
|
@@ -3930,7 +4021,11 @@ async function materializeHooks(options) {
|
|
|
3930
4021
|
for (const hookName of options.environments.git.hooks) {
|
|
3931
4022
|
const relPath = join(".git/hooks", hookName);
|
|
3932
4023
|
const absPath = join(projectRoot, relPath);
|
|
3933
|
-
const blockContent =
|
|
4024
|
+
const blockContent = buildGitHookBlock(
|
|
4025
|
+
hookName,
|
|
4026
|
+
recorder,
|
|
4027
|
+
options.hookCommands?.[hookName]
|
|
4028
|
+
);
|
|
3934
4029
|
const existing = await readTextFileIfExists(absPath) ?? "";
|
|
3935
4030
|
const updated = injectManagedBlock(existing, blockContent);
|
|
3936
4031
|
let action = "unchanged";
|
|
@@ -3954,9 +4049,9 @@ async function materializeHooks(options) {
|
|
|
3954
4049
|
const relPath = hooksJsonPath.startsWith(projectRoot + "/") ? hooksJsonPath.slice(projectRoot.length + 1) : DEFAULT_CURSOR_HOOKS_JSON;
|
|
3955
4050
|
const newEntries = buildDefaultCursorHooks(recorder);
|
|
3956
4051
|
const existingRaw = await readTextFileIfExists(hooksJsonPath);
|
|
3957
|
-
const existing =
|
|
4052
|
+
const existing = parseCursorHooksFile(existingRaw);
|
|
3958
4053
|
const merged = mergeCursorHooks(existing, newEntries);
|
|
3959
|
-
const serialized =
|
|
4054
|
+
const serialized = serializeCursorHooksFile(merged);
|
|
3960
4055
|
let action = "unchanged";
|
|
3961
4056
|
if (!existsSync(hooksJsonPath)) {
|
|
3962
4057
|
action = "created";
|
|
@@ -4003,14 +4098,10 @@ async function unmaterializeHooks(projectRoot, lockEntries) {
|
|
|
4003
4098
|
const absPath = join(root, entry.path);
|
|
4004
4099
|
const existingRaw = await readTextFileIfExists(absPath);
|
|
4005
4100
|
if (existingRaw === void 0) continue;
|
|
4006
|
-
const existing =
|
|
4101
|
+
const existing = parseCursorHooksFile(existingRaw);
|
|
4007
4102
|
const commandsToRemove = buildDefaultCursorHooks(DEFAULT_RECORDER).map((h) => h.command);
|
|
4008
4103
|
const updated = removeCursorHooks(existing, commandsToRemove);
|
|
4009
|
-
await writeFile(
|
|
4010
|
-
absPath,
|
|
4011
|
-
updated.length === 0 ? "[]\n" : JSON.stringify(updated, null, 2) + "\n",
|
|
4012
|
-
"utf8"
|
|
4013
|
-
);
|
|
4104
|
+
await writeFile(absPath, serializeCursorHooksFile(updated), "utf8");
|
|
4014
4105
|
}
|
|
4015
4106
|
}
|
|
4016
4107
|
const lockPath = join(root, GOVERNANCE_LOCK_PATH);
|
|
@@ -4051,6 +4142,11 @@ var SCAFFOLD_PROJECT_YAML = `schema: aaac/project/0.1
|
|
|
4051
4142
|
# observability:
|
|
4052
4143
|
# event_mapping: ./bindings/observability.yaml
|
|
4053
4144
|
# recorder: aaac-observ
|
|
4145
|
+
# hooks:
|
|
4146
|
+
# # Each file declares gate commands per git hook; a non-zero exit
|
|
4147
|
+
# # blocks the commit/push. Only hooks listed in environments.git.hooks
|
|
4148
|
+
# # are materialized.
|
|
4149
|
+
# - ./bindings/git.yaml
|
|
4054
4150
|
`;
|
|
4055
4151
|
|
|
4056
4152
|
// src/cli/handlers.ts
|
|
@@ -4313,10 +4409,12 @@ var handleInit = async (projectRoot, options, parentOpts) => {
|
|
|
4313
4409
|
}
|
|
4314
4410
|
return;
|
|
4315
4411
|
}
|
|
4412
|
+
const hookCommands = await loadHookBindings(root, config.bindings?.hooks);
|
|
4316
4413
|
const result = await materializeHooks({
|
|
4317
4414
|
projectRoot: root,
|
|
4318
4415
|
environments: config.environments,
|
|
4319
|
-
observabilityBinding: config.bindings?.observability
|
|
4416
|
+
observabilityBinding: config.bindings?.observability,
|
|
4417
|
+
hookCommands
|
|
4320
4418
|
});
|
|
4321
4419
|
if (!quiet) {
|
|
4322
4420
|
for (const m of result.materialized) {
|
|
@@ -4361,10 +4459,12 @@ var handleUpdate = async (projectRoot, options, parentOpts) => {
|
|
|
4361
4459
|
}
|
|
4362
4460
|
return;
|
|
4363
4461
|
}
|
|
4462
|
+
const hookCommands = await loadHookBindings(root, config.bindings?.hooks);
|
|
4364
4463
|
const result = await materializeHooks({
|
|
4365
4464
|
projectRoot: root,
|
|
4366
4465
|
environments: config.environments,
|
|
4367
|
-
observabilityBinding: config.bindings?.observability
|
|
4466
|
+
observabilityBinding: config.bindings?.observability,
|
|
4467
|
+
hookCommands
|
|
4368
4468
|
});
|
|
4369
4469
|
if (!quiet) {
|
|
4370
4470
|
for (const m of result.materialized) {
|