@agentmonitors/cli 0.3.2 → 0.4.0

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 CHANGED
@@ -2020,13 +2020,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
2020
2020
  * @param {string} source - expected values are default/config/env/cli/implied
2021
2021
  * @return {Command} `this` command for chaining
2022
2022
  */
2023
- setOptionValueWithSource(key, value, source5) {
2023
+ setOptionValueWithSource(key, value, source6) {
2024
2024
  if (this._storeOptionsAsProperties) {
2025
2025
  this[key] = value;
2026
2026
  } else {
2027
2027
  this._optionValues[key] = value;
2028
2028
  }
2029
- this._optionValueSources[key] = source5;
2029
+ this._optionValueSources[key] = source6;
2030
2030
  return this;
2031
2031
  }
2032
2032
  /**
@@ -2047,13 +2047,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
2047
2047
  * @return {string}
2048
2048
  */
2049
2049
  getOptionValueSourceWithGlobals(key) {
2050
- let source5;
2050
+ let source6;
2051
2051
  this._getCommandAndAncestors().forEach((cmd) => {
2052
2052
  if (cmd.getOptionValueSource(key) !== void 0) {
2053
- source5 = cmd.getOptionValueSource(key);
2053
+ source6 = cmd.getOptionValueSource(key);
2054
2054
  }
2055
2055
  });
2056
- return source5;
2056
+ return source6;
2057
2057
  }
2058
2058
  /**
2059
2059
  * Get user arguments from implied or explicit arguments.
@@ -2922,8 +2922,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
2922
2922
  const getErrorMessage = (option2) => {
2923
2923
  const bestOption = findBestOptionFromValue(option2);
2924
2924
  const optionKey = bestOption.attributeName();
2925
- const source5 = this.getOptionValueSource(optionKey);
2926
- if (source5 === "env") {
2925
+ const source6 = this.getOptionValueSource(optionKey);
2926
+ if (source6 === "env") {
2927
2927
  return `environment variable '${bestOption.envVar}'`;
2928
2928
  }
2929
2929
  return `option '${bestOption.flags}'`;
@@ -3773,13 +3773,13 @@ var require_common = __commonJS({
3773
3773
  else if (isNothing(sequence)) return [];
3774
3774
  return [sequence];
3775
3775
  }
3776
- function extend2(target, source5) {
3776
+ function extend2(target, source6) {
3777
3777
  var index, length, key, sourceKeys;
3778
- if (source5) {
3779
- sourceKeys = Object.keys(source5);
3778
+ if (source6) {
3779
+ sourceKeys = Object.keys(source6);
3780
3780
  for (index = 0, length = sourceKeys.length; index < length; index += 1) {
3781
3781
  key = sourceKeys[index];
3782
- target[key] = source5[key];
3782
+ target[key] = source6[key];
3783
3783
  }
3784
3784
  }
3785
3785
  return target;
@@ -4837,7 +4837,7 @@ var require_function = __commonJS({
4837
4837
  function resolveJavascriptFunction(data) {
4838
4838
  if (data === null) return false;
4839
4839
  try {
4840
- var source5 = "(" + data + ")", ast = esprima.parse(source5, { range: true });
4840
+ var source6 = "(" + data + ")", ast = esprima.parse(source6, { range: true });
4841
4841
  if (ast.type !== "Program" || ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement" || ast.body[0].expression.type !== "ArrowFunctionExpression" && ast.body[0].expression.type !== "FunctionExpression") {
4842
4842
  return false;
4843
4843
  }
@@ -4847,7 +4847,7 @@ var require_function = __commonJS({
4847
4847
  }
4848
4848
  }
4849
4849
  function constructJavascriptFunction(data) {
4850
- var source5 = "(" + data + ")", ast = esprima.parse(source5, { range: true }), params = [], body;
4850
+ var source6 = "(" + data + ")", ast = esprima.parse(source6, { range: true }), params = [], body;
4851
4851
  if (ast.type !== "Program" || ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement" || ast.body[0].expression.type !== "ArrowFunctionExpression" && ast.body[0].expression.type !== "FunctionExpression") {
4852
4852
  throw new Error("Failed to resolve function");
4853
4853
  }
@@ -4856,9 +4856,9 @@ var require_function = __commonJS({
4856
4856
  });
4857
4857
  body = ast.body[0].expression.body.range;
4858
4858
  if (ast.body[0].expression.body.type === "BlockStatement") {
4859
- return new Function(params, source5.slice(body[0] + 1, body[1] - 1));
4859
+ return new Function(params, source6.slice(body[0] + 1, body[1] - 1));
4860
4860
  }
4861
- return new Function(params, "return " + source5.slice(body[0], body[1]));
4861
+ return new Function(params, "return " + source6.slice(body[0], body[1]));
4862
4862
  }
4863
4863
  function representJavascriptFunction(object3) {
4864
4864
  return object3.toString();
@@ -5084,16 +5084,16 @@ var require_loader = __commonJS({
5084
5084
  state.result += _result;
5085
5085
  }
5086
5086
  }
5087
- function mergeMappings(state, destination, source5, overridableKeys) {
5087
+ function mergeMappings(state, destination, source6, overridableKeys) {
5088
5088
  var sourceKeys, key, index, quantity;
5089
- if (!common.isObject(source5)) {
5089
+ if (!common.isObject(source6)) {
5090
5090
  throwError(state, "cannot merge mappings; the provided source object is unacceptable");
5091
5091
  }
5092
- sourceKeys = Object.keys(source5);
5092
+ sourceKeys = Object.keys(source6);
5093
5093
  for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
5094
5094
  key = sourceKeys[index];
5095
5095
  if (!_hasOwnProperty.call(destination, key)) {
5096
- setProperty(destination, key, source5[key]);
5096
+ setProperty(destination, key, source6[key]);
5097
5097
  overridableKeys[key] = true;
5098
5098
  }
5099
5099
  }
@@ -10581,10 +10581,10 @@ var require_fast_uri = __commonJS({
10581
10581
  function normalize2(uri2, options2) {
10582
10582
  if (typeof uri2 === "string") {
10583
10583
  uri2 = /** @type {T} */
10584
- serialize(parse4(uri2, options2), options2);
10584
+ serialize2(parse4(uri2, options2), options2);
10585
10585
  } else if (typeof uri2 === "object") {
10586
10586
  uri2 = /** @type {T} */
10587
- parse4(serialize(uri2, options2), options2);
10587
+ parse4(serialize2(uri2, options2), options2);
10588
10588
  }
10589
10589
  return uri2;
10590
10590
  }
