avorelo 0.4.0-rc.1 → 0.4.0-rc.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "avorelo",
3
- "version": "0.4.0-rc.1",
3
+ "version": "0.4.0-rc.2",
4
4
  "private": false,
5
5
  "avoreloDistributionState": "ready_to_publish",
6
6
  "description": "Avorelo local-first activation, safety, and optimization runtime.",
@@ -19,12 +19,12 @@ const AVORELO_HOOK_MARKER = "_avorelo_managed";
19
19
  // The lifecycle-hook commands that Avorelo manages
20
20
  // Uses npx to resolve the binary from the project's node_modules
21
21
  const AVORELO_LIFECYCLE_COMMANDS = Object.freeze([
22
- { event: "SessionStart", command: "npx avorelo lifecycle-hook session-start --json", blocking: false },
23
- { event: "UserPromptSubmit", command: "npx avorelo lifecycle-hook user-prompt-submit --json", blocking: false },
24
- { event: "PreToolUse", command: "npx avorelo lifecycle-hook pre-tool-use --json", blocking: true },
25
- { event: "PostToolUse", command: "npx avorelo lifecycle-hook post-tool-use --json", blocking: false },
26
- { event: "Stop", command: "npx avorelo lifecycle-hook stop --json", blocking: true },
27
- { event: "SessionEnd", command: "npx avorelo lifecycle-hook session-end --json", blocking: false },
22
+ { event: "SessionStart", command: "npx avorelo lifecycle-hook session-start --json", blocking: false, matcher: "" },
23
+ { event: "UserPromptSubmit", command: "npx avorelo lifecycle-hook user-prompt-submit --json", blocking: false, matcher: "" },
24
+ { event: "PreToolUse", command: "npx avorelo lifecycle-hook pre-tool-use --json", blocking: true, matcher: "" },
25
+ { event: "PostToolUse", command: "npx avorelo lifecycle-hook post-tool-use --json", blocking: false, matcher: "" },
26
+ { event: "Stop", command: "npx avorelo lifecycle-hook stop --json", blocking: true, matcher: "" },
27
+ { event: "SessionEnd", command: "npx avorelo lifecycle-hook session-end --json", blocking: false, matcher: "" },
28
28
  ]);
29
29
 
30
30
  // Supported host configs
