Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ outputs:
runs:
using: composite
steps:
- name: Resolve project root
id: project-root
shell: bash
run: |
set -euo pipefail
if [ -f "${{ github.action_path }}/package-lock.json" ]; then
echo "root=${{ github.action_path }}" >> "$GITHUB_OUTPUT"
elif [ -f "${{ github.workspace }}/package-lock.json" ]; then
echo "root=${{ github.workspace }}" >> "$GITHUB_OUTPUT"
else
echo "::error title=Sorokeep setup failed::Could not find package-lock.json in the action directory or the workspace."
echo "action_path=${{ github.action_path }}"
echo "workspace=${{ github.workspace }}"
exit 1
fi

- name: Setup Node.js
uses: actions/setup-node@v4
with:
Expand All @@ -42,12 +58,12 @@ runs:
- name: Install dependencies
shell: bash
run: npm ci
working-directory: ${{ github.action_path }}
working-directory: ${{ steps.project-root.outputs.root }}

- name: Build sorokeep
shell: bash
run: npm run build
working-directory: ${{ github.action_path }}
working-directory: ${{ steps.project-root.outputs.root }}

- name: Run TTL check
id: ttl-check
Expand All @@ -57,5 +73,5 @@ runs:
SOROKEEP_NETWORK: ${{ inputs.network }}
SOROKEEP_THRESHOLD: ${{ inputs.threshold }}
SOROKEEP_RPC_URL: ${{ inputs.rpc-url }}
GITHUB_ACTION_PATH: ${{ github.action_path }}
run: ${{ github.action_path }}/action/entrypoint.sh
SOROKEEP_PROJECT_ROOT: ${{ steps.project-root.outputs.root }}
run: ${{ steps.project-root.outputs.root }}/action/entrypoint.sh
4 changes: 2 additions & 2 deletions action/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ CONTRACT_ID="${SOROKEEP_CONTRACT_ID}"
NETWORK="${SOROKEEP_NETWORK:-testnet}"
THRESHOLD="${SOROKEEP_THRESHOLD:-500}"
RPC_URL="${SOROKEEP_RPC_URL:-}"
ACTION_PATH="${GITHUB_ACTION_PATH:-.}"
PROJECT_ROOT="${SOROKEEP_PROJECT_ROOT:-${GITHUB_ACTION_PATH:-.}}"

ARGS=("${CONTRACT_ID}" --network "${NETWORK}" --threshold "${THRESHOLD}" --json)
if [ -n "${RPC_URL}" ]; then
Expand All @@ -18,7 +18,7 @@ echo "::group::Sorokeep TTL check — contract ${CONTRACT_ID} on ${NETWORK} (thr

# Run check; capture output and exit code without aborting on failure.
set +e
CHECK_JSON=$(node "${ACTION_PATH}/dist/index.js" check "${ARGS[@]}")
CHECK_JSON=$(node "${PROJECT_ROOT}/dist/index.js" check "${ARGS[@]}")
CHECK_EXIT=$?
set -e

Expand Down
21 changes: 21 additions & 0 deletions check-coverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const { execSync } = require('child_process');

try {
const output = execSync('npx vitest run --coverage', { encoding: 'utf8', stdio: 'pipe' });
console.log(output);
} catch (error) {
console.log(error.stdout);
console.log(error.stderr);

// Check if coverage was generated despite test failures
if (error.stdout.includes('Coverage for branches')) {
const branchMatch = error.stdout.match(/Coverage for branches \(([0-9.]+)%\)/);
if (branchMatch) {
console.log(`\n=== BRANCH COVERAGE: ${branchMatch[1]}% ===`);
}
}

if (error.stderr.includes('ERROR: Coverage for branches')) {
console.log('\n=== COVERAGE ERROR FOUND ===');
}
}
1 change: 1 addition & 0 deletions coverage-json.json

Large diffs are not rendered by default.

Binary file added coverage-output.json
Binary file not shown.
Binary file added coverage-output.txt
Binary file not shown.
106 changes: 106 additions & 0 deletions coverage_output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@

 RUN  v4.1.10 c:/Users/DELL/sorokeep
