Skip to content

Commit 2aad06e

Browse files
committed
Add 'Verify Server Endpoint Context' prompt
1 parent c896ace commit 2aad06e

4 files changed

Lines changed: 121 additions & 6 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
name: 'Verify Server Endpoint Context'
3+
description: 'Verify that behavior server logs and thrown errors start with the server name and end with the Matter endpoint id and number'
4+
argument-hint: 'Optional scope, notes, or request to fix violations'
5+
agent: 'agent'
6+
---
7+
8+
Verify endpoint context in Matterbridge behavior server implementations.
9+
10+
Scope:
11+
12+
- Inspect all server implementations in [packages/core/src/behaviors](../../packages/core/src/behaviors).
13+
- Inspect all server classes declared in files under [packages/core/src/devices](../../packages/core/src/devices), including files that also contain device classes or helper code.
14+
- In device files, limit the check to server class bodies. Do not report logs or throws belonging only to device classes or unrelated helpers.
15+
16+
Checks:
17+
18+
- Verify every textual log and throw message in scope starts with the exact name of its enclosing server class followed by a colon and one space. For example:
19+
20+
```typescript
21+
MatterbridgeBooleanStateConfigurationServer:
22+
```
23+
24+
- Verify every textual log and throw message in scope ends with this exact fragment:
25+
26+
```typescript
27+
(endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})
28+
```
29+
30+
- Treat calls to every log level as logs, including `debug`, `info`, `notice`, `warn`, `error`, and `fatal`, whether the logger is accessed through `device.log`, `this.state.log`, `this.log`, or another local reference.
31+
- Verify every error message created by a `throw` statement in scope follows the same prefix and suffix rules, including errors constructed directly in the `throw` and errors assigned to a variable before being thrown.
32+
- Follow local variables and simple helper methods when needed so multiline calls, template literals, and indirectly constructed error messages are not missed.
33+
- Do not accept a missing or abbreviated server name, text before the server name, or a prefix that does not match the enclosing server class name exactly.
34+
- Do not accept alternate endpoint formats, missing parentheses, a colon separator, `endpoint.id`, `endpoint.number`, messages containing only one endpoint component, or any text after the endpoint fragment's closing parenthesis.
35+
- Do not require the fragment in a log or thrown value that has no textual message, but report that case separately for manual review.
36+
- Ignore comments, JSDoc examples, tests, generated output, and imported server implementations.
37+
38+
Plugin forwarding contract:
39+
40+
- For every overridden Matter command handler in scope, verify that forwarding to the plugin through `device.commandHandler.executeHandler(...)` occurs immediately after the command-entry log.
41+
- Before the forwarding call, allow only the minimal local lookup required to access the logger and command handler, such as `const device = this.endpoint.stateOf(MatterbridgeServer)`, followed by the command-entry log.
42+
- Verify only the command-entry log immediately before forwarding uses the `info` level, for example `device.log.info(...)`. A command-entry log at `debug`, `notice`, `warn`, `error`, `fatal`, or any other level is not compliant.
43+
- Do not require any other log to use `info`. Logs outside the command-entry position may use any appropriate log level, but their messages must still satisfy the server-name prefix and endpoint suffix rules.
44+
- The forwarding call must be awaited before execution continues.
45+
- Do not allow request validation, assertions, conditionals, early returns, thrown errors, state reads used for decisions, state changes, event emission, additional logging, or other side effects between the command-entry log and completion of the awaited forwarding call.
46+
- Verify all validation and state mutation occur only after the awaited forwarding call.
47+
- Report a missing command-entry log, command-entry log at a level other than `info`, missing forwarding call, non-awaited forwarding call, or any disallowed operation before forwarding completes as a plugin forwarding contract violation.
48+
49+
Compliant examples from [booleanStateConfigurationServer.ts](../../packages/core/src/behaviors/booleanStateConfigurationServer.ts):
50+
51+
```typescript
52+
throw new StatusResponseError(
53+
`MatterbridgeBooleanStateConfigurationServer: requested alarm mode is not supported (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`,
54+
Status.ConstraintError,
55+
);
56+
57+
override async suppressAlarm(request: BooleanStateConfiguration.SuppressAlarmRequest): Promise<void> {
58+
const device = this.endpoint.stateOf(MatterbridgeServer);
59+
device.log.info(
60+
`MatterbridgeBooleanStateConfigurationServer: suppressing alarm ${debugStringify(request.alarmsToSuppress)}${nf} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`,
61+
);
62+
await device.commandHandler.executeHandler('BooleanStateConfiguration.suppressAlarm', {
63+
command: 'suppressAlarm',
64+
request,
65+
cluster: BooleanStateConfigurationServer.id,
66+
attributes: this.state as unknown as ClusterAttributeValues<(typeof BooleanStateConfiguration)['attributes']>,
67+
endpoint: this.endpoint as MatterbridgeEndpoint,
68+
context: this.context,
69+
});
70+
this.#assertAlarmModesSupported(request.alarmsToSuppress);
71+
}
72+
```
73+
74+
Output requirements:
75+
76+
- List each violation with a concise file and line reference, the log or throw kind, and the current message.
77+
- For each violation, identify whether the server-name prefix, endpoint suffix, or both are invalid.
78+
- List each plugin forwarding contract violation with the command handler, the invalid operation or ordering, and whether the command-entry log is missing or uses the wrong level, the forwarding call is missing, or forwarding is not awaited.
79+
- Group results by `behaviors` and `devices`.
80+
- If no violations are found, explicitly state that every in-scope log and thrown error starts with the enclosing server name and ends with the required endpoint fragment, and every command handler respects the plugin forwarding contract.
81+
- Do not modify files unless I explicitly ask you to fix the violations.
82+
- If fixes are requested, preserve each existing message where practical, prepend the exact enclosing server class name and `: `, append the exact endpoint fragment as the final message content, move awaited plugin forwarding before validation and state changes, then re-run the full verification and report any remaining violations.
83+
84+
Post-edit validation:
85+
86+
- After making any edits, run `npm run format`, `npm run build`, and `npm run lint` from the repository root.
87+
- Always run tests after making any edits. Use `npm run test` for the full test suite or `npm run test -- <testfile>` for a single relevant test file.
88+
- When using a single test file, run the complete file that covers every edited server. Do not rely only on a test-name filter, editor test adapter, source scan, type check, or previously completed test run.
89+
- Treat a test regression as a failed verification. Investigate whether the edit caused the failure and fix edit-related failures before completing the task.
90+
- Re-run any failed edit-related command or test after fixing it.
91+
- Report the result of formatting, build, lint, and tests. If any command cannot be run or any failure remains, report that explicitly with the failing command or test.

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,17 @@
410410
"chip:test:pump": "node scripts/run-matterbridge-chip-tests.mjs --test TC_PCC_",
411411
"chip:test:valve": "node scripts/run-matterbridge-chip-tests.mjs --test TC_VALCC_",
412412
"chip:test:operational-state": "node scripts/run-matterbridge-chip-tests.mjs --test TC_OPSTATE_",
413+
"chip:test:contact-sensor": "node scripts/run-matterbridge-chip-tests.mjs --test ContactSensor",
414+
"chip:test:light-sensor": "node scripts/run-matterbridge-chip-tests.mjs --test LightSensor",
415+
"chip:test:occupancy-sensor": "node scripts/run-matterbridge-chip-tests.mjs --test OccupancySensor",
416+
"chip:test:temperature-sensor": "node scripts/run-matterbridge-chip-tests.mjs --test TemperatureSensor",
417+
"chip:test:pressure-sensor": "node scripts/run-matterbridge-chip-tests.mjs --test PressureSensor",
418+
"chip:test:flow-sensor": "node scripts/run-matterbridge-chip-tests.mjs --test FlowSensor",
419+
"chip:test:humidity-sensor": "node scripts/run-matterbridge-chip-tests.mjs --test HumiditySensor",
420+
"chip:test:on-off-sensor": "node scripts/run-matterbridge-chip-tests.mjs --test OnOffSensor",
421+
"chip:test:smoke-co-alarm": "node scripts/run-matterbridge-chip-tests.mjs --test SmokeCOAlarm",
422+
"chip:test:air-quality-sensor": "node scripts/run-matterbridge-chip-tests.mjs --test AirQualitySensor",
423+
"chip:test:soil-sensor": "node scripts/run-matterbridge-chip-tests.mjs --test SoilSensor",
413424
"chip:test:rvc": "node scripts/run-matterbridge-chip-tests.mjs --test RoboticVacuumCleaner",
414425
"chip:test:rvc-service-area": "node scripts/run-matterbridge-chip-tests.mjs --test TC_SEAR",
415426
"chip:test:laundry-washer": "node scripts/run-matterbridge-chip-tests.mjs --test LaundryWasher",

packages/core/src/behaviors/booleanStateConfigurationServer.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ export class MatterbridgeBooleanStateConfigurationServer extends BooleanStateCon
9696

9797
#assertAlarmModesSupported(alarms: BooleanStateConfiguration.AlarmMode): void {
9898
if ([Boolean(alarms.visual && !this.state.alarmsSupported.visual), Boolean(alarms.audible && !this.state.alarmsSupported.audible)].some(Boolean)) {
99-
throw new StatusResponseError(`Requested alarm mode is not supported (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.ConstraintError);
99+
throw new StatusResponseError(
100+
`MatterbridgeBooleanStateConfigurationServer: requested alarm mode is not supported (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`,
101+
Status.ConstraintError,
102+
);
100103
}
101104
}
102105

@@ -107,7 +110,10 @@ export class MatterbridgeBooleanStateConfigurationServer extends BooleanStateCon
107110
Boolean(alarmsToSuppress.audible && (!this.state.alarmsActive.audible || !this.state.alarmsEnabled?.audible)),
108111
].some(Boolean)
109112
) {
110-
throw new StatusResponseError(`Requested alarm mode is not active (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`, Status.InvalidInState);
113+
throw new StatusResponseError(
114+
`MatterbridgeBooleanStateConfigurationServer: requested alarm mode is not active (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`,
115+
Status.InvalidInState,
116+
);
111117
}
112118
}
113119

@@ -118,7 +124,9 @@ export class MatterbridgeBooleanStateConfigurationServer extends BooleanStateCon
118124
*/
119125
override async suppressAlarm(request: BooleanStateConfiguration.SuppressAlarmRequest): Promise<void> {
120126
const device = this.endpoint.stateOf(MatterbridgeServer);
121-
device.log.info(`Suppressing alarm ${debugStringify(request.alarmsToSuppress)}${nf} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
127+
device.log.info(
128+
`MatterbridgeBooleanStateConfigurationServer: suppressing alarm ${debugStringify(request.alarmsToSuppress)}${nf} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`,
129+
);
122130
await device.commandHandler.executeHandler('BooleanStateConfiguration.suppressAlarm', {
123131
command: 'suppressAlarm',
124132
request,
@@ -140,7 +148,9 @@ export class MatterbridgeBooleanStateConfigurationServer extends BooleanStateCon
140148
*/
141149
override async enableDisableAlarm(request: BooleanStateConfiguration.EnableDisableAlarmRequest): Promise<void> {
142150
const device = this.endpoint.stateOf(MatterbridgeServer);
143-
device.log.info(`Enabling/disabling alarm ${debugStringify(request.alarmsToEnableDisable)}${nf} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
151+
device.log.info(
152+
`MatterbridgeBooleanStateConfigurationServer: enabling/disabling alarm ${debugStringify(request.alarmsToEnableDisable)}${nf} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`,
153+
);
144154
await device.commandHandler.executeHandler('BooleanStateConfiguration.enableDisableAlarm', {
145155
command: 'enableDisableAlarm',
146156
request,

packages/core/vitest/matterbridgeEndpoint-matterjs.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,15 +1253,18 @@ describe('Matterbridge ' + NAME, () => {
12531253
await leak.setAttribute('booleanStateConfiguration', 'alarmsActive', { audible: true, visual: true });
12541254
await leak.invokeBehaviorCommand('booleanStateConfiguration', 'suppressAlarm', { alarmsToSuppress: { audible: true, visual: true } });
12551255
expect(leak.getAttribute('booleanStateConfiguration', 'alarmsSuppressed')).toEqual({ audible: true, visual: true });
1256-
expect(loggerLogSpy).toHaveBeenCalledWith(LogLevel.INFO, `Suppressing alarm ${debugStringify({ audible: true, visual: true })}${nf} (endpoint ${leak.id}.${leak.number})`);
1256+
expect(loggerLogSpy).toHaveBeenCalledWith(
1257+
LogLevel.INFO,
1258+
`MatterbridgeBooleanStateConfigurationServer: suppressing alarm ${debugStringify({ audible: true, visual: true })}${nf} (endpoint ${leak.id}.${leak.number})`,
1259+
);
12571260
vi.clearAllMocks();
12581261
await leak.invokeBehaviorCommand('booleanStateConfiguration', 'enableDisableAlarm', { alarmsToEnableDisable: { audible: true, visual: false } });
12591262
expect(leak.getAttribute('booleanStateConfiguration', 'alarmsActive')).toEqual({ audible: true, visual: false });
12601263
expect(leak.getAttribute('booleanStateConfiguration', 'alarmsEnabled')).toEqual({ audible: true, visual: false });
12611264
expect(leak.getAttribute('booleanStateConfiguration', 'alarmsSuppressed')).toEqual({ audible: true, visual: false });
12621265
expect(loggerLogSpy).toHaveBeenCalledWith(
12631266
LogLevel.INFO,
1264-
`Enabling/disabling alarm ${debugStringify({ audible: true, visual: false })}${nf} (endpoint ${leak.id}.${leak.number})`,
1267+
`MatterbridgeBooleanStateConfigurationServer: enabling/disabling alarm ${debugStringify({ audible: true, visual: false })}${nf} (endpoint ${leak.id}.${leak.number})`,
12651268
);
12661269
});
12671270

0 commit comments

Comments
 (0)