@@ -10592,13 +10592,13 @@ var require_fast_uri = __commonJS({
10592
10592
  const schemelessOptions = options2 ? Object.assign({ scheme: "null" }, options2) : { scheme: "null" };
10593
10593
  const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
10594
10594
  schemelessOptions.skipEscape = true;
10595
- return serialize(resolved, schemelessOptions);
10595
+ return serialize2(resolved, schemelessOptions);
10596
10596
  }
10597
10597
  function resolveComponent(base, relative, options2, skipNormalization) {
10598
10598
  const target = {};
10599
10599
  if (!skipNormalization) {
10600
- base = parse4(serialize(base, options2), options2);
10601
- relative = parse4(serialize(relative, options2), options2);
10600
+ base = parse4(serialize2(base, options2), options2);
10601
+ relative = parse4(serialize2(relative, options2), options2);
10602
10602
  }
10603
10603
  options2 = options2 || {};
10604
10604
  if (!options2.tolerant && relative.scheme) {
@@ -10650,19 +10650,19 @@ var require_fast_uri = __commonJS({
10650
10650
  function equal(uriA, uriB, options2) {
10651
10651
  if (typeof uriA === "string") {
10652
10652
  uriA = unescape(uriA);
10653
- uriA = serialize(normalizeComponentEncoding(parse4(uriA, options2), true), { ...options2, skipEscape: true });
10653
+ uriA = serialize2(normalizeComponentEncoding(parse4(uriA, options2), true), { ...options2, skipEscape: true });
10654
10654
  } else if (typeof uriA === "object") {
10655
- uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options2, skipEscape: true });
10655
+ uriA = serialize2(normalizeComponentEncoding(uriA, true), { ...options2, skipEscape: true });
10656
10656
  }
10657
10657
  if (typeof uriB === "string") {
10658
10658
  uriB = unescape(uriB);
10659
- uriB = serialize(normalizeComponentEncoding(parse4(uriB, options2), true), { ...options2, skipEscape: true });
10659
+ uriB = serialize2(normalizeComponentEncoding(parse4(uriB, options2), true), { ...options2, skipEscape: true });
10660
10660
  } else if (typeof uriB === "object") {
10661
- uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options2, skipEscape: true });
10661
+ uriB = serialize2(normalizeComponentEncoding(uriB, true), { ...options2, skipEscape: true });
10662
10662
  }
10663
10663
  return uriA.toLowerCase() === uriB.toLowerCase();
10664
10664
  }
10665
- function serialize(cmpts, opts) {
10665
+ function serialize2(cmpts, opts) {
10666
10666
  const component = {
10667
10667
  host: cmpts.host,
10668
10668
  scheme: cmpts.scheme,
@@ -10818,7 +10818,7 @@ var require_fast_uri = __commonJS({
10818
10818
  resolve,
10819
10819
  resolveComponent,
10820
10820
  equal,
10821
- serialize,
10821
+ serialize: serialize2,
10822
10822
  parse: parse4
10823
10823
  };
10824
10824
  module2.exports = fastUri;
@@ -11553,12 +11553,12 @@ var require_ref = __commonJS({
11553
11553
  function callSyncRef() {
11554
11554
  cxt.result((0, code_1.callValidateCode)(cxt, v, passCxt), () => addEvaluatedFrom(v), () => addErrorsFrom(v));
11555
11555
  }
11556
- function addErrorsFrom(source5) {
11557
- const errs = (0, codegen_1._)`${source5}.errors`;
11556
+ function addErrorsFrom(source6) {
11557
+ const errs = (0, codegen_1._)`${source6}.errors`;
11558
11558
  gen.assign(names_1.default.vErrors, (0, codegen_1._)`${names_1.default.vErrors} === null ? ${errs} : ${names_1.default.vErrors}.concat(${errs})`);
11559
11559
  gen.assign(names_1.default.errors, (0, codegen_1._)`${names_1.default.vErrors}.length`);
11560
11560
  }
11561
- function addEvaluatedFrom(source5) {
11561
+ function addEvaluatedFrom(source6) {
11562
11562
  var _a2;
11563
11563
  if (!it.opts.unevaluated)
11564
11564
  return;
@@ -11569,7 +11569,7 @@ var require_ref = __commonJS({
11569
11569
  it.props = util_1.mergeEvaluated.props(gen, schEvaluated.props, it.props);
11570
11570
  }
11571
11571
  } else {
11572
- const props = gen.var("props", (0, codegen_1._)`${source5}.evaluated.props`);
11572
+ const props = gen.var("props", (0, codegen_1._)`${source6}.evaluated.props`);
11573
11573
  it.props = util_1.mergeEvaluated.props(gen, props, it.props, codegen_1.Name);
11574
11574
  }
11575
11575
  }
@@ -11579,7 +11579,7 @@ var require_ref = __commonJS({
11579
11579
  it.items = util_1.mergeEvaluated.items(gen, schEvaluated.items, it.items);
11580
11580
  }
11581
11581
  } else {
11582
- const items = gen.var("items", (0, codegen_1._)`${source5}.evaluated.items`);
11582
+ const items = gen.var("items", (0, codegen_1._)`${source6}.evaluated.items`);
11583
11583
  it.items = util_1.mergeEvaluated.items(gen, items, it.items, codegen_1.Name);
11584
11584
  }
11585
11585
  }
@@ -13905,6 +13905,23 @@ urgency: normal
13905
13905
  ---
13906
13906
 
13907
13907
  When the API response changes, review the differences and take appropriate action.
13908
+ `.trimStart(),
13909
+ "command-poll": yaml2`
13910
+ ---
13911
+ name: My command monitor
13912
+ watch:
13913
+ type: command-poll
13914
+ command:
13915
+ - git
13916
+ - status
13917
+ - --porcelain
13918
+ interval: 5m
13919
+ change-detection:
13920
+ strategy: text-diff
13921
+ urgency: normal
13922
+ ---
13923
+
13924
+ When the command output changes, review the differences and take appropriate action.
13908
13925
  `.trimStart(),
13909
13926
  schedule: yaml2`
13910
13927
  ---
@@ -28669,24 +28686,24 @@ var SQLiteSelectBuilder = class {
28669
28686
  this.withList = config2.withList;
28670
28687
  this.distinct = config2.distinct;
28671
28688
  }
28672
- from(source5) {
28689
+ from(source6) {
28673
28690
  const isPartialSelect = !!this.fields;
28674
28691
  let fields;
28675
28692
  if (this.fields) {
28676
28693
  fields = this.fields;
28677
- } else if (is(source5, Subquery)) {
28694
+ } else if (is(source6, Subquery)) {
28678
28695
  fields = Object.fromEntries(
28679
- Object.keys(source5._.selectedFields).map((key) => [key, source5[key]])
28696
+ Object.keys(source6._.selectedFields).map((key) => [key, source6[key]])
28680
28697
  );
28681
- } else if (is(source5, SQLiteViewBase)) {
28682
- fields = source5[ViewBaseConfig].selectedFields;
28683
- } else if (is(source5, SQL)) {
28698
+ } else if (is(source6, SQLiteViewBase)) {
28699
+ fields = source6[ViewBaseConfig].selectedFields;
28700
+ } else if (is(source6, SQL)) {
28684
28701
  fields = {};
28685
28702
  } else {
28686
- fields = getTableColumns(source5);
28703
+ fields = getTableColumns(source6);
28687
28704
  }
28688
28705
  return new SQLiteSelectBase({
28689
- table: source5,
28706
+ table: source6,
28690
28707
  fields,
28691
28708
  isPartialSelect,
28692
28709
  session: this.session,
@@ -29590,8 +29607,8 @@ var SQLiteUpdateBase = class extends QueryPromise {
29590
29607
  static [entityKind] = "SQLiteUpdate";
29591
29608
  /** @internal */
29592
29609
  config;
29593
- from(source5) {
29594
- this.config.from = source5;
29610
+ from(source6) {
29611
+ this.config.from = source6;
29595
29612
  return this;
29596
29613
  }
29597
29614
  createJoin(joinType) {
@@ -29743,11 +29760,11 @@ var SQLiteCountBuilder = class _SQLiteCountBuilder extends SQL {
29743
29760
  static [entityKind] = "SQLiteCountBuilderAsync";
29744
29761
  [Symbol.toStringTag] = "SQLiteCountBuilderAsync";
29745
29762
  session;
29746
- static buildEmbeddedCount(source5, filters) {
29747
- return sql`(select count(*) from ${source5}${sql.raw(" where ").if(filters)}${filters})`;
29763
+ static buildEmbeddedCount(source6, filters) {
29764
+ return sql`(select count(*) from ${source6}${sql.raw(" where ").if(filters)}${filters})`;
29748
29765
  }
29749
- static buildCount(source5, filters) {
29750
- return sql`select count(*) from ${source5}${sql.raw(" where ").if(filters)}${filters}`;
29766
+ static buildCount(source6, filters) {
29767
+ return sql`select count(*) from ${source6}${sql.raw(" where ").if(filters)}${filters}`;
29751
29768
  }
29752
29769
  then(onfulfilled, onrejected) {
29753
29770
  return Promise.resolve(this.session.count(this.sql)).then(
@@ -30032,8 +30049,8 @@ var BaseSQLiteDatabase = class {
30032
30049
  };
30033
30050
  return { as };
30034
30051
  };
30035
- $count(source5, filters) {
30036
- return new SQLiteCountBuilder({ source: source5, filters, session: this.session });
30052
+ $count(source6, filters) {
30053
+ return new SQLiteCountBuilder({ source: source6, filters, session: this.session });
30037
30054
  }
30038
30055
  /**
30039
30056
  * Incorporates a previously defined CTE (using `$with`) into the main query.
@@ -30607,8 +30624,8 @@ function drizzle(...params) {
30607
30624
  const { connection, client, ...drizzleConfig } = params[0];
30608
30625
  if (client) return construct(client, drizzleConfig);
30609
30626
  if (typeof connection === "object") {
30610
- const { source: source5, ...options2 } = connection;
30611
- const instance2 = new import_better_sqlite3.default(source5, options2);
30627
+ const { source: source6, ...options2 } = connection;
30628
+ const instance2 = new import_better_sqlite3.default(source6, options2);
30612
30629
  return construct(instance2, drizzleConfig);
30613
30630
  }
30614
30631
  const instance = new import_better_sqlite3.default(connection);
@@ -30749,8 +30766,8 @@ var monitorFrontmatterSchema = external_exports.object({
30749
30766
  notify: notifySchema.optional(),
30750
30767
  tags: external_exports.array(external_exports.string()).optional()
30751
30768
  });
30752
- function validateScope(scope, scopeSchema5) {
30753
- const validator = new Validator(scopeSchema5, "7", false);
30769
+ function validateScope(scope, scopeSchema6) {
30770
+ const validator = new Validator(scopeSchema6, "7", false);
30754
30771
  const result = validator.validate(scope);
30755
30772
  if (result.valid) return [];
30756
30773
  const messages = result.errors.map((unit) => {
@@ -31224,13 +31241,13 @@ var InboxService = class {
31224
31241
  };
31225
31242
  var SourceRegistry = class {
31226
31243
  sources = /* @__PURE__ */ new Map();
31227
- register(source5) {
31228
- if (this.sources.has(source5.name)) {
31244
+ register(source6) {
31245
+ if (this.sources.has(source6.name)) {
31229
31246
  throw new Error(
31230
- `Observation source "${source5.name}" is already registered`
31247
+ `Observation source "${source6.name}" is already registered`
31231
31248
  );
31232
31249
  }
31233
- this.sources.set(source5.name, source5);
31250
+ this.sources.set(source6.name, source6);
31234
31251
  }
31235
31252
  get(name) {
31236
31253
  return this.sources.get(name);
@@ -31247,7 +31264,7 @@ var SourceRegistry = class {
31247
31264
  };
31248
31265
  function generateMonitorSchema(sources) {
31249
31266
  const sourceNames = sources.map((s) => s.name);
31250
- const conditionals = sources.map((source5) => ({
31267
+ const conditionals = sources.map((source6) => ({
31251
31268
  if: {
31252
31269
  // `required: ['type']` on the inner `watch` is essential: JSON Schema
31253
31270
  // `properties` constraints are vacuously satisfied when the property is
@@ -31256,14 +31273,14 @@ function generateMonitorSchema(sources) {
31256
31273
  // instead of a clean "watch.type is required".
31257
31274
  properties: {
31258
31275
  watch: {
31259
- properties: { type: { const: source5.name } },
31276
+ properties: { type: { const: source6.name } },
31260
31277
  required: ["type"]
31261
31278
  }
31262
31279
  },
31263
31280
  required: ["watch"]
31264
31281
  },
31265
31282
  then: {
31266
- properties: { watch: source5.scopeSchema }
31283
+ properties: { watch: source6.scopeSchema }
31267
31284
  }
31268
31285
  }));
31269
31286
  return {
@@ -31315,6 +31332,194 @@ function generateMonitorSchema(sources) {
31315
31332
  allOf: conditionals
31316
31333
  };
31317
31334
  }
31335
+ function parseKeyedCollectionConfig(changeDetection) {
31336
+ if (changeDetection === null || typeof changeDetection !== "object" || Array.isArray(changeDetection)) {
31337
+ return void 0;
31338
+ }
31339
+ const collection = changeDetection["collection"];
31340
+ if (collection === void 0) return void 0;
31341
+ if (collection === null || typeof collection !== "object" || Array.isArray(collection)) {
31342
+ throw new Error(
31343
+ 'change-detection.collection must be an object with "path" and "key"'
31344
+ );
31345
+ }
31346
+ const c = collection;
31347
+ const path72 = c["path"];
31348
+ if (typeof path72 !== "string" || path72.length === 0) {
31349
+ throw new Error(
31350
+ "change-detection.collection.path must be a non-empty string"
31351
+ );
31352
+ }
31353
+ const key = c["key"];
31354
+ if (typeof key !== "string" || key.length === 0) {
31355
+ throw new Error(
31356
+ "change-detection.collection.key must be a non-empty string"
31357
+ );
31358
+ }
31359
+ const rawIgnore = c["ignore-paths"];
31360
+ let ignorePaths;
31361
+ if (rawIgnore !== void 0) {
31362
+ if (!Array.isArray(rawIgnore) || !rawIgnore.every((p) => typeof p === "string")) {
31363
+ throw new Error(
31364
+ "change-detection.collection.ignore-paths must be an array of strings"
31365
+ );
31366
+ }
31367
+ ignorePaths = rawIgnore;
31368
+ }
31369
+ return ignorePaths ? { path: path72, key, ignorePaths } : { path: path72, key };
31370
+ }
31371
+ function assertValidSegment(path72, segment) {
31372
+ if (/[[\]*?\s]/.test(segment)) {
31373
+ throw new Error(
31374
+ `Invalid collection path "${path72}": segment "${segment}" contains unsupported syntax (only plain field names are allowed \u2014 no "[index]", wildcards, or filters)`
31375
+ );
31376
+ }
31377
+ }
31378
+ function resolveDottedPath(root, path72) {
31379
+ if (path72 === "$") return root;
31380
+ if (!path72.startsWith("$.")) {
31381
+ throw new Error(
31382
+ `Invalid collection path "${path72}": must start with "$." (e.g. "$.tasks")`
31383
+ );
31384
+ }
31385
+ const segments = path72.slice(2).split(".");
31386
+ let current = root;
31387
+ for (const segment of segments) {
31388
+ if (segment.length === 0) {
31389
+ throw new Error(`Invalid collection path "${path72}": empty path segment`);
31390
+ }
31391
+ assertValidSegment(path72, segment);
31392
+ if (current === null || typeof current !== "object" || Array.isArray(current)) {
31393
+ return void 0;
31394
+ }
31395
+ current = current[segment];
31396
+ }
31397
+ return current;
31398
+ }
31399
+ function removeDottedPath(value, path72) {
31400
+ if (path72 === "$") return;
31401
+ if (!path72.startsWith("$.")) {
31402
+ throw new Error(
31403
+ `Invalid ignore-paths entry "${path72}": must start with "$." (e.g. "$.fetchedAt")`
31404
+ );
31405
+ }
31406
+ const segments = path72.slice(2).split(".");
31407
+ let current = value;
31408
+ for (let i = 0; i < segments.length - 1; i++) {
31409
+ const segment = segments[i];
31410
+ if (segment === void 0 || segment.length === 0) {
31411
+ throw new Error(
31412
+ `Invalid ignore-paths entry "${path72}": empty path segment`
31413
+ );
31414
+ }
31415
+ assertValidSegment(path72, segment);
31416
+ if (current === null || typeof current !== "object" || Array.isArray(current)) {
31417
+ return;
31418
+ }
31419
+ current = current[segment];
31420
+ }
31421
+ const last = segments[segments.length - 1];
31422
+ if (last === void 0 || last.length === 0) {
31423
+ throw new Error(`Invalid ignore-paths entry "${path72}": empty path segment`);
31424
+ }
31425
+ assertValidSegment(path72, last);
31426
+ if (current !== null && typeof current === "object" && !Array.isArray(current)) {
31427
+ Reflect.deleteProperty(current, last);
31428
+ }
31429
+ }
31430
+ function sortKeys(value) {
31431
+ if (Array.isArray(value)) return value.map(sortKeys);
31432
+ if (value !== null && typeof value === "object") {
31433
+ const sorted = {};
31434
+ for (const k of Object.keys(value).sort()) {
31435
+ sorted[k] = sortKeys(value[k]);
31436
+ }
31437
+ return sorted;
31438
+ }
31439
+ return value;
31440
+ }
31441
+ function normalizeElement(element, ignorePaths) {
31442
+ const clone2 = structuredClone(element);
31443
+ if (ignorePaths) {
31444
+ for (const p of ignorePaths) removeDottedPath(clone2, p);
31445
+ }
31446
+ return sortKeys(clone2);
31447
+ }
31448
+ function serialize(value) {
31449
+ return JSON.stringify(value);
31450
+ }
31451
+ function keyValueOf(element, keyField) {
31452
+ if (element === null || typeof element !== "object" || Array.isArray(element)) {
31453
+ throw new Error(
31454
+ `collection element is not an object (cannot read key "${keyField}")`
31455
+ );
31456
+ }
31457
+ const raw = element[keyField];
31458
+ if (typeof raw === "string") return raw;
31459
+ if (typeof raw === "number" || typeof raw === "boolean") return String(raw);
31460
+ throw new Error(
31461
+ `collection element is missing a scalar key field "${keyField}"`
31462
+ );
31463
+ }
31464
+ function diffKeyedCollection(parsedOutput, config2, monitorObjectKey, previousSnapshot, observationFields) {
31465
+ const resolved = resolveDottedPath(parsedOutput, config2.path);
31466
+ if (!Array.isArray(resolved)) {
31467
+ throw new Error(
31468
+ `collection path "${config2.path}" must select an array (got ${resolved === void 0 ? "nothing" : typeof resolved})`
31469
+ );
31470
+ }
31471
+ const current = {};
31472
+ for (const element of resolved) {
31473
+ const keyValue = keyValueOf(element, config2.key);
31474
+ if (Object.prototype.hasOwnProperty.call(current, keyValue)) {
31475
+ throw new Error(
31476
+ `collection key "${config2.key}" value "${keyValue}" is not unique within the collection`
31477
+ );
31478
+ }
31479
+ current[keyValue] = normalizeElement(element, config2.ignorePaths);
31480
+ }
31481
+ if (previousSnapshot === void 0) {
31482
+ return { observations: [], snapshot: current };
31483
+ }
31484
+ const observations = [];
31485
+ const emit = (keyValue, changeKind) => {
31486
+ const objectKey = `${monitorObjectKey}#${keyValue}`;
31487
+ const title = `${titleVerb(changeKind)}: ${objectKey}`;
31488
+ observations.push({
31489
+ title,
31490
+ summary: title,
31491
+ objectKey,
31492
+ changeKind,
31493
+ payload: { ...observationFields?.payload, key: keyValue, changeKind },
31494
+ queryScope: { ...observationFields?.queryScope, objectKey }
31495
+ });
31496
+ };
31497
+ for (const keyValue of Object.keys(current)) {
31498
+ if (!Object.prototype.hasOwnProperty.call(previousSnapshot, keyValue)) {
31499
+ emit(keyValue, "created");
31500
+ } else if (serialize(current[keyValue]) !== serialize(previousSnapshot[keyValue])) {
31501
+ emit(keyValue, "modified");
31502
+ }
31503
+ }
31504
+ for (const keyValue of Object.keys(previousSnapshot)) {
31505
+ if (!Object.prototype.hasOwnProperty.call(current, keyValue)) {
31506
+ emit(keyValue, "descoped");
31507
+ }
31508
+ }
31509
+ return { observations, snapshot: current };
31510
+ }
31511
+ function titleVerb(changeKind) {
31512
+ switch (changeKind) {
31513
+ case "created":
31514
+ return "Item added";
31515
+ case "modified":
31516
+ return "Item changed";
31517
+ case "descoped":
31518
+ return "Item removed";
31519
+ case "deleted":
31520
+ return "Item deleted";
31521
+ }
31522
+ }
31318
31523
  function parseDuration(duration3) {
31319
31524
  const match2 = /^(?<digits>\d+)(?<unit>[smhd])$/.exec(duration3);
31320
31525
  const digits = match2?.groups?.["digits"];
@@ -32099,8 +32304,8 @@ var AgentMonitorRuntime = class {
32099
32304
  for (const parsed of result.monitors) {
32100
32305
  const monitor = parsed.monitor;
32101
32306
  const sourceName = monitor.frontmatter.watch.type;
32102
- const source5 = this.registry.get(sourceName);
32103
- if (!source5) {
32307
+ const source6 = this.registry.get(sourceName);
32308
+ if (!source6) {
32104
32309
  throw new Error(
32105
32310
  `Monitor "${monitor.id}" references unknown source "${sourceName}".`
32106
32311
  );
@@ -32112,7 +32317,7 @@ var AgentMonitorRuntime = class {
32112
32317
  let observationResult;
32113
32318
  try {
32114
32319
  const monitorState2 = this.store.getMonitorState(monitor.id);
32115
- observationResult = await source5.observe(
32320
+ observationResult = await source6.observe(
32116
32321
  watchConfig(monitor.frontmatter.watch),
32117
32322
  {
32118
32323
  previousState: monitorState2.sourceState,
@@ -32263,18 +32468,18 @@ var AgentMonitorRuntime = class {
32263
32468
  for (const parsed of result.monitors) {
32264
32469
  const monitor = parsed.monitor;
32265
32470
  const sourceName = monitor.frontmatter.watch.type;
32266
- const source5 = this.registry.get(sourceName);
32267
- if (!source5) {
32471
+ const source6 = this.registry.get(sourceName);
32472
+ if (!source6) {
32268
32473
  throw new Error(
32269
32474
  `Monitor "${monitor.id}" references unknown source "${sourceName}".`
32270
32475
  );
32271
32476
  }
32272
- if (!source5.watch) continue;
32477
+ if (!source6.watch) continue;
32273
32478
  if (this.activeWatchers.has(monitor.id)) continue;
32274
32479
  const controller = new AbortController();
32275
32480
  controllers.set(monitor.id, controller);
32276
32481
  this.activeWatchers.add(monitor.id);
32277
- const watch = source5.watch.bind(source5);
32482
+ const watch = source6.watch.bind(source6);
32278
32483
  tasks.push(
32279
32484
  this.consumeWatch(
32280
32485
  monitor,
@@ -32668,12 +32873,17 @@ function parseScopeConfig2(config2) {
32668
32873
  const cd = config2["change-detection"];
32669
32874
  const strategy = cd?.strategy;
32670
32875
  const changeDetection = strategy === "status-code" || strategy === "json-diff" ? strategy : "text-diff";
32876
+ const collection = parseKeyedCollectionConfig(config2["change-detection"]);
32877
+ if (collection && changeDetection !== "json-diff") {
32878
+ throw new Error("change-detection.collection requires strategy: json-diff");
32879
+ }
32671
32880
  return {
32672
32881
  url,
32673
32882
  auth: config2["auth"],
32674
32883
  headers: config2["headers"],
32675
32884
  method: typeof config2["method"] === "string" ? config2["method"] : void 0,
32676
- changeDetection
32885
+ changeDetection,
32886
+ collection
32677
32887
  };
32678
32888
  }
32679
32889
  function resolveAuth(auth) {
@@ -32693,12 +32903,12 @@ function resolveAuth(auth) {
32693
32903
  const encoded = Buffer.from(`${username}:${password}`).toString("base64");
32694
32904
  return { Authorization: `Basic ${encoded}` };
32695
32905
  }
32696
- function sortKeys(value) {
32697
- if (Array.isArray(value)) return value.map(sortKeys);
32906
+ function sortKeys2(value) {
32907
+ if (Array.isArray(value)) return value.map(sortKeys2);
32698
32908
  if (value !== null && typeof value === "object") {
32699
32909
  const sorted = {};
32700
32910
  for (const key of Object.keys(value).sort()) {
32701
- sorted[key] = sortKeys(value[key]);
32911
+ sorted[key] = sortKeys2(value[key]);
32702
32912
  }
32703
32913
  return sorted;
32704
32914
  }
@@ -32710,8 +32920,8 @@ function hasChanged(strategy, prev, curr) {
32710
32920
  return prev.status !== curr.status;
32711
32921
  case "json-diff":
32712
32922
  try {
32713
- const prevJson = JSON.stringify(sortKeys(JSON.parse(prev.body)));
32714
- const currJson = JSON.stringify(sortKeys(JSON.parse(curr.body)));
32923
+ const prevJson = JSON.stringify(sortKeys2(JSON.parse(prev.body)));
32924
+ const currJson = JSON.stringify(sortKeys2(JSON.parse(curr.body)));
32715
32925
  return prevJson !== currJson;
32716
32926
  } catch {
32717
32927
  return prev.body !== curr.body;
@@ -32750,7 +32960,37 @@ var scopeSchema2 = {
32750
32960
  strategy: {
32751
32961
  type: "string",
32752
32962
  enum: ["json-diff", "text-diff", "status-code"]
32963
+ },
32964
+ // Keyed-collection mode (003 §12). The `collection` block is only valid
32965
+ // under `strategy: json-diff`; the `if/then` below enforces that at
32966
+ // authoring time (BP3).
32967
+ collection: {
32968
+ type: "object",
32969
+ properties: {
32970
+ path: {
32971
+ type: "string",
32972
+ description: 'Dotted $.-path to the array within the parsed JSON (e.g. "$.tasks")'
32973
+ },
32974
+ key: {
32975
+ type: "string",
32976
+ description: "Field on each element used as the per-object identity"
32977
+ },
32978
+ "ignore-paths": {
32979
+ type: "array",
32980
+ items: { type: "string" },
32981
+ description: "Dotted $.-paths (relative to each element) removed before comparison"
32982
+ }
32983
+ },
32984
+ required: ["path", "key"]
32753
32985
  }
32986
+ },
32987
+ // BP3: change-detection.collection requires strategy: json-diff. Under any
32988
+ // other strategy (or the defaulted text-diff), presence of `collection` is an
32989
+ // authoring-time error.
32990
+ if: { required: ["collection"] },
32991
+ then: {
32992
+ properties: { strategy: { const: "json-diff" } },
32993
+ required: ["strategy"]
32754
32994
  }
32755
32995
  }
32756
32996
  },
@@ -32761,15 +33001,30 @@ var source2 = {
32761
33001
  stateful: true,
32762
33002
  scopeSchema: scopeSchema2,
32763
33003
  async observe(config2, context = { now: /* @__PURE__ */ new Date() }) {
32764
- const { url, auth, headers, method, changeDetection } = parseScopeConfig2(config2);
33004
+ const { url, auth, headers, method, changeDetection, collection } = parseScopeConfig2(config2);
32765
33005
  const authHeaders = resolveAuth(auth);
32766
33006
  const response = await fetch(url, {
32767
33007
  method: method ?? "GET",
32768
33008
  headers: { ...authHeaders, ...headers }
32769
33009
  });
32770
33010
  const body = await response.text();
32771
- const curr = { body, status: response.status };
32772
33011
  const prev = context.previousState && typeof context.previousState === "object" && !Array.isArray(context.previousState) ? context.previousState : void 0;
33012
+ if (collection) {
33013
+ const result = diffKeyedCollection(
33014
+ JSON.parse(body),
33015
+ collection,
33016
+ url,
33017
+ prev?.keyedSnapshot,
33018
+ { payload: { url }, queryScope: { url } }
33019
+ );
33020
+ const curr2 = {
33021
+ body,
33022
+ status: response.status,
33023
+ keyedSnapshot: result.snapshot
33024
+ };
33025
+ return { observations: result.observations, nextState: curr2 };
33026
+ }
33027
+ const curr = { body, status: response.status };
32773
33028
  if (prev !== void 0 && hasChanged(changeDetection, prev, curr)) {
32774
33029
  return {
32775
33030
  observations: [
@@ -32804,9 +33059,335 @@ var source2 = {
32804
33059
  };
32805
33060
  var index_default2 = source2;
32806
33061
 
32807
- // ../../plugins/source-schedule/dist/index.js
33062
+ // ../../plugins/source-command-poll/dist/index.js
32808
33063
  init_cjs_shims();
33064
+ var import_child_process = require("child_process");
33065
+ var DEFAULT_TIMEOUT_MS = 3e4;
33066
+ var SIGKILL_GRACE_MS = 5e3;
33067
+ var STDOUT_CAP_BYTES = 1024 * 1024;
33068
+ var STDERR_TAIL_CHARS = 2e3;
32809
33069
  function parseScopeConfig3(config2) {
33070
+ const command = config2["command"];
33071
+ if (!Array.isArray(command) || command.length === 0 || !command.every((c) => typeof c === "string")) {
33072
+ throw new Error(
33073
+ 'scope.command must be a non-empty array of strings (argv form, e.g. ["git", "status"])'
33074
+ );
33075
+ }
33076
+ const cd = config2["change-detection"];
33077
+ const rawStrategy = cd?.strategy;
33078
+ const strategy = rawStrategy === "json-diff" || rawStrategy === "exit-code" ? rawStrategy : "text-diff";
33079
+ const collection = parseKeyedCollectionConfig(config2["change-detection"]);
33080
+ if (collection && strategy !== "json-diff") {
33081
+ throw new Error("change-detection.collection requires strategy: json-diff");
33082
+ }
33083
+ const cwd = typeof config2["cwd"] === "string" ? config2["cwd"] : void 0;
33084
+ const rawEnv = config2["env"];
33085
+ const env = rawEnv !== null && typeof rawEnv === "object" && !Array.isArray(rawEnv) && Object.values(rawEnv).every((v) => typeof v === "string") ? rawEnv : void 0;
33086
+ const rawTimeout = config2["timeout"];
33087
+ const timeoutMs = typeof rawTimeout === "string" ? parseDuration(rawTimeout) : DEFAULT_TIMEOUT_MS;
33088
+ const key = config2["key"];
33089
+ const objectKey = typeof key === "string" && key.length > 0 ? key : command.join(" ");
33090
+ return { command, cwd, env, timeoutMs, objectKey, strategy, collection };
33091
+ }
33092
+ async function runCommand(scope) {
33093
+ return new Promise((resolve) => {
33094
+ const [file, ...args] = scope.command;
33095
+ const child = (0, import_child_process.execFile)(
33096
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
33097
+ file,
33098
+ args,
33099
+ {
33100
+ cwd: scope.cwd,
33101
+ // `env` is merged over the inherited daemon environment (003 §11.1).
33102
+ env: scope.env ? { ...process.env, ...scope.env } : process.env,
33103
+ // We enforce the timeout ourselves (SIGTERM→SIGKILL) rather than relying on
33104
+ // execFile's `timeout`, so the grace escalation matches the spec exactly.
33105
+ shell: false,
33106
+ // Bound stdout capture at the 1 MiB cap (003 §11.2). When the child overruns,
33107
+ // execFile kills it and reports ERR_CHILD_PROCESS_STDIO_MAXBUFFER with the
33108
+ // captured-so-far bytes — we treat that as a truncated result, not a failure.
33109
+ maxBuffer: STDOUT_CAP_BYTES,
33110
+ encoding: "buffer"
33111
+ },
33112
+ (error2, stdoutBuf, stderrBuf) => {
33113
+ if (settled) return;
33114
+ settled = true;
33115
+ clearTimeout(killTimer);
33116
+ clearTimeout(graceTimer);
33117
+ const stderrFull = stderrBuf instanceof Buffer ? stderrBuf.toString("utf8") : "";
33118
+ const stderrTail = stderrFull.slice(-STDERR_TAIL_CHARS);
33119
+ const err = error2;
33120
+ const overflowed = err?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
33121
+ if (timedOut) {
33122
+ resolve({
33123
+ kind: "failure",
33124
+ error: `Command timed out after ${String(scope.timeoutMs)}ms`,
33125
+ stderrTail
33126
+ });
33127
+ return;
33128
+ }
33129
+ if (err !== null && typeof err.code === "string" && !overflowed) {
33130
+ resolve({
33131
+ kind: "failure",
33132
+ error: err.message,
33133
+ stderrTail
33134
+ });
33135
+ return;
33136
+ }
33137
+ const buf = Buffer.isBuffer(stdoutBuf) ? stdoutBuf : Buffer.from(String(stdoutBuf), "utf8");
33138
+ const { text: text2, truncated } = capStdout(buf, overflowed);
33139
+ const exitCode = err != null && typeof err.code === "number" ? err.code : 0;
33140
+ resolve({
33141
+ kind: "result",
33142
+ result: { stdout: text2, exitCode, truncated }
33143
+ });
33144
+ }
33145
+ );
33146
+ let settled = false;
33147
+ let timedOut = false;
33148
+ let graceTimer;
33149
+ const killTimer = setTimeout(() => {
33150
+ timedOut = true;
33151
+ child.kill("SIGTERM");
33152
+ graceTimer = setTimeout(() => {
33153
+ child.kill("SIGKILL");
33154
+ }, SIGKILL_GRACE_MS);
33155
+ graceTimer.unref();
33156
+ }, scope.timeoutMs);
33157
+ killTimer.unref();
33158
+ });
33159
+ }
33160
+ function capStdout(stdout, overflowed) {
33161
+ const truncated = overflowed || stdout.length > STDOUT_CAP_BYTES;
33162
+ const slice = stdout.length > STDOUT_CAP_BYTES ? stdout.subarray(0, STDOUT_CAP_BYTES) : stdout;
33163
+ return { text: slice.toString("utf8"), truncated };
33164
+ }
33165
+ function sortKeys3(value) {
33166
+ if (Array.isArray(value)) return value.map(sortKeys3);
33167
+ if (value !== null && typeof value === "object") {
33168
+ const sorted = {};
33169
+ for (const key of Object.keys(value).sort()) {
33170
+ sorted[key] = sortKeys3(value[key]);
33171
+ }
33172
+ return sorted;
33173
+ }
33174
+ return value;
33175
+ }
33176
+ function hasChanged2(strategy, prev, curr) {
33177
+ switch (strategy) {
33178
+ case "exit-code":
33179
+ return prev.exitCode !== curr.exitCode;
33180
+ case "json-diff":
33181
+ try {
33182
+ const prevJson = JSON.stringify(sortKeys3(JSON.parse(prev.stdout)));
33183
+ const currJson = JSON.stringify(sortKeys3(JSON.parse(curr.stdout)));
33184
+ return prevJson !== currJson;
33185
+ } catch {
33186
+ return prev.stdout !== curr.stdout;
33187
+ }
33188
+ case "text-diff":
33189
+ return prev.stdout !== curr.stdout;
33190
+ }
33191
+ }
33192
+ function isCommandState(value) {
33193
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
33194
+ return false;
33195
+ }
33196
+ const v = value;
33197
+ return typeof v["stdout"] === "string" && typeof v["exitCode"] === "number" && typeof v["truncated"] === "boolean" && (v["health"] === "ok" || v["health"] === "failing") && typeof v["baselined"] === "boolean";
33198
+ }
33199
+ function changedObservation(scope, result) {
33200
+ return {
33201
+ title: `Command output changed: ${scope.objectKey}`,
33202
+ summary: `Command output changed: ${scope.objectKey}`,
33203
+ payload: {
33204
+ command: scope.command,
33205
+ exitCode: result.exitCode,
33206
+ strategy: scope.strategy,
33207
+ stdout: result.stdout,
33208
+ truncated: result.truncated
33209
+ },
33210
+ snapshotText: result.stdout,
33211
+ objectKey: scope.objectKey,
33212
+ queryScope: { command: scope.objectKey },
33213
+ snapshot: {
33214
+ command: scope.command,
33215
+ exitCode: result.exitCode,
33216
+ stdoutLength: result.stdout.length,
33217
+ strategy: scope.strategy
33218
+ },
33219
+ changeKind: "modified"
33220
+ };
33221
+ }
33222
+ var scopeSchema3 = {
33223
+ type: "object",
33224
+ properties: {
33225
+ command: {
33226
+ type: "array",
33227
+ items: { type: "string" },
33228
+ minItems: 1,
33229
+ description: "Argv array; command[0] is the executable (resolved via PATH). Spawned directly, never via a shell."
33230
+ },
33231
+ cwd: {
33232
+ type: "string",
33233
+ description: "Working directory for the child process"
33234
+ },
33235
+ env: {
33236
+ type: "object",
33237
+ additionalProperties: { type: "string" },
33238
+ description: "Literal env vars merged over the inherited daemon environment"
33239
+ },
33240
+ timeout: {
33241
+ type: "string",
33242
+ pattern: "^\\d+[smhd]$",
33243
+ description: 'Wall-clock limit (e.g. "30s"). Expiry is an execution failure.'
33244
+ },
33245
+ key: {
33246
+ type: "string",
33247
+ description: "Overrides the observation objectKey (defaults to the joined argv)"
33248
+ },
33249
+ interval: {
33250
+ type: "string",
33251
+ pattern: "^\\d+[smhd]$",
33252
+ description: 'Polling interval (e.g., "5m"). Used by the scheduling engine, not by this plugin directly.'
33253
+ },
33254
+ "change-detection": {
33255
+ type: "object",
33256
+ properties: {
33257
+ strategy: {
33258
+ type: "string",
33259
+ enum: ["text-diff", "json-diff", "exit-code"]
33260
+ },
33261
+ // Keyed-collection mode (003 §12). The `collection` block is only valid
33262
+ // under `strategy: json-diff`; the `if/then` below enforces that at
33263
+ // authoring time (BP3).
33264
+ collection: {
33265
+ type: "object",
33266
+ properties: {
33267
+ path: {
33268
+ type: "string",
33269
+ description: 'Dotted $.-path to the array within the parsed JSON (e.g. "$.tasks")'
33270
+ },
33271
+ key: {
33272
+ type: "string",
33273
+ description: "Field on each element used as the per-object identity"
33274
+ },
33275
+ "ignore-paths": {
33276
+ type: "array",
33277
+ items: { type: "string" },
33278
+ description: "Dotted $.-paths (relative to each element) removed before comparison"
33279
+ }
33280
+ },
33281
+ required: ["path", "key"]
33282
+ }
33283
+ },
33284
+ // BP3: change-detection.collection requires strategy: json-diff. Under any
33285
+ // other strategy (or the defaulted text-diff), presence of `collection` is an
33286
+ // authoring-time error.
33287
+ if: { required: ["collection"] },
33288
+ then: {
33289
+ properties: { strategy: { const: "json-diff" } },
33290
+ required: ["strategy"]
33291
+ }
33292
+ }
33293
+ },
33294
+ required: ["command"]
33295
+ };
33296
+ var source3 = {
33297
+ name: "command-poll",
33298
+ stateful: true,
33299
+ scopeSchema: scopeSchema3,
33300
+ async observe(config2, context = { now: /* @__PURE__ */ new Date() }) {
33301
+ const scope = parseScopeConfig3(config2);
33302
+ const prev = isCommandState(context.previousState) ? context.previousState : void 0;
33303
+ const outcome = await runCommand(scope);
33304
+ if (outcome.kind === "failure") {
33305
+ const wasFailing = prev?.health === "failing";
33306
+ const nextState2 = {
33307
+ stdout: prev?.stdout ?? "",
33308
+ exitCode: prev?.exitCode ?? 0,
33309
+ truncated: prev?.truncated ?? false,
33310
+ health: "failing",
33311
+ baselined: prev?.baselined ?? false,
33312
+ // Carry the keyed baseline forward untouched so recovery diffs against it.
33313
+ ...prev?.keyedSnapshot ? { keyedSnapshot: prev.keyedSnapshot } : {}
33314
+ };
33315
+ return {
33316
+ observations: wasFailing ? [] : [failingObservation(scope, outcome)],
33317
+ nextState: nextState2
33318
+ };
33319
+ }
33320
+ const result = outcome.result;
33321
+ const recovered = prev?.health === "failing";
33322
+ const hadBaseline = prev?.baselined ?? false;
33323
+ const observations = [];
33324
+ if (recovered) {
33325
+ observations.push(recoveredObservation(scope));
33326
+ }
33327
+ if (scope.collection) {
33328
+ const result2 = diffKeyedCollection(
33329
+ JSON.parse(result.stdout),
33330
+ scope.collection,
33331
+ scope.objectKey,
33332
+ hadBaseline ? prev?.keyedSnapshot : void 0,
33333
+ {
33334
+ payload: { command: scope.command },
33335
+ queryScope: { command: scope.objectKey }
33336
+ }
33337
+ );
33338
+ observations.push(...result2.observations);
33339
+ const nextState2 = {
33340
+ stdout: result.stdout,
33341
+ exitCode: result.exitCode,
33342
+ truncated: result.truncated,
33343
+ health: "ok",
33344
+ baselined: true,
33345
+ keyedSnapshot: result2.snapshot
33346
+ };
33347
+ return { observations, nextState: nextState2 };
33348
+ }
33349
+ const nextState = {
33350
+ stdout: result.stdout,
33351
+ exitCode: result.exitCode,
33352
+ truncated: result.truncated,
33353
+ health: "ok",
33354
+ baselined: true
33355
+ };
33356
+ if (prev !== void 0 && hadBaseline && hasChanged2(scope.strategy, prev, result)) {
33357
+ observations.push(changedObservation(scope, result));
33358
+ }
33359
+ return { observations, nextState };
33360
+ }
33361
+ };
33362
+ function failingObservation(scope, outcome) {
33363
+ return {
33364
+ title: `Command failing: ${scope.objectKey}`,
33365
+ summary: `Command failing: ${scope.objectKey}`,
33366
+ payload: {
33367
+ command: scope.command,
33368
+ error: outcome.error,
33369
+ stderrTail: outcome.stderrTail
33370
+ },
33371
+ objectKey: scope.objectKey,
33372
+ queryScope: { command: scope.objectKey },
33373
+ changeKind: "modified"
33374
+ };
33375
+ }
33376
+ function recoveredObservation(scope) {
33377
+ return {
33378
+ title: `Command recovered: ${scope.objectKey}`,
33379
+ summary: `Command recovered: ${scope.objectKey}`,
33380
+ payload: { command: scope.command },
33381
+ objectKey: scope.objectKey,
33382
+ queryScope: { command: scope.objectKey },
33383
+ changeKind: "modified"
33384
+ };
33385
+ }
33386
+ var index_default3 = source3;
33387
+
33388
+ // ../../plugins/source-schedule/dist/index.js
33389
+ init_cjs_shims();
33390
+ function parseScopeConfig4(config2) {
32810
33391
  const cron = config2["cron"];
32811
33392
  if (typeof cron !== "string") {
32812
33393
  throw new Error("scope.cron must be a string");
@@ -32817,7 +33398,7 @@ function parseScopeConfig3(config2) {
32817
33398
  label: typeof config2["label"] === "string" ? config2["label"] : void 0
32818
33399
  };
32819
33400
  }
32820
- var scopeSchema3 = {
33401
+ var scopeSchema4 = {
32821
33402
  type: "object",
32822
33403
  properties: {
32823
33404
  cron: {
@@ -32835,11 +33416,11 @@ var scopeSchema3 = {
32835
33416
  },
32836
33417
  required: ["cron"]
32837
33418
  };
32838
- var source3 = {
33419
+ var source4 = {
32839
33420
  name: "schedule",
32840
- scopeSchema: scopeSchema3,
33421
+ scopeSchema: scopeSchema4,
32841
33422
  observe(config2, context = { now: /* @__PURE__ */ new Date() }) {
32842
- const { cron, timezone, label } = parseScopeConfig3(config2);
33423
+ const { cron, timezone, label } = parseScopeConfig4(config2);
32843
33424
  return Promise.resolve({
32844
33425
  observations: [
32845
33426
  {
@@ -32861,12 +33442,12 @@ var source3 = {
32861
33442
  });
32862
33443
  }
32863
33444
  };
32864
- var index_default3 = source3;
33445
+ var index_default4 = source4;
32865
33446
 
32866
33447
  // ../../plugins/source-incoming-changes/dist/index.js
32867
33448
  init_cjs_shims();
32868
- var import_child_process = require("child_process");
32869
- function parseScopeConfig4(config2) {
33449
+ var import_child_process2 = require("child_process");
33450
+ function parseScopeConfig5(config2) {
32870
33451
  const paths = config2["paths"];
32871
33452
  if (!Array.isArray(paths) || !paths.every((p) => typeof p === "string")) {
32872
33453
  throw new Error("scope.paths must be an array of strings");
@@ -32882,7 +33463,7 @@ var MAX_BUFFER = 64 * 1024 * 1024;
32882
33463
  function tryResolveCurrentRef(cwd, branch) {
32883
33464
  const ref = branch ?? "HEAD";
32884
33465
  try {
32885
- const raw = (0, import_child_process.execFileSync)("git", ["rev-parse", "--end-of-options", ref], {
33466
+ const raw = (0, import_child_process2.execFileSync)("git", ["rev-parse", "--end-of-options", ref], {
32886
33467
  cwd,
32887
33468
  encoding: "utf-8",
32888
33469
  maxBuffer: MAX_BUFFER
@@ -32907,7 +33488,7 @@ function tryGetDiffEntries(cwd, fromRef, toRef, paths) {
32907
33488
  ];
32908
33489
  let output;
32909
33490
  try {
32910
- output = (0, import_child_process.execFileSync)("git", args, {
33491
+ output = (0, import_child_process2.execFileSync)("git", args, {
32911
33492
  cwd,
32912
33493
  encoding: "buffer",
32913
33494
  maxBuffer: MAX_BUFFER
@@ -32943,7 +33524,7 @@ function tryGetDiffEntries(cwd, fromRef, toRef, paths) {
32943
33524
  }
32944
33525
  function getFileContent(cwd, ref, filePath2) {
32945
33526
  try {
32946
- const content = (0, import_child_process.execFileSync)("git", ["show", `${ref}:${filePath2}`], {
33527
+ const content = (0, import_child_process2.execFileSync)("git", ["show", `${ref}:${filePath2}`], {
32947
33528
  cwd,
32948
33529
  encoding: "buffer",
32949
33530
  maxBuffer: MAX_BUFFER
@@ -32985,7 +33566,7 @@ function buildObservation(entry, fromRef, toRef, cwd) {
32985
33566
  }
32986
33567
  return observation;
32987
33568
  }
32988
- var scopeSchema4 = {
33569
+ var scopeSchema5 = {
32989
33570
  $schema: "http://json-schema.org/draft-07/schema#",
32990
33571
  type: "object",
32991
33572
  properties: {
@@ -33010,13 +33591,13 @@ var scopeSchema4 = {
33010
33591
  },
33011
33592
  required: ["paths"]
33012
33593
  };
33013
- var source4 = {
33594
+ var source5 = {
33014
33595
  name: "incoming-changes",
33015
33596
  stateful: true,
33016
- scopeSchema: scopeSchema4,
33597
+ scopeSchema: scopeSchema5,
33017
33598
  observe(config2, context = { now: /* @__PURE__ */ new Date() }) {
33018
33599
  try {
33019
- const { paths, branch, cwd } = parseScopeConfig4(config2);
33600
+ const { paths, branch, cwd } = parseScopeConfig5(config2);
33020
33601
  const currentRef = tryResolveCurrentRef(cwd, branch);
33021
33602
  if (currentRef === void 0) {
33022
33603
  return Promise.resolve({ observations: [] });
@@ -33056,7 +33637,7 @@ var source4 = {
33056
33637
  }
33057
33638
  }
33058
33639
  };
33059
- var index_default4 = source4;
33640
+ var index_default5 = source5;
33060
33641
 
33061
33642
  // src/sources.ts
33062
33643
  function registerCoreSources(registry2) {
@@ -33064,6 +33645,7 @@ function registerCoreSources(registry2) {
33064
33645
  registry2.register(index_default2);
33065
33646
  registry2.register(index_default3);
33066
33647
  registry2.register(index_default4);
33648
+ registry2.register(index_default5);
33067
33649
  }
33068
33650
 
33069
33651
  // src/validation.ts
@@ -33104,6 +33686,16 @@ function requireDirectory(dirPath, json) {
33104
33686
  }
33105
33687
 
33106
33688
  // src/commands/validate.ts
33689
+ function changeDetectionCollectionError(watchConfig2) {
33690
+ const cd = watchConfig2["change-detection"];
33691
+ if (cd === null || typeof cd !== "object" || Array.isArray(cd))
33692
+ return void 0;
33693
+ const cdObj = cd;
33694
+ if (cdObj["collection"] === void 0) return void 0;
33695
+ const strategy = cdObj["strategy"];
33696
+ if (strategy === "json-diff") return void 0;
33697
+ return "change-detection.collection requires strategy: json-diff";
33698
+ }
33107
33699
  var validateCommand = new Command("validate").description("Validate MONITOR.md files in a directory").argument("[path]", "Path to monitors directory", ".claude/monitors").addOption(
33108
33700
  new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")
33109
33701
  ).action(async (monitorPath, options2) => {
@@ -33114,8 +33706,8 @@ var validateCommand = new Command("validate").description("Validate MONITOR.md f
33114
33706
  const scopeErrors = [];
33115
33707
  const validMonitors = result.monitors.filter((m) => {
33116
33708
  const sourceName = m.monitor.frontmatter.watch.type;
33117
- const source5 = registry2.get(sourceName);
33118
- if (!source5) {
33709
+ const source6 = registry2.get(sourceName);
33710
+ if (!source6) {
33119
33711
  scopeErrors.push({
33120
33712
  id: m.monitor.id,
33121
33713
  errors: [
@@ -33125,9 +33717,11 @@ var validateCommand = new Command("validate").description("Validate MONITOR.md f
33125
33717
  return false;
33126
33718
  }
33127
33719
  const { type: _type, ...watchConfig2 } = m.monitor.frontmatter.watch;
33128
- const errors = validateScope(watchConfig2, source5.scopeSchema);
33129
- if (errors.length > 0) {
33130
- scopeErrors.push({ id: m.monitor.id, errors });
33720
+ const errors = validateScope(watchConfig2, source6.scopeSchema);
33721
+ const collectionError = changeDetectionCollectionError(watchConfig2);
33722
+ const allScopeErrors = collectionError ? [collectionError, ...errors.filter((e) => !e.includes("then"))] : errors;
33723
+ if (allScopeErrors.length > 0) {
33724
+ scopeErrors.push({ id: m.monitor.id, errors: allScopeErrors });
33131
33725
  return false;
33132
33726
  }
33133
33727
  return true;
@@ -33967,24 +34561,24 @@ function createFollowupObservationContext(context) {
33967
34561
  previousState: context.previousState
33968
34562
  };
33969
34563
  }
33970
- async function handleStatefulSource(source5, scope, monitorName, json, context) {
34564
+ async function handleStatefulSource(source6, scope, monitorName, json, context) {
33971
34565
  if (!json) {
33972
34566
  console.log(
33973
34567
  `
33974
- Baseline established. The "${source5.name}" source requires a prior baseline before it can detect changes.`
34568
+ Baseline established. The "${source6.name}" source requires a prior baseline before it can detect changes.`
33975
34569
  );
33976
34570
  console.log(
33977
34571
  "Running a second observation to demonstrate change detection...\n"
33978
34572
  );
33979
34573
  }
33980
34574
  await (0, import_promises4.setTimeout)(100);
33981
- const secondResult = await source5.observe(
34575
+ const secondResult = await source6.observe(
33982
34576
  scope,
33983
34577
  createFollowupObservationContext(context)
33984
34578
  );
33985
34579
  const secondObservations = secondResult.observations;
33986
34580
  if (json) {
33987
- printJsonResult(monitorName, source5.name, true, secondObservations);
34581
+ printJsonResult(monitorName, source6.name, true, secondObservations);
33988
34582
  return;
33989
34583
  }
33990
34584
  if (secondObservations.length > 0) {
@@ -33994,7 +34588,7 @@ Baseline established. The "${source5.name}" source requires a prior baseline bef
33994
34588
  console.log(
33995
34589
  "No changes detected since baseline. This is expected \u2014 both observations happened within the same command invocation."
33996
34590
  );
33997
- const messages = SOURCE_TEST_MESSAGES[source5.name] ?? DEFAULT_TEST_MESSAGES;
34591
+ const messages = SOURCE_TEST_MESSAGES[source6.name] ?? DEFAULT_TEST_MESSAGES;
33998
34592
  for (const msg of messages) {
33999
34593
  console.log(`
34000
34594
  ${msg}`);
@@ -34023,8 +34617,8 @@ monitorTestCommand.command("test").description("Dry-run a monitor observation so
34023
34617
  }
34024
34618
  const registry2 = new SourceRegistry();
34025
34619
  registerCoreSources(registry2);
34026
- const source5 = registry2.get(result.monitor.frontmatter.watch.type);
34027
- if (!source5) {
34620
+ const source6 = registry2.get(result.monitor.frontmatter.watch.type);
34621
+ if (!source6) {
34028
34622
  reportError(
34029
34623
  `Unknown source: "${result.monitor.frontmatter.watch.type}". Available: ${registry2.names().join(", ")}`,
34030
34624
  json
@@ -34035,27 +34629,27 @@ monitorTestCommand.command("test").description("Dry-run a monitor observation so
34035
34629
  const { type: _type, ...monitorWatchConfig } = result.monitor.frontmatter.watch;
34036
34630
  if (!json) {
34037
34631
  console.log(
34038
- `Testing monitor "${monitorName}" (source: ${source5.name})...`
34632
+ `Testing monitor "${monitorName}" (source: ${source6.name})...`
34039
34633
  );
34040
34634
  }
34041
34635
  try {
34042
34636
  let context = { now: /* @__PURE__ */ new Date() };
34043
- const firstResult = await source5.observe(monitorWatchConfig, context);
34637
+ const firstResult = await source6.observe(monitorWatchConfig, context);
34044
34638
  const observations = firstResult.observations;
34045
34639
  context = {
34046
34640
  now: /* @__PURE__ */ new Date(),
34047
34641
  previousState: firstResult.nextState
34048
34642
  };
34049
- if (observations.length === 0 && source5.stateful) {
34643
+ if (observations.length === 0 && source6.stateful) {
34050
34644
  await handleStatefulSource(
34051
- source5,
34645
+ source6,
34052
34646
  monitorWatchConfig,
34053
34647
  monitorName,
34054
34648
  json,
34055
34649
  context
34056
34650
  );
34057
34651
  } else if (json) {
34058
- printJsonResult(monitorName, source5.name, false, observations);
34652
+ printJsonResult(monitorName, source6.name, false, observations);
34059
34653
  } else if (observations.length === 0) {
34060
34654
  console.log("No observations produced.");
34061
34655
  } else {
@@ -34114,11 +34708,11 @@ sourceCommand.command("list").description("List installed observation sources").
34114
34708
  registerCoreSources(registry2);
34115
34709
  const sources = registry2.list();
34116
34710
  if (options2.format === "json") {
34117
- const output = sources.map((source5) => {
34118
- const requiredFields = source5.scopeSchema["required"] ?? [];
34119
- const properties = source5.scopeSchema["properties"] ?? {};
34711
+ const output = sources.map((source6) => {
34712
+ const requiredFields = source6.scopeSchema["required"] ?? [];
34713
+ const properties = source6.scopeSchema["properties"] ?? {};
34120
34714
  return {
34121
- name: source5.name,
34715
+ name: source6.name,
34122
34716
  scopeFields: Object.keys(properties),
34123
34717
  required: requiredFields
34124
34718
  };
@@ -34131,10 +34725,10 @@ sourceCommand.command("list").description("List installed observation sources").
34131
34725
  return;
34132
34726
  }
34133
34727
  console.log("Installed sources:\n");
34134
- for (const source5 of sources) {
34135
- const requiredFields = source5.scopeSchema["required"] ?? [];
34136
- const properties = source5.scopeSchema["properties"] ?? {};
34137
- console.log(` ${source5.name}`);
34728
+ for (const source6 of sources) {
34729
+ const requiredFields = source6.scopeSchema["required"] ?? [];
34730
+ const properties = source6.scopeSchema["properties"] ?? {};
34731
+ console.log(` ${source6.name}`);
34138
34732
  console.log(` Scope fields: ${Object.keys(properties).join(", ")}`);
34139
34733
  console.log(` Required: ${requiredFields.join(", ") || "(none)"}`);
34140
34734
  console.log("");
@@ -35136,10 +35730,10 @@ function cached(getter) {
35136
35730
  function nullish(input) {
35137
35731
  return input === null || input === void 0;
35138
35732
  }
35139
- function cleanRegex(source5) {
35140
- const start = source5.startsWith("^") ? 1 : 0;
35141
- const end = source5.endsWith("$") ? source5.length - 1 : source5.length;
35142
- return source5.slice(start, end);
35733
+ function cleanRegex(source6) {
35734
+ const start = source6.startsWith("^") ? 1 : 0;
35735
+ const end = source6.endsWith("$") ? source6.length - 1 : source6.length;
35736
+ return source6.slice(start, end);
35143
35737
  }
35144
35738
  function floatSafeRemainder2(val, step) {
35145
35739
  const valDecCount = (val.toString().split(".")[1] || "").length;