Coverage enabled with v8

❯ src/alerts/alerts.test.ts (0 test)
❯ src/alerts/channels.test.ts (0 test)
❯ src/alerts/discord.test.ts (0 test)
❯ tests/lib.test.ts (0 test)
❯ src/alerts/instance_scan.test.ts (0 test)
❯ src/alerts/keys.test.ts (0 test)
❯ src/alerts/pagerduty.test.ts (0 test)
❯ src/alerts/resource.test.ts (0 test)
❯ src/alerts/simulation_cache.test.ts (0 test)
❯ src/alerts/telegram.test.ts (0 test)
❯ src/alerts/webhook.test.ts (0 test)
❯ tests/action/ttl-check-action.test.ts (0 test)
❯ tests/alerts/builtins.test.ts (0 test)
❯ tests/alerts/dispatcher.test.ts (0 test)
❯ tests/alerts/pagerduty_custom_templates.test.ts (0 test)
❯ tests/alerts/registry.test.ts (0 test)
❯ tests/alerts/lifecycle.test.ts (0 test)
❯ tests/alerts/slack.test.ts (0 test)
❯ tests/alerts/slack_custom_templates.test.ts (0 test)
❯ tests/alerts/state_change_alerts.test.ts (0 test)
❯ tests/alerts/template_context.test.ts (0 test)
❯ tests/alerts/templates.test.ts (0 test)
❯ tests/alerts/webhook.test.ts (0 test)
❯ tests/cli/schemaFormatter.test.ts (0 test)
❯ tests/commands/alerts.test.ts (0 test)
❯ tests/commands/alerts_plugin_channel.test.ts (0 test)
❯ tests/commands/budget.test.ts (0 test)
❯ tests/commands/channel_plugin_flag.test.ts (0 test)
❯ tests/commands/check.test.ts (0 test)
❯ tests/commands/channels.test.ts (0 test)
❯ tests/commands/completion.test.ts (0 test)
❯ tests/commands/completion_command.test.ts (0 test)
❯ tests/commands/costs.test.ts (0 test)
❯ tests/commands/db.test.ts (0 test)
❯ tests/commands/daemon.test.ts (0 test)
❯ tests/commands/db_branches.test.ts (0 test)
❯ tests/commands/history.test.ts (0 test)
❯ tests/commands/guard.test.ts (0 test)
❯ tests/commands/inspect.test.ts (0 test)
❯ tests/commands/pause.test.ts (0 test)
❯ tests/commands/resources.test.ts (0 test)
❯ tests/commands/status.test.ts (0 test)
❯ tests/core/aws_secrets.test.ts (0 test)
❯ tests/commands/restore.test.ts (0 test)
❯ tests/commands/watch.test.ts (0 test)
❯ tests/core/budget.test.ts (0 test)
❯ tests/core/budget_progress.test.ts (0 test)
❯ tests/core/budget_enforcement.test.ts (0 test)
❯ tests/core/channels.test.ts (0 test)
❯ tests/core/check.test.ts (0 test)
❯ tests/core/costs.test.ts (0 test)
❯ tests/core/decoder.test.ts (0 test)
❯ tests/core/discovery.test.ts (0 test)
❯ tests/core/extension.test.ts (0 test)
❯ tests/core/inspect.test.ts (0 test)
❯ tests/core/introspection_cache.test.ts (0 test)
❯ tests/core/monitor.test.ts (0 test)
❯ tests/core/rent_projection.test.ts (0 test)
❯ tests/core/rate_limiter.test.ts (0 test)
❯ tests/core/state_diff.test.ts (0 test)
❯ tests/core/scvalTranslator.test.ts (0 test)
❯ tests/core/status.test.ts (0 test)
❯ tests/core/vault.test.ts (0 test)
❯ tests/core/watch.test.ts (0 test)
❯ tests/daemon/introspection.test.ts (0 test)
❯ tests/docker/devnet-compose.test.ts (0 test)
❯ tests/daemon/loop.test.ts (0 test)
❯ tests/docker/docker-compose.test.ts (0 test)
❯ tests/docker/docker.test.ts (0 test)
❯ tests/db/alert_delivery.test.ts (0 test)
❯ tests/db/backup.test.ts (0 test)
❯ tests/db/budget_tracking.test.ts (0 test)
❯ tests/db/channel_type_migration.test.ts (0 test)
❯ tests/db/database.test.ts (0 test)
❯ tests/db/introspection_cache.test.ts (0 test)
❯ tests/db/migrator.test.ts (0 test)
❯ tests/db/rate_limiter.test.ts (0 test)
❯ tests/db/repositories.test.ts (0 test)
❯ tests/db/resource_usage_logs.test.ts (0 test)
❯ tests/docs/cicd.test.ts (0 test)
❯ tests/docs/get_monitored_keys_spec.test.ts (0 test)
❯ tests/docs/onboarding.test.ts (0 test)
❯ tests/logging/index.test.ts (0 test)
❯ tests/e2e/daemon-execution.test.ts (0 test)
❯ tests/e2e/sandbox-network.test.ts (0 test)
❯ tests/logging/logger.test.ts (0 test)
❯ tests/mcp/get_contract_status.test.ts (0 test)
❯ tests/mcp/get-extension-costs.test.ts (0 test)
❯ tests/mcp/lifecycle.test.ts (0 test)
❯ tests/mcp/server.test.ts (0 test)
❯ tests/rpc/client.test.ts (0 test)
❯ tests/systemd/service.test.ts (0 test)
❯ tests/rpc/resource_estimate.test.ts (0 test)
❯ tests/utils/formatting.test.ts (0 test)
❯ tests/utils/config.test.ts (0 test)
❯ tests/utils/watch-config.test.ts (0 test)

 Test Files  96 failed (96)
 Tests  no tests
 Start at  07:14:59
 Duration  74.21s (transform 8.39s, setup 0ms, import 0ms, tests 0ms, environment 46ms)