@@ -110,26 +110,31 @@ function parseExistingConfig(configAbsPath, format) {
110
110
 
111
111
  /**
112
112
  * Check if a hook entry is Avorelo-managed.
113
- * An entry is Avorelo-managed if its command matches one of our lifecycle commands.
113
+ * Supports both nested format ({matcher, hooks: [...]}) and flat format ({type, command}).
114
114
  */
115
115
  function isAvoreloManagedHook(entry) {
116
116
  if (!entry || typeof entry !== "object") return false;
117
- // Check marker field
118
117
  if (entry[AVORELO_HOOK_MARKER] === true) return true;
119
- // Check command match
118
+ if (Array.isArray(entry.hooks)) {
119
+ return entry.hooks.some((h) => h && typeof h.command === "string" && h.command.includes("avorelo lifecycle-hook"));
120
+ }
120
121
  const cmd = entry.command || "";
121
- return AVORELO_LIFECYCLE_COMMANDS.some((lc) => cmd.includes("lifecycle-hook"));
122
+ return cmd.includes("avorelo lifecycle-hook");
122
123
  }
123
124
 
124
125
  /**
125
126
  * Build the Avorelo hook entries for Claude Code settings.json format.
126
- * Each entry is tagged with _avorelo_managed for idempotent apply.
127
+ * Claude Code v2.1+ expects: {matcher: string, hooks: [{type, command}]}
127
128
  */
128
129
  function buildClaudeCodeHookEntries() {
129
130
  return AVORELO_LIFECYCLE_COMMANDS.map((lc) => ({
130
- type: "command",
131
- command: lc.command,
132
- blocking: lc.blocking,
131
+ matcher: lc.matcher,
132
+ hooks: [
133
+ {
134
+ type: "command",
135
+ command: lc.command,
136
+ },
137
+ ],
133
138
  [AVORELO_HOOK_MARKER]: true,
134
139
  _event: lc.event,
135
140
  }));
@@ -157,7 +162,6 @@ function mergeClaudeCodeConfig(existingData) {
157
162
  ? base.hooks
158
163
  : {};
159
164
 
160
- // Claude Code settings.json uses nested format: { hooks: { PreToolUse: [...], PostToolUse: [...] } }
161
165
  const merged = { ...existingHooks };
162
166
  const avoreloEntries = buildClaudeCodeHookEntries();
163
167
 
@@ -204,8 +208,9 @@ function removeAvoreloHooks(existingData, format) {
204
208
  : {};
205
209
  const cleaned = {};
206
210
  for (const [event, entries] of Object.entries(hooks)) {
207
- const kept = Array.isArray(entries) ? entries.filter((h) => !isAvoreloManagedHook(h)) : entries;
208
- if (Array.isArray(kept) && kept.length > 0) cleaned[event] = kept;
211
+ if (!Array.isArray(entries)) { cleaned[event] = entries; continue; }
212
+ const kept = entries.filter((h) => !isAvoreloManagedHook(h));
213
+ if (kept.length > 0) cleaned[event] = kept;
209
214
  }
210
215
  return { ...existingData, hooks: cleaned };
211
216
  }
@@ -546,35 +551,41 @@ function validateAppliedHooks(cwd, options = {}) {
546
551
  continue;
547
552
  }
548
553
 
549
- let flatHooks = [];
550
- if (Array.isArray(data.hooks)) {
551
- flatHooks = data.hooks;
552
- } else if (data.hooks && typeof data.hooks === "object") {
553
- for (const [event, entries] of Object.entries(data.hooks)) {
554
- if (Array.isArray(entries)) {
555
- for (const entry of entries) flatHooks.push({ ...entry, event });
554
+ const hooksByEvent = data.hooks && typeof data.hooks === "object" && !Array.isArray(data.hooks)
555
+ ? data.hooks : {};
556
+ let avoreloEventCount = 0;
557
+ const foundEvents = [];
558
+ const allEntries = [];
559
+
560
+ for (const [event, entries] of Object.entries(hooksByEvent)) {
561
+ if (!Array.isArray(entries)) continue;
562
+ for (const entry of entries) {
563
+ allEntries.push({ ...entry, _validationEvent: event });
564
+ if (isAvoreloManagedHook(entry)) {
565
+ avoreloEventCount++;
566
+ foundEvents.push(event);
556
567
  }
557
568
  }
558
569
  }
559
- const avoreloHooks = flatHooks.filter(isAvoreloManagedHook);
570
+
560
571
  const expectedCount = AVORELO_LIFECYCLE_COMMANDS.length;
561
572
  const missingEvents = AVORELO_LIFECYCLE_COMMANDS
562
- .filter((lc) => !avoreloHooks.some((h) => h.event === lc.event))
573
+ .filter((lc) => !foundEvents.includes(lc.event))
563
574
  .map((lc) => lc.event);
564
575
 
565
576
  const duplicates = AVORELO_LIFECYCLE_COMMANDS
566
- .filter((lc) => avoreloHooks.filter((h) => h.event === lc.event).length > 1)
577
+ .filter((lc) => foundEvents.filter((e) => e === lc.event).length > 1)
567
578
  .map((lc) => lc.event);
568
579
 
569
580
  validations.push({
570
581
  host: hostKey,
571
- status: avoreloHooks.length >= expectedCount && missingEvents.length === 0 ? "valid" : "incomplete",
582
+ status: avoreloEventCount >= expectedCount && missingEvents.length === 0 ? "valid" : "incomplete",
572
583
  configPath: hostConf.configPath,
573
- avoreloHookCount: avoreloHooks.length,
584
+ avoreloHookCount: avoreloEventCount,
574
585
  expectedCount,
575
586
  missingEvents,
576
587
  duplicates,
577
- userHookCount: flatHooks.length - avoreloHooks.length,
588
+ userHookCount: allEntries.length - avoreloEventCount,
578
589
  });
579
590
  }
580
591
 
@@ -788,18 +799,19 @@ function runHookDoctor(cwd, options = {}) {
788
799
  continue;
789
800
  }
790
801
 
802
+ const hooksByEvent = data.hooks && typeof data.hooks === "object" && !Array.isArray(data.hooks)
803
+ ? data.hooks : {};
804
+ const avoreloEvents = [];
791
805
  let flatHooks = [];
792
- if (Array.isArray(data.hooks)) {
793
- flatHooks = data.hooks;
794
- } else if (data.hooks && typeof data.hooks === "object") {
795
- for (const [event, entries] of Object.entries(data.hooks)) {
796
- if (Array.isArray(entries)) {
797
- for (const entry of entries) flatHooks.push({ ...entry, _event: event });
798
- }
806
+ for (const [event, entries] of Object.entries(hooksByEvent)) {
807
+ if (!Array.isArray(entries)) continue;
808
+ for (const entry of entries) {
809
+ flatHooks.push({ ...entry, _event: event });
810
+ if (isAvoreloManagedHook(entry)) avoreloEvents.push(event);
799
811
  }
800
812
  }
801
813
  const avoreloHooks = flatHooks.filter(isAvoreloManagedHook);
802
- const hasMissing = AVORELO_LIFECYCLE_COMMANDS.some((lc) => !avoreloHooks.some((h) => (h.event || h._event) === lc.event));
814
+ const hasMissing = AVORELO_LIFECYCLE_COMMANDS.some((lc) => !avoreloEvents.includes(lc.event));
803
815
 
804
816
  if (avoreloHooks.length === 0) {
805
817
  checks.push({ id: "hooks_installed", host: hostKey, status: "warn", message: "No Avorelo hooks found in config. Run `hooks apply --yes` to install." });