@aaac/contracts 0.1.10 → 0.1.12

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 CHANGED
@@ -10,11 +10,11 @@ import {
10
10
  parseComponentFile,
11
11
  validateComponent,
12
12
  writeGeneratedFiles
13
- } from "../chunk-PJ6U3DZV.js";
13
+ } from "../chunk-MWZFJ2VL.js";
14
14
 
15
15
  // src/cli/index.ts
16
16
  import { readFileSync } from "fs";
17
- import { dirname as dirname2, resolve as resolve2 } from "path";
17
+ import { dirname as dirname3, resolve as resolve3 } from "path";
18
18
  import { fileURLToPath } from "url";
19
19
 
20
20
  // ../../node_modules/commander/lib/error.js
@@ -3714,6 +3714,18 @@ function createProgram(handlers2, version) {
3714
3714
  }
3715
3715
  await handlers2.introspect(file, opts, globalOpts);
3716
3716
  });
3717
+ program3.command("init").description("Initialize a project: copy the CLI binary, scaffold project.yaml, and materialize git hooks").argument("[projectRoot]", "Project root directory (default: current directory)").option("--project-yaml <value>", "Path to project.yaml relative to projectRoot (default: project.yaml)").action(async (projectRoot, opts, cmd) => {
3718
+ const globalOpts = cmd.optsWithGlobals();
3719
+ await handlers2.init(projectRoot, opts, globalOpts);
3720
+ });
3721
+ program3.command("update").description("Re-read project.yaml and re-materialize git hooks").argument("[projectRoot]", "Project root directory (default: current directory)").option("--project-yaml <value>", "Path to project.yaml relative to projectRoot (default: project.yaml)").action(async (projectRoot, opts, cmd) => {
3722
+ const globalOpts = cmd.optsWithGlobals();
3723
+ await handlers2.update(projectRoot, opts, globalOpts);
3724
+ });
3725
+ program3.command("uninstall").description("Remove all managed hook blocks recorded in governance-manifest.lock").argument("[projectRoot]", "Project root directory (default: current directory)").action(async (projectRoot, opts, cmd) => {
3726
+ const globalOpts = cmd.optsWithGlobals();
3727
+ await handlers2.uninstall(projectRoot, opts, globalOpts);
3728
+ });
3717
3729
  program3.command("extract").description("Extract contract specification for this CLI tool.").argument("[commands...]", "Command IDs to extract. Use dot notation.").option("-a, --all", "Extract all commands.", false).option("--include-meta", "Include extraction metadata.", true).option("-F, --format <format>", "Output format (yaml or json).", "yaml").action(async (commands, opts, cmd) => {
3718
3730
  if (commands.length === 0 && !opts.all) {
3719
3731
  process.stderr.write(JSON.stringify({ code: "INVALID_ARGS", message: "Specify command IDs or use --all" }) + "\n");
@@ -3731,7 +3743,7 @@ function createProgram(handlers2, version) {
3731
3743
  type: "cli-contracts/extract",
3732
3744
  extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
3733
3745
  specVersion: doc.cli_contracts ?? "0.1.0",
3734
- commands: ["aaac.compile", "aaac.generate", "aaac.validate", "aaac.introspect"]
3746
+ commands: ["aaac.compile", "aaac.generate", "aaac.validate", "aaac.introspect", "aaac.init", "aaac.update", "aaac.uninstall"]
3735
3747
  };
3736
3748
  }
3737
3749
  Object.assign(out, doc);
@@ -3748,7 +3760,7 @@ function createProgram(handlers2, version) {
3748
3760
  yamlLines.push("extractedAt: " + (/* @__PURE__ */ new Date()).toISOString());
3749
3761
  yamlLines.push("spec_version: " + (doc.cli_contracts ?? "0.1.0"));
3750
3762
  yamlLines.push("commands:");
3751
- for (const id of ["aaac.compile", "aaac.generate", "aaac.validate", "aaac.introspect"]) {
3763
+ for (const id of ["aaac.compile", "aaac.generate", "aaac.validate", "aaac.introspect", "aaac.init", "aaac.update", "aaac.uninstall"]) {
3752
3764
  yamlLines.push(" - " + id);
3753
3765
  }
3754
3766
  }
@@ -3788,9 +3800,260 @@ function createProgram(handlers2, version) {
3788
3800
  }
3789
3801
 
3790
3802
  // src/cli/handlers.ts
3791
- import { mkdir, writeFile } from "fs/promises";
3792
- import { dirname, join, resolve } from "path";
3803
+ import { existsSync as existsSync2 } from "fs";
3804
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
3805
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
3793
3806
  import { stringify as stringifyYaml } from "yaml";
3807
+
3808
+ // src/cli/governance.ts
3809
+ import { createHash } from "crypto";
3810
+ import { existsSync } from "fs";
3811
+ import { chmod, mkdir, readFile, writeFile } from "fs/promises";
3812
+ import { dirname, join, resolve } from "path";
3813
+ import { parse as parseYaml } from "yaml";
3814
+ import { z } from "zod";
3815
+ var MANAGED_BLOCK_START = "# >>> aaac-governance:managed (do not edit) >>>";
3816
+ var MANAGED_BLOCK_END = "# <<< aaac-governance:managed <<<";
3817
+ var GOVERNANCE_LOCK_PATH = ".agent-logs/governance-manifest.lock";
3818
+ var DEFAULT_RECORDER = ".cursor/hooks/observ-record.sh";
3819
+ var DEFAULT_CURSOR_HOOKS_JSON = ".cursor/hooks.json";
3820
+ var DEFAULT_BINARY_DEST = "bin/aaac.js";
3821
+ var EnvironmentsSchema = z.object({
3822
+ git: z.object({ hooks: z.array(z.string()).optional() }).optional(),
3823
+ cursor: z.object({ hooks_json: z.string().optional() }).optional()
3824
+ }).optional();
3825
+ var BindingsObservabilitySchema = z.object({
3826
+ event_mapping: z.string().optional(),
3827
+ recorder: z.string().optional()
3828
+ }).optional();
3829
+ var BindingsSchema = z.object({
3830
+ observability: BindingsObservabilitySchema
3831
+ }).optional();
3832
+ var InitProjectConfigSchema = z.object({
3833
+ environments: EnvironmentsSchema,
3834
+ bindings: BindingsSchema
3835
+ });
3836
+ async function loadInitConfig(configPath) {
3837
+ const absPath = resolve(configPath);
3838
+ const raw = await readFile(absPath, "utf8");
3839
+ const parsed = parseYaml(raw);
3840
+ return InitProjectConfigSchema.parse(parsed ?? {});
3841
+ }
3842
+ function escapeRegex(value) {
3843
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3844
+ }
3845
+ function createManagedBlockRegex() {
3846
+ return new RegExp(
3847
+ `${escapeRegex(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegex(MANAGED_BLOCK_END)}\\n?`,
3848
+ "g"
3849
+ );
3850
+ }
3851
+ function buildManagedBlock(blockContent) {
3852
+ return `${MANAGED_BLOCK_START}
3853
+ ${blockContent.trimEnd()}
3854
+ ${MANAGED_BLOCK_END}
3855
+ `;
3856
+ }
3857
+ function injectManagedBlock(existingContent, blockContent) {
3858
+ const managedBlock = buildManagedBlock(blockContent);
3859
+ const re = createManagedBlockRegex();
3860
+ if (re.test(existingContent)) {
3861
+ return existingContent.replace(createManagedBlockRegex(), managedBlock);
3862
+ }
3863
+ const trimmed = existingContent.replace(/\s+$/, "");
3864
+ if (trimmed.length === 0) {
3865
+ return `#!/bin/sh
3866
+ ${managedBlock}`;
3867
+ }
3868
+ const separator = trimmed.endsWith("\n") ? "" : "\n";
3869
+ return `${trimmed}${separator}${managedBlock}`;
3870
+ }
3871
+ function removeManagedBlock(existingContent) {
3872
+ if (!existingContent.includes(MANAGED_BLOCK_START)) {
3873
+ return existingContent;
3874
+ }
3875
+ const withoutBlock = existingContent.replace(createManagedBlockRegex(), "");
3876
+ const meaningful = withoutBlock.split("\n").filter((line) => {
3877
+ const t = line.trim();
3878
+ return t.length > 0 && t !== "#!/bin/sh" && t !== "#!/bin/bash";
3879
+ });
3880
+ if (meaningful.length === 0) {
3881
+ return "";
3882
+ }
3883
+ return withoutBlock.replace(/\n{3,}/g, "\n\n").replace(/\s+$/, "\n");
3884
+ }
3885
+ function mergeCursorHooks(existing, newEntries) {
3886
+ const byCommand = /* @__PURE__ */ new Map();
3887
+ for (const entry of existing) byCommand.set(entry.command, entry);
3888
+ for (const entry of newEntries) byCommand.set(entry.command, entry);
3889
+ return Array.from(byCommand.values());
3890
+ }
3891
+ function removeCursorHooks(existing, commands) {
3892
+ const removeSet = new Set(commands);
3893
+ return existing.filter((entry) => !removeSet.has(entry.command));
3894
+ }
3895
+ function buildDefaultCursorHooks(recorder) {
3896
+ return [
3897
+ { event: "beforeSubmitPrompt", command: `${recorder} beforeSubmitPrompt` },
3898
+ { event: "afterShellExecution", command: `${recorder} afterShellExecution` }
3899
+ ];
3900
+ }
3901
+ async function ensureParentDir(filePath) {
3902
+ await mkdir(dirname(filePath), { recursive: true });
3903
+ }
3904
+ async function readTextFileIfExists(filePath) {
3905
+ if (!existsSync(filePath)) return void 0;
3906
+ return readFile(filePath, "utf8");
3907
+ }
3908
+ async function writeGovernanceLock(projectRoot, entries) {
3909
+ const lockPath = join(projectRoot, GOVERNANCE_LOCK_PATH);
3910
+ const lock = {
3911
+ materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
3912
+ entries
3913
+ };
3914
+ await ensureParentDir(lockPath);
3915
+ await writeFile(lockPath, JSON.stringify(lock, null, 2) + "\n", "utf8");
3916
+ return GOVERNANCE_LOCK_PATH;
3917
+ }
3918
+ async function readGovernanceLock(projectRoot) {
3919
+ const lockPath = join(projectRoot, GOVERNANCE_LOCK_PATH);
3920
+ if (!existsSync(lockPath)) return void 0;
3921
+ const raw = await readFile(lockPath, "utf8");
3922
+ if (!raw.trim()) return void 0;
3923
+ return JSON.parse(raw);
3924
+ }
3925
+ async function materializeHooks(options) {
3926
+ const projectRoot = resolve(options.projectRoot);
3927
+ const result = { materialized: [], lockEntries: [] };
3928
+ const recorder = options.observabilityBinding?.recorder ?? DEFAULT_RECORDER;
3929
+ if (options.environments.git?.hooks?.length) {
3930
+ for (const hookName of options.environments.git.hooks) {
3931
+ const relPath = join(".git/hooks", hookName);
3932
+ const absPath = join(projectRoot, relPath);
3933
+ const blockContent = `exec ${recorder} ${hookName} "$@"`;
3934
+ const existing = await readTextFileIfExists(absPath) ?? "";
3935
+ const updated = injectManagedBlock(existing, blockContent);
3936
+ let action = "unchanged";
3937
+ if (!existsSync(absPath)) {
3938
+ action = "created";
3939
+ } else if (updated !== existing) {
3940
+ action = "updated";
3941
+ }
3942
+ if (action !== "unchanged") {
3943
+ await ensureParentDir(absPath);
3944
+ await writeFile(absPath, updated, "utf8");
3945
+ await chmod(absPath, 493);
3946
+ }
3947
+ result.materialized.push({ path: relPath, action });
3948
+ result.lockEntries.push({ path: relPath, type: "git-hook" });
3949
+ }
3950
+ }
3951
+ if (options.environments.cursor) {
3952
+ const rawHooksJsonValue = options.environments.cursor.hooks_json;
3953
+ const hooksJsonPath = rawHooksJsonValue ? resolve(projectRoot, rawHooksJsonValue) : join(projectRoot, DEFAULT_CURSOR_HOOKS_JSON);
3954
+ const relPath = hooksJsonPath.startsWith(projectRoot + "/") ? hooksJsonPath.slice(projectRoot.length + 1) : DEFAULT_CURSOR_HOOKS_JSON;
3955
+ const newEntries = buildDefaultCursorHooks(recorder);
3956
+ const existingRaw = await readTextFileIfExists(hooksJsonPath);
3957
+ const existing = existingRaw ? JSON.parse(existingRaw) : [];
3958
+ const merged = mergeCursorHooks(existing, newEntries);
3959
+ const serialized = JSON.stringify(merged, null, 2) + "\n";
3960
+ let action = "unchanged";
3961
+ if (!existsSync(hooksJsonPath)) {
3962
+ action = "created";
3963
+ } else if (serialized !== existingRaw) {
3964
+ action = "updated";
3965
+ }
3966
+ if (action !== "unchanged") {
3967
+ await ensureParentDir(hooksJsonPath);
3968
+ await writeFile(hooksJsonPath, serialized, "utf8");
3969
+ }
3970
+ result.materialized.push({ path: relPath, action });
3971
+ result.lockEntries.push({ path: relPath, type: "cursor-hook" });
3972
+ }
3973
+ if (result.lockEntries.length > 0) {
3974
+ const lockExisted = existsSync(join(projectRoot, GOVERNANCE_LOCK_PATH));
3975
+ await writeGovernanceLock(projectRoot, result.lockEntries);
3976
+ result.materialized.push({
3977
+ path: GOVERNANCE_LOCK_PATH,
3978
+ action: lockExisted ? "updated" : "created"
3979
+ });
3980
+ result.lockEntries.push({ path: GOVERNANCE_LOCK_PATH, type: "generated" });
3981
+ }
3982
+ return result;
3983
+ }
3984
+ async function unmaterializeHooks(projectRoot, lockEntries) {
3985
+ const root = resolve(projectRoot);
3986
+ for (const entry of lockEntries) {
3987
+ if (entry.type === "generated") {
3988
+ const absPath = join(root, entry.path);
3989
+ if (existsSync(absPath)) {
3990
+ await writeFile(absPath, "", "utf8");
3991
+ }
3992
+ continue;
3993
+ }
3994
+ if (entry.type === "git-hook") {
3995
+ const absPath = join(root, entry.path);
3996
+ const existing = await readTextFileIfExists(absPath);
3997
+ if (existing === void 0) continue;
3998
+ const updated = removeManagedBlock(existing);
3999
+ await writeFile(absPath, updated, "utf8");
4000
+ continue;
4001
+ }
4002
+ if (entry.type === "cursor-hook") {
4003
+ const absPath = join(root, entry.path);
4004
+ const existingRaw = await readTextFileIfExists(absPath);
4005
+ if (existingRaw === void 0) continue;
4006
+ const existing = JSON.parse(existingRaw);
4007
+ const commandsToRemove = buildDefaultCursorHooks(DEFAULT_RECORDER).map((h) => h.command);
4008
+ const updated = removeCursorHooks(existing, commandsToRemove);
4009
+ await writeFile(
4010
+ absPath,
4011
+ updated.length === 0 ? "[]\n" : JSON.stringify(updated, null, 2) + "\n",
4012
+ "utf8"
4013
+ );
4014
+ }
4015
+ }
4016
+ const lockPath = join(root, GOVERNANCE_LOCK_PATH);
4017
+ if (existsSync(lockPath)) {
4018
+ await writeFile(lockPath, "", "utf8");
4019
+ }
4020
+ }
4021
+ async function copyCLIBinary(srcPath, projectRoot, destRelPath = DEFAULT_BINARY_DEST) {
4022
+ const dstPath = join(projectRoot, destRelPath);
4023
+ const srcContent = await readFile(srcPath);
4024
+ const srcHash = createHash("sha256").update(srcContent).digest("hex");
4025
+ if (existsSync(dstPath)) {
4026
+ const dstContent = await readFile(dstPath);
4027
+ const dstHash = createHash("sha256").update(dstContent).digest("hex");
4028
+ if (srcHash === dstHash) {
4029
+ return { path: destRelPath, action: "unchanged" };
4030
+ }
4031
+ await writeFile(dstPath, srcContent);
4032
+ await chmod(dstPath, 493);
4033
+ return { path: destRelPath, action: "updated" };
4034
+ }
4035
+ await ensureParentDir(dstPath);
4036
+ await writeFile(dstPath, srcContent);
4037
+ await chmod(dstPath, 493);
4038
+ return { path: destRelPath, action: "created" };
4039
+ }
4040
+ var SCAFFOLD_PROJECT_YAML = `schema: aaac/project/0.1
4041
+
4042
+ # Uncomment and configure to enable git hook governance:
4043
+ #
4044
+ # environments:
4045
+ # git:
4046
+ # hooks: [pre-commit, pre-push, post-commit]
4047
+ # cursor:
4048
+ # hooks_json: .cursor/hooks.json
4049
+ #
4050
+ # bindings:
4051
+ # observability:
4052
+ # event_mapping: ./bindings/observability.yaml
4053
+ # recorder: aaac-observ
4054
+ `;
4055
+
4056
+ // src/cli/handlers.ts
3794
4057
  function formatError(err, file) {
3795
4058
  if (err instanceof ComponentParseError) {
3796
4059
  return err.message;
@@ -3818,10 +4081,10 @@ function resolveComponentPath(file) {
3818
4081
  if (!file) {
3819
4082
  fail("Error: component file path is required");
3820
4083
  }
3821
- return resolve(file);
4084
+ return resolve2(file);
3822
4085
  }
3823
4086
  function defaultOutputDir(componentPath) {
3824
- return join(dirname(componentPath), "generated");
4087
+ return join2(dirname2(componentPath), "generated");
3825
4088
  }
3826
4089
  function buildIntrospectionDump(ir) {
3827
4090
  const operations = {};
@@ -3856,10 +4119,10 @@ var handleCompile = async (file, options, parentOpts) => {
3856
4119
  const ir = await compileComponentFile(componentPath);
3857
4120
  const json = JSON.stringify(ir, null, 2);
3858
4121
  if (options.output) {
3859
- await writeFile(resolve(options.output), `${json}
4122
+ await writeFile2(resolve2(options.output), `${json}
3860
4123
  `, "utf-8");
3861
4124
  if (!parentOpts.quiet) {
3862
- process.stdout.write(`Wrote IR to ${resolve(options.output)}
4125
+ process.stdout.write(`Wrote IR to ${resolve2(options.output)}
3863
4126
  `);
3864
4127
  }
3865
4128
  return;
@@ -3874,7 +4137,7 @@ async function resolveEmbeddedDslData(ir, componentPath) {
3874
4137
  if (!ir.embeddedDslDir || !ir.implementation) {
3875
4138
  return void 0;
3876
4139
  }
3877
- const implPath = resolve(dirname(componentPath), ir.implementation);
4140
+ const implPath = resolve2(dirname2(componentPath), ir.implementation);
3878
4141
  const { resolve: resolveDsl } = await import("agent-contracts");
3879
4142
  const dslResult = await resolveDsl(implPath);
3880
4143
  return dslResult.data;
@@ -3893,7 +4156,7 @@ function splitGeneratedFiles(files) {
3893
4156
  }
3894
4157
  var handleGenerate = async (file, options, parentOpts) => {
3895
4158
  const componentPath = resolveComponentPath(file);
3896
- const outputDir = resolve(options.outputDir ?? defaultOutputDir(componentPath));
4159
+ const outputDir = resolve2(options.outputDir ?? defaultOutputDir(componentPath));
3897
4160
  const dryRun = Boolean(options.dryRun);
3898
4161
  try {
3899
4162
  const ir = await compileComponentFile(componentPath);
@@ -3918,10 +4181,10 @@ var handleGenerate = async (file, options, parentOpts) => {
3918
4181
  }
3919
4182
  }
3920
4183
  for (const generated of embedded) {
3921
- const embeddedDir = resolve(dirname(componentPath), generated.targetDir);
4184
+ const embeddedDir = resolve2(dirname2(componentPath), generated.targetDir);
3922
4185
  const action = generated.overwrite ? "write" : "create";
3923
4186
  process.stdout.write(
3924
- ` ${action}: ${join(embeddedDir, generated.path)} (embedded DSL)
4187
+ ` ${action}: ${join2(embeddedDir, generated.path)} (embedded DSL)
3925
4188
  `
3926
4189
  );
3927
4190
  }
@@ -3930,10 +4193,10 @@ var handleGenerate = async (file, options, parentOpts) => {
3930
4193
  }
3931
4194
  const result = await writeGeneratedFiles(main, outputDir);
3932
4195
  for (const generated of embedded) {
3933
- const embeddedDir = resolve(dirname(componentPath), generated.targetDir);
3934
- await mkdir(embeddedDir, { recursive: true });
3935
- await writeFile(join(embeddedDir, generated.path), generated.content, "utf-8");
3936
- result.written.push(join(generated.targetDir, generated.path));
4196
+ const embeddedDir = resolve2(dirname2(componentPath), generated.targetDir);
4197
+ await mkdir2(embeddedDir, { recursive: true });
4198
+ await writeFile2(join2(embeddedDir, generated.path), generated.content, "utf-8");
4199
+ result.written.push(join2(generated.targetDir, generated.path));
3937
4200
  }
3938
4201
  if (!parentOpts.quiet) {
3939
4202
  if (result.written.length > 0) {
@@ -3963,7 +4226,7 @@ var handleGenerate = async (file, options, parentOpts) => {
3963
4226
  };
3964
4227
  var handleValidate = async (file, options, parentOpts) => {
3965
4228
  const componentPath = resolveComponentPath(file);
3966
- const basePath = dirname(componentPath);
4229
+ const basePath = dirname2(componentPath);
3967
4230
  try {
3968
4231
  const dsl = await parseComponentFile(componentPath);
3969
4232
  const ir = compileComponent(dsl, { basePath });
@@ -3995,17 +4258,156 @@ var handleIntrospect = async (file, options, _parentOpts) => {
3995
4258
  fail(formatError(err, componentPath));
3996
4259
  }
3997
4260
  };
4261
+ var DEFAULT_PROJECT_YAML = "project.yaml";
4262
+ var handleInit = async (projectRoot, options, parentOpts) => {
4263
+ const root = resolve2(projectRoot ?? ".");
4264
+ const quiet = Boolean(parentOpts.quiet);
4265
+ const projectYamlPath = join2(root, options.projectYaml ?? DEFAULT_PROJECT_YAML);
4266
+ const srcBinary = resolve2(options.binaryPath ?? process.argv[1]);
4267
+ const binaryResult = await copyCLIBinary(srcBinary, root);
4268
+ if (!quiet && binaryResult.action !== "unchanged") {
4269
+ process.stdout.write(` ${binaryResult.action}: ${binaryResult.path}
4270
+ `);
4271
+ }
4272
+ if (!existsSync2(projectYamlPath)) {
4273
+ await writeFile2(projectYamlPath, SCAFFOLD_PROJECT_YAML, "utf8");
4274
+ if (!quiet) {
4275
+ process.stdout.write(` created: ${DEFAULT_PROJECT_YAML}
4276
+ `);
4277
+ }
4278
+ }
4279
+ const gitDir = join2(root, ".git");
4280
+ if (!existsSync2(gitDir)) {
4281
+ process.stderr.write(
4282
+ `Warning: .git/ not found in ${root}; skipping hook materialization
4283
+ `
4284
+ );
4285
+ if (!quiet) {
4286
+ process.stdout.write(`Initialized project at ${root}
4287
+ `);
4288
+ }
4289
+ return;
4290
+ }
4291
+ let config;
4292
+ try {
4293
+ config = await loadInitConfig(projectYamlPath);
4294
+ } catch (err) {
4295
+ process.stderr.write(
4296
+ `Warning: Could not parse ${DEFAULT_PROJECT_YAML}: ${err instanceof Error ? err.message : String(err)}
4297
+ `
4298
+ );
4299
+ if (!quiet) {
4300
+ process.stdout.write(`Initialized project at ${root}
4301
+ `);
4302
+ }
4303
+ return;
4304
+ }
4305
+ if (!config.environments) {
4306
+ if (!quiet) {
4307
+ process.stdout.write(
4308
+ ` no environments configured in ${DEFAULT_PROJECT_YAML}; skipping hooks
4309
+ `
4310
+ );
4311
+ process.stdout.write(`Initialized project at ${root}
4312
+ `);
4313
+ }
4314
+ return;
4315
+ }
4316
+ const result = await materializeHooks({
4317
+ projectRoot: root,
4318
+ environments: config.environments,
4319
+ observabilityBinding: config.bindings?.observability
4320
+ });
4321
+ if (!quiet) {
4322
+ for (const m of result.materialized) {
4323
+ if (m.action !== "unchanged") {
4324
+ process.stdout.write(` ${m.action}: ${m.path}
4325
+ `);
4326
+ }
4327
+ }
4328
+ process.stdout.write(`Initialized project at ${root}
4329
+ `);
4330
+ }
4331
+ };
4332
+ var handleUpdate = async (projectRoot, options, parentOpts) => {
4333
+ const root = resolve2(projectRoot ?? ".");
4334
+ const quiet = Boolean(parentOpts.quiet);
4335
+ const projectYamlPath = join2(root, options.projectYaml ?? DEFAULT_PROJECT_YAML);
4336
+ if (!existsSync2(projectYamlPath)) {
4337
+ fail(
4338
+ `Error: ${DEFAULT_PROJECT_YAML} not found at ${root}. Run 'aaac init' first.`
4339
+ );
4340
+ }
4341
+ const gitDir = join2(root, ".git");
4342
+ if (!existsSync2(gitDir)) {
4343
+ process.stderr.write(
4344
+ `Warning: .git/ not found in ${root}; skipping hook materialization
4345
+ `
4346
+ );
4347
+ return;
4348
+ }
4349
+ let config;
4350
+ try {
4351
+ config = await loadInitConfig(projectYamlPath);
4352
+ } catch (err) {
4353
+ fail(
4354
+ `Error: Could not parse ${DEFAULT_PROJECT_YAML}: ${err instanceof Error ? err.message : String(err)}`
4355
+ );
4356
+ }
4357
+ if (!config.environments) {
4358
+ if (!quiet) {
4359
+ process.stdout.write(`No environments configured in ${DEFAULT_PROJECT_YAML}
4360
+ `);
4361
+ }
4362
+ return;
4363
+ }
4364
+ const result = await materializeHooks({
4365
+ projectRoot: root,
4366
+ environments: config.environments,
4367
+ observabilityBinding: config.bindings?.observability
4368
+ });
4369
+ if (!quiet) {
4370
+ for (const m of result.materialized) {
4371
+ process.stdout.write(` ${m.action}: ${m.path}
4372
+ `);
4373
+ }
4374
+ process.stdout.write(`Updated hooks at ${root}
4375
+ `);
4376
+ }
4377
+ };
4378
+ var handleUninstall = async (projectRoot, _options, parentOpts) => {
4379
+ const root = resolve2(projectRoot ?? ".");
4380
+ const quiet = Boolean(parentOpts.quiet);
4381
+ const lock = await readGovernanceLock(root);
4382
+ if (!lock || lock.entries.length === 0) {
4383
+ if (!quiet) {
4384
+ process.stdout.write(
4385
+ `Nothing to uninstall (no governance lock found at ${join2(root, GOVERNANCE_LOCK_PATH)})
4386
+ `
4387
+ );
4388
+ }
4389
+ return;
4390
+ }
4391
+ await unmaterializeHooks(root, lock.entries);
4392
+ if (!quiet) {
4393
+ process.stdout.write(`Uninstalled governance hooks from ${root}
4394
+ `);
4395
+ }
4396
+ };
3998
4397
  var handlers = {
3999
4398
  compile: handleCompile,
4000
4399
  generate: handleGenerate,
4001
4400
  validate: handleValidate,
4002
- introspect: handleIntrospect
4401
+ introspect: handleIntrospect,
4402
+ init: handleInit,
4403
+ update: handleUpdate,
4404
+ uninstall: handleUninstall
4003
4405
  };
4004
4406
 
4005
4407
  // src/cli/index.ts
4006
- var __dirname = dirname2(fileURLToPath(import.meta.url));
4408
+ var __dirname = dirname3(fileURLToPath(import.meta.url));
4007
4409
  var pkg = JSON.parse(
4008
- readFileSync(resolve2(__dirname, "../../package.json"), "utf8")
4410
+ readFileSync(resolve3(__dirname, "../../package.json"), "utf8")
4009
4411
  );
4010
4412
  var program2 = createProgram(handlers, pkg.version);
4011
4413
  await program2.parseAsync();