Comment on lines +102 to +105

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Resolve the failed test run before committing coverage results.

This report records 96 failed suites and zero executed tests, so it cannot demonstrate coverage or a passing CI state. Fix the underlying suite-loading failure, then regenerate the report.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@coverage_output.txt` around lines 102 - 105, The committed coverage report
reflects a failed test run with 96 suite-loading failures and no executed tests.
Resolve the underlying test discovery or initialization failure, rerun the
complete test suite until it passes, then regenerate and commit the coverage
report from that successful run.


Binary file added coverage_result.txt
Binary file not shown.
49 changes: 45 additions & 4 deletions docs/adding-an-alert-channel.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Look at `alerts/webhook.ts` or `alerts/pagerduty.ts` for the timeout/AbortContro

## 2. Register a `ChannelDefinition`

Add your channel to `src/alerts/builtins.ts` if it's shipping in sorokeep itself, or register it from your own application's startup code if you're embedding sorokeep as a library (`import { registerAlertChannel } from "sorokeep"` — see `src/lib.ts`).
Add your channel to `src/alerts/builtins.ts` if it's shipping in sorokeep itself, register it from your own application's startup code if you're embedding sorokeep as a library (`import { registerAlertChannel } from "sorokeep"` — see `src/lib.ts`), or publish it as an external npm package and load it with `--channel-plugin`.

```ts
import { registerAlertChannel } from "./registry.js";
Expand All @@ -73,18 +73,59 @@ Field-by-field:

If you added it to `builtins.ts`, that's it — `registerBuiltinChannels()` is called once (idempotently) from both `dispatcher.ts` and `commands/alerts.ts`, so your channel is live everywhere.

## 3. What you did *not* need to touch
## 3. External plugin package convention

If you want a channel to be installable without changing sorokeep's source, publish a package whose default export is a registration function. The CLI loads it with `--channel-plugin <package>` and passes in sorokeep's public `registerAlertChannel` function.

```ts
// package entrypoint, e.g. src/index.ts in your npm package
export default function registerMatrixChannel(
registerAlertChannel: typeof import("sorokeep").registerAlertChannel,
): void {
registerAlertChannel({
name: "matrix",
channel: { send: (target, event) => sendMatrixAlert(target, event) },
targetOption: "url",
missingTargetError: "Error: --url is required when --type is matrix.",
supportsSigning: false,
});
}
```
Comment on lines +80 to +93

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Import the sender in the external-plugin example.

sendMatrixAlert is neither declared nor imported here, so a copied plugin will fail when it sends its first alert.

Proposed fix
 // package entrypoint, e.g. src/index.ts in your npm package
+import { sendMatrixAlert } from "./matrix.js";
+
 export default function registerMatrixChannel(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```ts
// package entrypoint, e.g. src/index.ts in your npm package
export default function registerMatrixChannel(
registerAlertChannel: typeof import("sorokeep").registerAlertChannel,
): void {
registerAlertChannel({
name: "matrix",
channel: { send: (target, event) => sendMatrixAlert(target, event) },
targetOption: "url",
missingTargetError: "Error: --url is required when --type is matrix.",
supportsSigning: false,
});
}
```
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adding-an-alert-channel.md` around lines 80 - 93, Update the
external-plugin example’s registerMatrixChannel implementation to import
sendMatrixAlert from the module that defines it before using it in the
channel.send callback, ensuring copied plugins have a resolvable sender.


Minimal package metadata:

```json
{
"name": "sorokeep-alert-channel-matrix",
"type": "module",
"exports": "./dist/index.js"
}
```

Example CLI usage after `npm install` or a local link:

```bash
sorokeep --channel-plugin sorokeep-alert-channel-matrix alerts add \
--contract <contractId> \
--type matrix \
--url '!room:example.org' \
--threshold 1000
```

The same flag also applies to long-running processes such as `sorokeep daemon`, so the daemon can deliver alerts through channels that were registered by external packages before it starts processing contracts.

## 4. What you did *not* need to touch

- `alerts/dispatcher.ts` — its default channel map is built from the registry (`listAlertChannels()`), not a hardcoded object.
- `commands/alerts.ts` — `alerts add --type matrix ...` resolves your `ChannelDefinition` from the registry; the target flag, error message, and signing behavior all come from what you registered.
- `db/schema.sql` — `channel_type` is a plain `TEXT` column with a non-empty check, not a fixed SQL enum. No migration needed.

## 4. Tests
## 5. Tests

Follow the pattern in `tests/alerts/builtins.test.ts`: mock the underlying module (e.g. `vi.mock("../../src/alerts/matrix.js", ...)`), then assert the registered `channel.send` delegates to it with the right arguments. Add a contract-shaped test the way `tests/db/repositories.test.ts`'s `"accepts discord as a valid channel_type"` tests do, if your channel needs any DB-level exercise beyond what's already generic.

Per [CONTRIBUTING.md](../CONTRIBUTING.md#test-driven-development), write the test first.

## 5. Docs
## 6. Docs

Add your channel to the table in `README.md`'s Alerting section, and to `sorokeep alerts add`'s `--type` help text in the same file.
28 changes: 26 additions & 2 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import tseslintPlugin from "@typescript-eslint/eslint-plugin";

export default [
js.configs.recommended,
{
linterOptions: {
reportUnusedDisableDirectives: "off",
},
},
{
files: ["src/**/*.ts", "tests/**/*.ts"],
languageOptions: {
Expand All @@ -18,11 +23,30 @@ export default [
},
rules: {
...tseslintPlugin.configs.recommended.rules,
"@typescript-eslint/no-unused-vars": ["warn"],
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
"@typescript-eslint/no-explicit-any": "off",
"no-undef": "off" // TypeScript handles this
},
},
{
files: ["tests/**/*.ts", "src/**/*.test.ts"],
rules: {
"@typescript-eslint/no-unused-vars": "off",
"no-extra-boolean-cast": "off",
"no-prototype-builtins": "off",
"no-useless-assignment": "off",
"no-constant-condition": "off",
"@typescript-eslint/ban-ts-comment": "off",
"preserve-caught-error": "off",
},
},
{
ignores: ["dist/**", "node_modules/**"]
}
Expand Down
Binary file added final_coverage.txt
Binary file not shown.
1 change: 1 addition & 0 deletions lint-report.json

Large diffs are not rendered by default.

Loading
Loading