@sweny-ai/core 0.1.84 → 0.1.86

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.
@@ -1,3 +1,3 @@
1
- export const triageWorkflow = {"id":"triage","name":"Alert Triage","description":"Investigate a production alert, determine root cause, create an issue, implement a fix, and notify the team","entry":"gather","nodes":{"gather":{"name":"Gather Context","instruction":"You are investigating a production alert. Gather all relevant context using the available tools:\n\n1. **Observability**: Pull error details, stack traces, recent logs, metrics, and active incidents around the time of the alert. Use whichever observability tools are available to you.\n2. **Source control**: Check recent commits, pull requests, and deploys that might be related.\n3. **Issue tracker**: Search for similar past issues or known problems.\n\nBe thorough — the investigation step depends on complete context. Use every tool available to you.","skills":["github","sentry","datadog","betterstack","linear"]},"investigate":{"name":"Root Cause Analysis","instruction":"Based on the gathered context, classify every distinct issue you found into one of two buckets: **novel** or **duplicate**.\n\nFor EACH issue found:\n1. Identify the root cause and affected code/service.\n2. Assess severity: critical (service down), high (major feature broken), medium (degraded), low (cosmetic/minor).\n3. Assess fix complexity: \"simple\" (a few lines, clear change), \"moderate\" (multiple files but well-understood), or \"complex\" (architectural, risky, or unclear).\n4. **Novelty check (REQUIRED — you MUST call tools for this step):** Filing a duplicate ticket is worse than under-reporting — the team inherits sprawl that humans have to clean up later. Complete EVERY step below before classifying anything as novel. Do NOT skip these searches even if the gather context already mentions existing issues or branches — you must verify independently.\n4a. **Search broadly — THIS REQUIRES TOOL CALLS.** You MUST call `linear_search_issues` (or `github_search_issues`) with multiple queries against the issue tracker (BOTH open AND closed): the affected file or module path, the error message or log string verbatim, the feature or service name, and the symptom family (e.g. \"startup crash\", \"data dropped silently\"). Do not rely on a single keyword — a narrow search is how duplicates slip through. If you skip this search, the verify check will fail.\n4b. **Read the full body of every promising candidate — do NOT match on title alone.** For each top match, open the issue and read its entire description, including any \"Recommended Fix\", \"Proposed Fix\", \"Action Items\", or \"Acceptance Criteria\" section. An existing open issue whose recommended fix already prescribes the change you were about to propose IS a duplicate, even if the symptom you just observed is a new instance of the same underlying bug.\n4c. **Check for meta-issues and bug families.** If an existing ticket names a pattern that your finding is another instance of (same root cause, same function/module, different input or caller), treat your finding as a sibling of that family and comment on the existing ticket. Do not file a parallel ticket per instance — that is the exact pattern that produces backlog sprawl.\n4d. **When uncertain, comment don't file.** If you are less than ~80% confident a finding is truly novel after steps 4a–4c, classify it as a duplicate and add a \"+1 — another instance\" comment to the closest existing issue with the new context. Over-reporting duplicates is cheap; ticket sprawl is expensive.\n\n - If matched → **duplicate**. Record the existing issue ID in `duplicate_of`.\n - If no match after 4a–4d → **novel**.\n\n**Output rules:**\n- `findings`: array of ALL issues found (both novel and duplicate).\n- `novel_count`: how many findings are novel (not duplicates).\n- `highest_severity`: the highest severity across ALL findings.\n- `recommendation`: what should happen next.\n\nDownstream nodes will act ONLY on novel findings. Duplicates will be +1'd automatically.\n\n**IMPORTANT — scope boundaries for this node:**\n- DO NOT create issues (Linear, GitHub, or any tracker). A downstream `create_issue` node handles that.\n- DO NOT create branches, commits, or pull requests. A downstream `implement` node handles that.\n- DO NOT call any write tools (`linear_create_issue`, `github_create_issue`, `github_create_pr`, `linear_add_comment`, `github_add_comment`, etc.). Downstream nodes handle all writes — issue creation, +1 comments, and PRs.\n- Your ONLY job is read, search, classify, and output. If you create an issue or add a comment here, the downstream verify check will fail or duplicate work.","skills":["github","linear"],"verify":{"any_tool_called":["linear_search_issues","github_search_issues"]},"output":{"type":"object","properties":{"findings":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"Short description of the issue"},"root_cause":{"type":"string"},"severity":{"type":"string","enum":["critical","high","medium","low"]},"affected_services":{"type":"array","items":{"type":"string"}},"is_duplicate":{"type":"boolean"},"duplicate_of":{"type":"string","description":"Existing issue ID/URL if duplicate"},"fix_approach":{"type":"string"},"fix_complexity":{"type":"string","enum":["simple","moderate","complex"]}},"required":["title","root_cause","severity","is_duplicate"]}},"novel_count":{"type":"number","description":"Count of novel (non-duplicate) findings"},"highest_severity":{"type":"string","enum":["critical","high","medium","low"]},"recommendation":{"type":"string"}},"required":["findings","novel_count","highest_severity","recommendation"]}},"create_issue":{"name":"Create Issues & Triage Duplicates","instruction":"Process ALL findings from the investigation. The findings array contains both novel and duplicate issues.\n\n**For each NOVEL finding** (is_duplicate = false):\n1. **First, check if a prior triage run already created an issue for this exact bug.** Call `linear_search_issues` (or `github_search_issues`) with the error message or root cause. If a matching issue already exists with the same root cause:\n - Treat it as a duplicate: DO NOT create a new issue.\n - Populate `issueIdentifier`, `issueTitle`, and `issueUrl` from the existing issue.\n - Add a \"+1\" comment if appropriate (using `linear_add_comment` or `github_add_comment`).\n - Set the `action` to `updated` in the `issues` array.\n - This prevents duplicate tickets when the same alert fires multiple times.\n\n2. If no matching issue exists, create a new one with a clear, actionable title by invoking `linear_create_issue` or `github_create_issue`. You MUST actually call the tool — do NOT synthesize an identifier from an alert ID, commit SHA, or your own imagination. If the tool errors, stop and report the failure; do not invent a fallback identifier. **Use the exact tool names above** — do NOT use generic alternatives like `create_issue` or `create_pull_request` from ToolSearch. The verify check requires the specific tool names listed here.\n3. Include: root cause, severity, affected services, reproduction steps, and recommended fix.\n4. Add appropriate labels (bug, severity level, affected service).\n5. Link to relevant commits, PRs, or existing issues.\n6. Copy the identifier returned by the tool verbatim into the `issueIdentifier` output field. The identifier must match `^[A-Z][A-Z0-9]*-\\d+$` (e.g. `OFF-1234`) — do not prefix it with anything else.\n\n**For each DUPLICATE finding** (is_duplicate = true):\n1. Find the existing issue (check duplicate_of field).\n2. Check the issue's comments — if the most recent comment is already from SWEny (contains \"+1\") within the last 24 hours, skip adding another comment.\n3. Otherwise add a SHORT comment: \"+1 — seen again {UTC timestamp}. {one sentence of new context}.\" (Keep it under 2 lines. No markdown headers, no emoji, no formatting.)\n4. If the existing issue is closed/done, reopen it.\n\nIf context.issueTemplate is provided, use it as the format for new issue bodies. Otherwise use a clear structure with: Summary, Root Cause, Impact, Steps to Reproduce, and Recommended Fix.\n\nUse the issue tracker specified in context (e.g. Linear, Jira, or GitHub Issues) — do NOT fall back to a different tracker. Output the created/updated issue identifiers.\n\n**IMPORTANT — scope boundaries for this node:**\n- DO NOT create branches, commits, or pull requests. The `implement` and `create_pr` nodes handle that.\n- DO NOT call `create_pull_request`, `github_create_pr`, or any PR/branch tools.\n- Your job is issue creation and duplicate management ONLY.","skills":["linear","github"],"verify":{"any_tool_called":["linear_create_issue","github_create_issue","linear_search_issues","github_search_issues","linear_add_comment","github_add_comment"]},"output":{"type":"object","properties":{"issueIdentifier":{"type":"string","pattern":"^[A-Z][A-Z0-9]*-\\d+$","description":"Primary issue identifier (e.g. OFF-1234). MUST be the identifier returned by `linear_create_issue` / `github_create_issue` — do NOT invent a value from a Sentry short-ID or commit SHA. Used by downstream nodes for branch naming and PR linking."},"issueTitle":{"type":"string","description":"Title of the primary issue created"},"issueUrl":{"type":"string","pattern":"^https?://","description":"URL of the primary issue created, as returned by the create-issue tool"},"issues":{"type":"array","minItems":1,"description":"All issues created or updated during this step","items":{"type":"object","properties":{"identifier":{"type":"string"},"title":{"type":"string"},"url":{"type":"string"},"action":{"type":"string","enum":["created","updated","reopened"]}},"required":["identifier","action"]}}},"required":["issueIdentifier","issueTitle","issueUrl","issues"]},"retry":{"max":1,"instruction":{"auto":true}}},"skip":{"name":"Skip — All Duplicates or Low Priority","instruction":"Every finding from the investigation was either a duplicate or low-priority. No new issues need to be created.\n\nFor each **duplicate** finding (check the findings array for items where is_duplicate = true):\n1. Find the existing issue (check duplicate_of field).\n2. Check the issue's comments — if the most recent comment is already from SWEny (contains \"+1\") within the last 24 hours, skip adding another comment.\n3. Otherwise add a SHORT comment: \"+1 — seen again {UTC timestamp}. {one sentence of new context}.\" (Keep it under 2 lines. No markdown headers, no emoji, no formatting.)\n4. If the issue is closed/done, reopen it.\n\nFor **low priority** findings, log a brief note about why they were skipped.","skills":["linear","github"]},"implement":{"name":"Implement Fix","instruction":"Implement the fix identified during investigation, plus the tests that pin\nit as a regression guard. The issue created in the previous step is in your\ncontext — use its identifier for branch naming and commit messages.\n\n## Quality bar (non-negotiable)\n\nEvery change you make must clear all of these. If you cannot, STOP and\nreturn a failed status — do not ship a half-measure.\n\n- **Correctness**: the fix actually closes the failure mode in the issue.\n- **Completeness**: handles the obvious edge cases (nulls, empties, off-by-one,\n concurrent paths). If the issue lists multiple symptoms, address them all\n or explicitly state which are out of scope and why.\n- **Idiomatic for THIS repo, not in general.** Read at least one\n neighboring file in the same module before you write any code, and\n match the patterns you see: error-handling primitives the codebase\n already uses, type discipline, naming, file layout. Examples to make\n the shape concrete:\n - In a NestJS service, throwing `NotFoundException` /\n `BadRequestException` is idiomatic; `throw new Error(...)` is not.\n - In a Go service, returning a wrapped `fmt.Errorf(\"...: %w\", err)`\n beats panicking.\n - In a TypeScript repo, narrow types properly — do not `as any` past\n the type checker.\n These are illustrations. Generalize: whatever the repo already does,\n keep doing.\n- **No hacky shortcuts**: no `try/catch` to swallow errors, no commented-out\n guards, no `// TODO: revisit`, no global flags to bypass validation, no\n copy-pasted bodies. If the right fix needs a small refactor (e.g. extract\n a helper), do it; if it needs a large refactor, file a follow-up issue\n and ship the minimal correct fix without the refactor.\n- **Well-tested**: see Test Requirements below.\n\n## Steps\n\n0. **Precondition — verify context.create_issue.issueIdentifier is well-formed.**\n The identifier MUST match `^[A-Z][A-Z0-9]*-\\d+$` (e.g. `OFF-1234`). If it\n is missing, empty, or matches a Sentry short-ID / slug / any other shape,\n STOP immediately — do NOT invent a branch name from the Sentry event,\n commit SHA, or alert payload. Return a failed status with a clear message\n instead; the workflow will route to notify and a human will pick it up.\n\n1. Create a feature branch from the base branch (check context for\n baseBranch, default `main`). **Branch name MUST include the issue\n identifier** exactly as provided in `context.create_issue.issueIdentifier`.\n Format: lowercase the identifier and use it as a prefix, e.g.\n `off-1234-fix-null-check`. If the repository uses a different branch\n convention (check context.branchPattern or the rules/CLAUDE.md for a\n regex), follow that — but the identifier piece MUST still come from\n `context.create_issue.issueIdentifier`, never from a Sentry short-ID.\n\n2. Read the relevant source files to understand the current code AND read\n the nearest existing test file (e.g. `foo.service.spec.ts` next to\n `foo.service.ts`, or `foo_test.py` next to `foo.py`) so your new tests\n match the existing test conventions: framework, mock style, factory use,\n describe/it shape, naming.\n\n3. Make the necessary code change. Keep it minimal and focused — do not\n refactor surrounding code or add unrelated improvements.\n\n4. **Write tests for the fix.** See Test Requirements below.\n\n5. **Run the test suite for the affected package** and confirm green\n (the package's `test` command from package.json, `pytest` for Python,\n etc.). If the test runner is not obvious, read the project's\n CLAUDE.md / README / package.json scripts. If you cannot run the\n tests at all in this environment, say so explicitly in the output —\n do not claim \"tests pass\" when you didn't run them.\n\n6. Stage and commit with a clear commit message. Start the message with the\n issue identifier in brackets, e.g.\n `[OFF-1234] fix: guard null projectId before database lookup`.\n\n## Test Requirements\n\nA code change without a test is unfinished work. Every fix this node ships\nMUST come with at least one new test case that:\n\n- **Would fail on the unfixed code.** A test that passes both before and\n after the fix is decoration, not a regression guard. Mentally run it\n against the previous version; if it would have passed, it is not the\n right test.\n- **Asserts the user-facing contract**, not just an implementation\n detail. For an API/handler bug: assert the response shape, status code,\n or error type the client sees. For a data corruption bug: assert the\n persisted row's final state, not just an internal call count.\n- **Covers the obvious adjacent edge cases.** If the bug was \"throws plain\n Error on missing record\", add the missing-record case AND at least one\n success-path case to confirm the fix didn't break the happy path.\n- **Lives in the right file** (next to the source under test, in the\n project's existing test layout).\n\nThe verify gate on this node REQUIRES `test_status` to be either\n`pass` (tests ran and all passed) OR `no-framework` (the project\ngenuinely has no test runner — not \"I couldn't be bothered\").\n\nIf the project HAS a test framework, you must write tests and they\nmust pass. Empty `test_files_changed` with `test_status: pass` is a\nlie and verify will reject it via the schema's required-fields\ncontract; `not-run` and `fail` will retry once with reflection and\nthen halt for human attention.\n\nIf the fix turns out to be more complex than expected, stop and\nexplain why — do not force a bad fix.","skills":["github"],"output":{"type":"object","properties":{"branch":{"type":"string","description":"Feature branch name (must include the issue identifier)."},"commit_sha":{"type":"string","description":"SHA of the commit shipping the fix and tests."},"files_changed":{"type":"array","description":"Source files modified by the fix (NOT including test files).","items":{"type":"string"}},"test_files_changed":{"type":"array","description":"Test files added or modified to cover the fix. Should be\nnon-empty whenever `test_status: pass`. May be empty when\n`test_status: no-framework` (genuinely no test runner in\nthe repo).\n","items":{"type":"string"}},"test_command":{"type":"string","description":"Exact command run to validate (e.g. `npx nx test server`)."},"test_status":{"type":"string","enum":["pass","fail","no-framework","not-run"],"description":"Outcome of running the test suite.\n- `pass` — tests ran end-to-end and every assertion passed.\n `test_files_changed` MUST be non-empty in this case (you\n changed code; tests must cover it).\n- `no-framework` — the repo genuinely has no test runner\n (e.g. a docs-only repo, a YAML-config repo, a research\n notebook collection). Reserved for cases where there is\n no test infrastructure to extend — not a permission slip\n to skip tests in a normal codebase.\n- `fail` — tests ran and at least one failed. Do not push\n a broken fix. Return this status, retry will fire once.\n- `not-run` — tests exist but the environment couldn't\n execute them. Explain in `test_notes`. Retry will fire.\n"},"test_notes":{"type":"string","description":"One-paragraph explanation of which tests were added and what they assert."}},"required":["branch","commit_sha","files_changed","test_files_changed","test_status"]},"verify":{"output_matches":[{"path":"test_status","in":["pass","no-framework"]}]},"retry":{"max":1,"instruction":{"auto":true}}},"create_pr":{"name":"Open Pull Request","instruction":"Open a pull request for the fix. The issue identifier and URL are in your context from the create_issue step.\n\n1. Push the branch to the remote.\n2. Create the PR using the `github_create_pr` tool. **Use this exact tool name** — do NOT use `create_pull_request` or any other variant from ToolSearch. The verify check requires `github_create_pr` specifically.\n3. PR title MUST start with the issue identifier in brackets. Format: `[{context.create_issue.issueIdentifier}] fix: {short description}`.\n4. In the PR body, include: summary of the bug, what the fix does, and a link to the issue (context.create_issue.issueUrl). If the investigation found a Sentry issue, include \"Fixes {sentryShortId}\" in the PR body so Sentry auto-resolves on merge.\n5. Add appropriate labels if available.\n\nIf context.prTemplate is provided, use it as the format for the PR body. Otherwise use a clear structure with: Summary, Changes, Testing, and Related Issues.\n\nIf context.cloudRunId is provided, append this HTML comment to the end of the PR body: <!-- sweny:run_id:{context.cloudRunId} -->\n\nReturn the PR URL and number.","skills":["github"],"verify":{"any_tool_called":["github_create_pr"]},"retry":{"max":1,"instruction":{"auto":true}}},"notify":{"name":"Notify Team","instruction":"Send a notification summarizing the triage result:\n\n1. Include: alert summary, severity, root cause (1-2 sentences), and links to any created issues or PRs.\n2. For critical/high severity, make the notification urgent.\n3. For medium/low, a standard notification is fine.\n\nUse whichever notification channel is available to you.","skills":["slack","notification"]}},"edges":[{"from":"gather","to":"investigate"},{"from":"investigate","to":"create_issue","when":"novel_count is greater than 0 AND highest_severity is medium or higher"},{"from":"investigate","to":"skip","when":"novel_count is 0, OR highest_severity is low"},{"from":"create_issue","to":"implement","when":"at least one novel finding has fix_complexity simple or moderate AND fix_approach is provided"},{"from":"create_issue","to":"notify","when":"all novel findings have fix_complexity complex, OR no clear fix_approach"},{"from":"skip","to":"notify"},{"from":"implement","to":"create_pr"},{"from":"create_pr","to":"notify"}],"skills":{}};
1
+ export const triageWorkflow = {"id":"triage","name":"Alert Triage","description":"Investigate a production alert, determine root cause, create an issue, implement a fix, and notify the team","entry":"gather","nodes":{"gather":{"name":"Gather Context","instruction":"You are investigating a production alert. Gather all relevant context using the available tools:\n\n1. **Observability**: Pull error details, stack traces, recent logs, metrics, and active incidents around the time of the alert. Use whichever observability tools are available to you.\n2. **Source control**: Check recent commits, pull requests, and deploys that might be related.\n3. **Issue tracker**: Search for similar past issues or known problems.\n\nBe thorough — the investigation step depends on complete context. Use every tool available to you.","skills":["github","sentry","datadog","betterstack","linear"]},"investigate":{"name":"Root Cause Analysis","instruction":"Based on the gathered context, classify every distinct issue you found into one of two buckets: **novel** or **duplicate**.\n\nFor EACH issue found:\n1. Identify the root cause and affected code/service.\n2. Assess severity: critical (service down), high (major feature broken), medium (degraded), low (cosmetic/minor).\n3. Assess fix complexity: \"simple\" (a few lines, clear change), \"moderate\" (multiple files but well-understood), or \"complex\" (architectural, risky, or unclear).\n4. **Novelty check (REQUIRED — you MUST call tools for this step):** Filing a duplicate ticket is worse than under-reporting — the team inherits sprawl that humans have to clean up later. Complete EVERY step below before classifying anything as novel. Do NOT skip these searches even if the gather context already mentions existing issues or branches — you must verify independently.\n4a. **Search broadly — THIS REQUIRES TOOL CALLS.** You MUST call `linear_search_issues` (or `github_search_issues`) with multiple queries against the issue tracker (BOTH open AND closed): the affected file or module path, the error message or log string verbatim, the feature or service name, and the symptom family (e.g. \"startup crash\", \"data dropped silently\"). Do not rely on a single keyword — a narrow search is how duplicates slip through. If you skip this search, the eval check will fail.\n4b. **Read the full body of every promising candidate — do NOT match on title alone.** For each top match, open the issue and read its entire description, including any \"Recommended Fix\", \"Proposed Fix\", \"Action Items\", or \"Acceptance Criteria\" section. An existing open issue whose recommended fix already prescribes the change you were about to propose IS a duplicate, even if the symptom you just observed is a new instance of the same underlying bug.\n4c. **Check for meta-issues and bug families.** If an existing ticket names a pattern that your finding is another instance of (same root cause, same function/module, different input or caller), treat your finding as a sibling of that family and comment on the existing ticket. Do not file a parallel ticket per instance — that is the exact pattern that produces backlog sprawl.\n4d. **When uncertain, comment don't file.** If you are less than ~80% confident a finding is truly novel after steps 4a–4c, classify it as a duplicate and add a \"+1 — another instance\" comment to the closest existing issue with the new context. Over-reporting duplicates is cheap; ticket sprawl is expensive.\n\n - If matched → **duplicate**. Record the existing issue ID in `duplicate_of`.\n - If no match after 4a–4d → **novel**.\n\n**Output rules:**\n- `findings`: array of ALL issues found (both novel and duplicate).\n- `novel_count`: how many findings are novel (not duplicates).\n- `highest_severity`: the highest severity across ALL findings.\n- `recommendation`: what should happen next.\n\nDownstream nodes will act ONLY on novel findings. Duplicates will be +1'd automatically.\n\n**IMPORTANT — scope boundaries for this node:**\n- DO NOT create issues (Linear, GitHub, or any tracker). A downstream `create_issue` node handles that.\n- DO NOT create branches, commits, or pull requests. A downstream `implement` node handles that.\n- DO NOT call any write tools (`linear_create_issue`, `github_create_issue`, `github_create_pr`, `linear_add_comment`, `github_add_comment`, etc.). Downstream nodes handle all writes — issue creation, +1 comments, and PRs.\n- Your ONLY job is read, search, classify, and output. If you create an issue or add a comment here, the downstream eval check will fail or duplicate work.","skills":["github","linear"],"eval":[{"name":"novelty_search_was_performed","kind":"function","rule":{"any_tool_called":["linear_search_issues","github_search_issues"]}}],"output":{"type":"object","properties":{"findings":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"Short description of the issue"},"root_cause":{"type":"string"},"severity":{"type":"string","enum":["critical","high","medium","low"]},"affected_services":{"type":"array","items":{"type":"string"}},"is_duplicate":{"type":"boolean"},"duplicate_of":{"type":"string","description":"Existing issue ID/URL if duplicate"},"fix_approach":{"type":"string"},"fix_complexity":{"type":"string","enum":["simple","moderate","complex"]}},"required":["title","root_cause","severity","is_duplicate"]}},"novel_count":{"type":"number","description":"Count of novel (non-duplicate) findings"},"highest_severity":{"type":"string","enum":["critical","high","medium","low"]},"recommendation":{"type":"string"}},"required":["findings","novel_count","highest_severity","recommendation"]}},"create_issue":{"name":"Create Issues & Triage Duplicates","instruction":"Process ALL findings from the investigation. The findings array contains both novel and duplicate issues.\n\n**For each NOVEL finding** (is_duplicate = false):\n1. **First, check if a prior triage run already created an issue for this exact bug.** Call `linear_search_issues` (or `github_search_issues`) with the error message or root cause. If a matching issue already exists with the same root cause:\n - Treat it as a duplicate: DO NOT create a new issue.\n - Populate `issueIdentifier`, `issueTitle`, and `issueUrl` from the existing issue.\n - Add a \"+1\" comment if appropriate (using `linear_add_comment` or `github_add_comment`).\n - Set the `action` to `updated` in the `issues` array.\n - This prevents duplicate tickets when the same alert fires multiple times.\n\n2. If no matching issue exists, create a new one with a clear, actionable title by invoking `linear_create_issue` or `github_create_issue`. You MUST actually call the tool — do NOT synthesize an identifier from an alert ID, commit SHA, or your own imagination. If the tool errors, stop and report the failure; do not invent a fallback identifier. **Use the exact tool names above** — do NOT use generic alternatives like `create_issue` or `create_pull_request` from ToolSearch. The eval check requires the specific tool names listed here.\n3. Include: root cause, severity, affected services, reproduction steps, and recommended fix.\n4. Add appropriate labels (bug, severity level, affected service).\n5. Link to relevant commits, PRs, or existing issues.\n6. Copy the identifier returned by the tool verbatim into the `issueIdentifier` output field. The identifier must match `^[A-Z][A-Z0-9]*-\\d+$` (e.g. `OFF-1234`) — do not prefix it with anything else.\n\n**For each DUPLICATE finding** (is_duplicate = true):\n1. Find the existing issue (check duplicate_of field).\n2. Check the issue's comments — if the most recent comment is already from SWEny (contains \"+1\") within the last 24 hours, skip adding another comment.\n3. Otherwise add a SHORT comment: \"+1 — seen again {UTC timestamp}. {one sentence of new context}.\" (Keep it under 2 lines. No markdown headers, no emoji, no formatting.)\n4. If the existing issue is closed/done, reopen it.\n\nIf context.issueTemplate is provided, use it as the format for new issue bodies. Otherwise use a clear structure with: Summary, Root Cause, Impact, Steps to Reproduce, and Recommended Fix.\n\nUse the issue tracker specified in context (e.g. Linear, Jira, or GitHub Issues) — do NOT fall back to a different tracker. Output the created/updated issue identifiers.\n\n**IMPORTANT — scope boundaries for this node:**\n- DO NOT create branches, commits, or pull requests. The `implement` and `create_pr` nodes handle that.\n- DO NOT call `create_pull_request`, `github_create_pr`, or any PR/branch tools.\n- Your job is issue creation and duplicate management ONLY.","skills":["linear","github"],"eval":[{"name":"issue_tracker_tool_was_called","kind":"function","rule":{"any_tool_called":["linear_create_issue","github_create_issue","linear_search_issues","github_search_issues","linear_add_comment","github_add_comment"]}}],"output":{"type":"object","properties":{"issueIdentifier":{"type":"string","pattern":"^[A-Z][A-Z0-9]*-\\d+$","description":"Primary issue identifier (e.g. OFF-1234). MUST be the identifier returned by `linear_create_issue` / `github_create_issue` — do NOT invent a value from a Sentry short-ID or commit SHA. Used by downstream nodes for branch naming and PR linking."},"issueTitle":{"type":"string","description":"Title of the primary issue created"},"issueUrl":{"type":"string","pattern":"^https?://","description":"URL of the primary issue created, as returned by the create-issue tool"},"issues":{"type":"array","minItems":1,"description":"All issues created or updated during this step","items":{"type":"object","properties":{"identifier":{"type":"string"},"title":{"type":"string"},"url":{"type":"string"},"action":{"type":"string","enum":["created","updated","reopened"]}},"required":["identifier","action"]}}},"required":["issueIdentifier","issueTitle","issueUrl","issues"]},"retry":{"max":1,"instruction":{"auto":true}}},"skip":{"name":"Skip — All Duplicates or Low Priority","instruction":"Every finding from the investigation was either a duplicate or low-priority. No new issues need to be created.\n\nFor each **duplicate** finding (check the findings array for items where is_duplicate = true):\n1. Find the existing issue (check duplicate_of field).\n2. Check the issue's comments — if the most recent comment is already from SWEny (contains \"+1\") within the last 24 hours, skip adding another comment.\n3. Otherwise add a SHORT comment: \"+1 — seen again {UTC timestamp}. {one sentence of new context}.\" (Keep it under 2 lines. No markdown headers, no emoji, no formatting.)\n4. If the issue is closed/done, reopen it.\n\nFor **low priority** findings, log a brief note about why they were skipped.","skills":["linear","github"]},"implement":{"name":"Implement Fix","instruction":"Implement the fix identified during investigation, plus the tests that pin\nit as a regression guard. The issue created in the previous step is in your\ncontext — use its identifier for branch naming and commit messages.\n\n## Quality bar (non-negotiable)\n\nEvery change you make must clear all of these. If you cannot, STOP and\nreturn a failed status — do not ship a half-measure.\n\n- **Correctness**: the fix actually closes the failure mode in the issue.\n- **Completeness**: handles the obvious edge cases (nulls, empties, off-by-one,\n concurrent paths). If the issue lists multiple symptoms, address them all\n or explicitly state which are out of scope and why.\n- **Idiomatic for THIS repo, not in general.** Read at least one\n neighboring file in the same module before you write any code, and\n match the patterns you see: error-handling primitives the codebase\n already uses, type discipline, naming, file layout. Examples to make\n the shape concrete:\n - In a NestJS service, throwing `NotFoundException` /\n `BadRequestException` is idiomatic; `throw new Error(...)` is not.\n - In a Go service, returning a wrapped `fmt.Errorf(\"...: %w\", err)`\n beats panicking.\n - In a TypeScript repo, narrow types properly — do not `as any` past\n the type checker.\n These are illustrations. Generalize: whatever the repo already does,\n keep doing.\n- **No hacky shortcuts**: no `try/catch` to swallow errors, no commented-out\n guards, no `// TODO: revisit`, no global flags to bypass validation, no\n copy-pasted bodies. If the right fix needs a small refactor (e.g. extract\n a helper), do it; if it needs a large refactor, file a follow-up issue\n and ship the minimal correct fix without the refactor.\n- **Well-tested**: see Test Requirements below.\n\n## Steps\n\n0. **Precondition — verify context.create_issue.issueIdentifier is well-formed.**\n The identifier MUST match `^[A-Z][A-Z0-9]*-\\d+$` (e.g. `OFF-1234`). If it\n is missing, empty, or matches a Sentry short-ID / slug / any other shape,\n STOP immediately — do NOT invent a branch name from the Sentry event,\n commit SHA, or alert payload. Return a failed status with a clear message\n instead; the workflow will route to notify and a human will pick it up.\n\n1. Create a feature branch from the base branch (check context for\n baseBranch, default `main`). **Branch name MUST include the issue\n identifier** exactly as provided in `context.create_issue.issueIdentifier`.\n Format: lowercase the identifier and use it as a prefix, e.g.\n `off-1234-fix-null-check`. If the repository uses a different branch\n convention (check context.branchPattern or the rules/CLAUDE.md for a\n regex), follow that — but the identifier piece MUST still come from\n `context.create_issue.issueIdentifier`, never from a Sentry short-ID.\n\n2. Read the relevant source files to understand the current code AND read\n the nearest existing test file (e.g. `foo.service.spec.ts` next to\n `foo.service.ts`, or `foo_test.py` next to `foo.py`) so your new tests\n match the existing test conventions: framework, mock style, factory use,\n describe/it shape, naming.\n\n3. Make the necessary code change. Keep it minimal and focused — do not\n refactor surrounding code or add unrelated improvements.\n\n4. **Write tests for the fix.** See Test Requirements below.\n\n5. **Run the test suite for the affected package** and confirm green\n (the package's `test` command from package.json, `pytest` for Python,\n etc.). If the test runner is not obvious, read the project's\n CLAUDE.md / README / package.json scripts. If you cannot run the\n tests at all in this environment, say so explicitly in the output —\n do not claim \"tests pass\" when you didn't run them.\n\n6. Stage and commit with a clear commit message. Start the message with the\n issue identifier in brackets, e.g.\n `[OFF-1234] fix: guard null projectId before database lookup`.\n\n## Test Requirements\n\nA code change without a test is unfinished work. Every fix this node ships\nMUST come with at least one new test case that:\n\n- **Would fail on the unfixed code.** A test that passes both before and\n after the fix is decoration, not a regression guard. Mentally run it\n against the previous version; if it would have passed, it is not the\n right test.\n- **Asserts the user-facing contract**, not just an implementation\n detail. For an API/handler bug: assert the response shape, status code,\n or error type the client sees. For a data corruption bug: assert the\n persisted row's final state, not just an internal call count.\n- **Covers the obvious adjacent edge cases.** If the bug was \"throws plain\n Error on missing record\", add the missing-record case AND at least one\n success-path case to confirm the fix didn't break the happy path.\n- **Lives in the right file** (next to the source under test, in the\n project's existing test layout).\n\nThe eval gate on this node REQUIRES `test_status` to be either\n`pass` (tests ran and all passed) OR `no-framework` (the project\ngenuinely has no test runner — not \"I couldn't be bothered\").\n\nIf the project HAS a test framework, you must write tests and they\nmust pass. Empty `test_files_changed` with `test_status: pass` is a\nlie and eval will reject it via the schema's required-fields\ncontract; `not-run` and `fail` will retry once with reflection and\nthen halt for human attention.\n\nIf the fix turns out to be more complex than expected, stop and\nexplain why — do not force a bad fix.","skills":["github"],"output":{"type":"object","properties":{"branch":{"type":"string","description":"Feature branch name (must include the issue identifier)."},"commit_sha":{"type":"string","description":"SHA of the commit shipping the fix and tests."},"files_changed":{"type":"array","description":"Source files modified by the fix (NOT including test files).","items":{"type":"string"}},"test_files_changed":{"type":"array","description":"Test files added or modified to cover the fix. Should be\nnon-empty whenever `test_status: pass`. May be empty when\n`test_status: no-framework` (genuinely no test runner in\nthe repo).\n","items":{"type":"string"}},"test_command":{"type":"string","description":"Exact command run to validate (e.g. `npx nx test server`)."},"test_status":{"type":"string","enum":["pass","fail","no-framework","not-run"],"description":"Outcome of running the test suite.\n- `pass` — tests ran end-to-end and every assertion passed.\n `test_files_changed` MUST be non-empty in this case (you\n changed code; tests must cover it).\n- `no-framework` — the repo genuinely has no test runner\n (e.g. a docs-only repo, a YAML-config repo, a research\n notebook collection). Reserved for cases where there is\n no test infrastructure to extend — not a permission slip\n to skip tests in a normal codebase.\n- `fail` — tests ran and at least one failed. Do not push\n a broken fix. Return this status, retry will fire once.\n- `not-run` — tests exist but the environment couldn't\n execute them. Explain in `test_notes`. Retry will fire.\n"},"test_notes":{"type":"string","description":"One-paragraph explanation of which tests were added and what they assert."}},"required":["branch","commit_sha","files_changed","test_files_changed","test_status"]},"eval":[{"name":"test_status_is_pass_or_no_framework","kind":"value","rule":{"output_matches":[{"path":"test_status","in":["pass","no-framework"]}]}},{"name":"tests_present_when_pass_claimed","kind":"judge","rubric":"You are checking the contract between test_status and test_files_changed.\n\nRead result.data.test_status and result.data.test_files_changed.\n\nRules:\n- If test_status is \"pass\": test_files_changed MUST contain at least\n one real test file path (paths with extensions like .spec.ts,\n .test.ts, .test.tsx, .test.py, _test.go, _spec.rb, .test.rs,\n etc., or paths in a __tests__/, tests/, or spec/ directory).\n An empty array, or paths that are clearly not test files\n (README.md, package.json, source files), is a contract violation.\n- If test_status is \"no-framework\": test_files_changed MAY be empty.\n Pass vacuously.\n- If test_status is \"fail\" or \"not-run\": this evaluator should not\n run (the prior value evaluator catches that). Pass vacuously.\n\nRespond yes if the contract is satisfied, no if it is violated.\n","pass_when":"yes"}],"retry":{"max":1,"instruction":{"auto":true}}},"create_pr":{"name":"Open Pull Request","instruction":"Open a pull request for the fix. The issue identifier and URL are in your context from the create_issue step.\n\n1. Push the branch to the remote.\n2. Create the PR using the `github_create_pr` tool. **Use this exact tool name** — do NOT use `create_pull_request` or any other variant from ToolSearch. The eval check requires `github_create_pr` specifically.\n3. PR title MUST start with the issue identifier in brackets. Format: `[{context.create_issue.issueIdentifier}] fix: {short description}`.\n4. In the PR body, include: summary of the bug, what the fix does, and a link to the issue (context.create_issue.issueUrl). If the investigation found a Sentry issue, include \"Fixes {sentryShortId}\" in the PR body so Sentry auto-resolves on merge.\n5. Add appropriate labels if available.\n\nIf context.prTemplate is provided, use it as the format for the PR body. Otherwise use a clear structure with: Summary, Changes, Testing, and Related Issues.\n\nIf context.cloudRunId is provided, append this HTML comment to the end of the PR body: <!-- sweny:run_id:{context.cloudRunId} -->\n\nReturn the PR URL and number.","skills":["github"],"eval":[{"name":"pr_was_created","kind":"function","rule":{"any_tool_called":["github_create_pr"]}}],"retry":{"max":1,"instruction":{"auto":true}}},"notify":{"name":"Notify Team","instruction":"Send a notification summarizing the triage result:\n\n1. Include: alert summary, severity, root cause (1-2 sentences), and links to any created issues or PRs.\n2. For critical/high severity, make the notification urgent.\n3. For medium/low, a standard notification is fine.\n\nUse whichever notification channel is available to you.","skills":["slack","notification"]}},"edges":[{"from":"gather","to":"investigate"},{"from":"investigate","to":"create_issue","when":"novel_count is greater than 0 AND highest_severity is medium or higher"},{"from":"investigate","to":"skip","when":"novel_count is 0, OR highest_severity is low"},{"from":"create_issue","to":"implement","when":"at least one novel finding has fix_complexity simple or moderate AND fix_approach is provided"},{"from":"create_issue","to":"notify","when":"all novel findings have fix_complexity complex, OR no clear fix_approach"},{"from":"skip","to":"notify"},{"from":"implement","to":"create_pr"},{"from":"create_pr","to":"notify"}],"skills":{}};
2
2
  export const implementWorkflow = {"id":"implement","name":"Implement Fix","description":"Implement a code fix for an issue and open a pull request","entry":"analyze","nodes":{"analyze":{"name":"Analyze Issue","instruction":"Read the issue details and understand what needs to be fixed:\n\n1. Fetch the issue from the tracker (GitHub or Linear).\n2. Read the relevant source files to understand the current code.\n3. Identify the exact files and lines that need to change.\n4. Plan the fix approach.\n\nOutput a clear analysis of what needs to change and why.","skills":["github","linear"],"output":{"type":"object","properties":{"issue_summary":{"type":"string"},"files_to_change":{"type":"array","items":{"type":"string"}},"fix_plan":{"type":"string"},"risk_level":{"type":"string","enum":["low","medium","high"]}},"required":["issue_summary","fix_plan"]}},"implement":{"name":"Implement Fix","instruction":"Implement the planned fix:\n\n1. Create a feature branch.\n2. Make the necessary code changes.\n3. Ensure changes are minimal and focused — fix the bug, nothing more.\n4. Stage and commit with a clear commit message referencing the issue.\n\nIf the fix is too risky or complex, explain why and skip.","skills":["github"]},"create_pr":{"name":"Open Pull Request","instruction":"Open a pull request for the fix:\n\n1. Push the branch to the remote.\n2. Create a PR with a clear title and description.\n3. Reference the original issue in the PR body.\n4. Add appropriate reviewers or labels if possible.\n\nIf context.prTemplate is provided, use it as the format for the PR body. Otherwise use a clear structure with: Summary, Changes, Testing, and Related Issues.\n\nIf context.cloudRunId is provided, append this HTML comment to the end of the PR body: <!-- sweny:run_id:{context.cloudRunId} -->\n\nReturn the PR URL.","skills":["github"]},"notify":{"name":"Notify","instruction":"Send a notification about the implementation result:\n\n1. Include: issue reference, PR link, brief summary of changes.\n2. Keep it concise — one message, not a wall of text.","skills":["slack","notification"]},"skip":{"name":"Skip — Too Complex","instruction":"The fix was determined to be too complex or risky for automated implementation.\nAdd a comment to the issue explaining what was found and why it needs manual attention.","skills":["github","linear"]}},"edges":[{"from":"analyze","to":"implement","when":"Fix risk level is low or medium and a clear plan exists"},{"from":"analyze","to":"skip","when":"Fix is too complex, risky, or unclear"},{"from":"implement","to":"create_pr"},{"from":"skip","to":"notify"},{"from":"create_pr","to":"notify"}],"skills":{}};
3
3
  export const seedContentWorkflow = {"id":"seed-content","name":"Content Generation Pipeline","description":"Audit content gaps, generate educational content, validate quality, and publish to Supabase","entry":"audit","nodes":{"audit":{"name":"Audit Content Gaps","instruction":"You are auditing an educational platform's content library.\n\nUse supabase_count to check how many items exist for each combination of:\n- Content types: worksheets, games, lessons\n- Subjects: math, science, english, history, geography, economics, music, art, life-skills, foreign-language\n- Difficulties: beginner, easy, medium, hard\n\nAlso check the input parameters — if the user specified a subject, topic, difficulty, and count,\nskip the full audit and just report what they want generated.\n\nReturn a structured content plan: what to generate, how many, at what difficulty and grade level.\nPrioritize gaps where a subject has ZERO content of a given type.","skills":["supabase"],"output":{"type":"object","properties":{"plan":{"type":"array","items":{"type":"object","properties":{"contentType":{"type":"string"},"subject":{"type":"string"},"topic":{"type":"string"},"difficulty":{"type":"string"},"gradeLevel":{"type":"string"},"count":{"type":"number"},"reason":{"type":"string"}},"required":["contentType","subject","topic","difficulty","count"]}},"summary":{"type":"string"}},"required":["plan","summary"]}},"generate":{"name":"Generate Content","instruction":"You are an expert educator creating content for children ages 5-14.\n\nFor each item in the content plan from the previous step, generate the content directly.\nThe content must be high-quality, engaging, age-appropriate, and educationally sound.\n\n**For worksheets:**\nGenerate a JSON array of problems. Each problem needs: \"problem\", \"answer\", \"format\" (answer-line | multiple-choice | write-on).\nRequirements:\n- Every answer MUST be correct. Double-check math, facts, spelling.\n- Problems must progress in difficulty within the set.\n- Use at least 2 different problem structures (e.g., equations + word problems for math).\n- Vary phrasing — no two problems should feel copy-pasted.\n- For the target grade level, use appropriate vocabulary and complexity.\n\n**For lessons:**\nGenerate complete HTML + CSS + JS for an interactive lesson.\nRequirements:\n- Must have substantial educational content (not just a shell with navigation).\n- Include interactive elements: quizzes, click-to-reveal, drag-and-drop.\n- Must be visually engaging with colors, icons, and clear typography.\n- Include practice exercises with immediate feedback.\n- Minimum 5 teaching sections plus 2 practice activities.\n\n**For games:**\nGenerate a description and educational requirements only (actual game code is generated\nby the platform's Phaser-based generator).\n\nUse supabase_insert to write each piece of content to the appropriate table.\n\nTable schemas:\n- worksheets: id (auto), title, subject, topic, difficulty, grade_level, num_questions, problems (jsonb), status='published', source='ai-seed'\n- lessons: id (auto), title, subject, difficulty, grade_level, lesson_type, html_code, css_code, js_code, status='published', source='ai-seed'\n- games: id (auto), title, subject, difficulty, grade_level, game_type, html_code, css_code, js_code, status='published', source='ai-seed'\n\nReturn a summary of what was generated and inserted.","skills":["supabase"],"output":{"type":"object","properties":{"generated":{"type":"array","items":{"type":"object","properties":{"contentType":{"type":"string"},"title":{"type":"string"},"subject":{"type":"string"},"id":{"type":"string"},"status":{"type":"string"}}}},"totalGenerated":{"type":"number"},"errors":{"type":"array","items":{"type":"string"}}},"required":["generated","totalGenerated"]}},"validate":{"name":"Validate Content Quality","instruction":"Review the content that was just generated and inserted.\n\nFor each item, use supabase_query to fetch it back and verify:\n\n**Worksheets:**\n- Parse the problems JSON — are all answers correct?\n- Are problems diverse (not repetitive)?\n- Is difficulty appropriate for the stated grade level?\n- Are there the right number of questions?\n\n**Lessons:**\n- Is the HTML substantial (> 500 characters)?\n- Does it contain actual educational content (not just navigation shells)?\n- Are there interactive elements?\n\n**Games:**\n- Does the HTML contain a working game structure?\n\nFlag any issues. If critical issues are found (wrong answers, empty content),\nuse supabase_update to set status='draft' and add a note.\n\nReturn a quality report.","skills":["supabase"],"output":{"type":"object","properties":{"totalReviewed":{"type":"number"},"passed":{"type":"number"},"flagged":{"type":"number"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"issue":{"type":"string"},"severity":{"type":"string"},"action":{"type":"string"}}}},"summary":{"type":"string"}},"required":["totalReviewed","passed","flagged","summary"]}},"report":{"name":"Generate Report","instruction":"Compile a final report of the content generation run.\n\nInclude:\n- Total content generated by type and subject\n- Quality validation results\n- Any issues found and actions taken\n- Recommendations for next content generation run (what gaps remain)\n\nFormat as a clean markdown summary that a solo operator can scan in 30 seconds.","skills":[],"output":{"type":"object","properties":{"markdown":{"type":"string"},"nextPriorities":{"type":"array","items":{"type":"string"}}},"required":["markdown"]}}},"edges":[{"from":"audit","to":"generate"},{"from":"generate","to":"validate"},{"from":"validate","to":"report"}],"skills":{}};
@@ -49,7 +49,7 @@ nodes:
49
49
  `github_search_issues`) with multiple queries against the issue tracker (BOTH open AND closed): the
50
50
  affected file or module path, the error message or log string verbatim, the feature or service name,
51
51
  and the symptom family (e.g. "startup crash", "data dropped silently"). Do not rely on a single
52
- keyword — a narrow search is how duplicates slip through. If you skip this search, the verify check
52
+ keyword — a narrow search is how duplicates slip through. If you skip this search, the eval check
53
53
  will fail.
54
54
 
55
55
  4b. **Read the full body of every promising candidate — do NOT match on title alone.** For each top match,
@@ -95,17 +95,20 @@ nodes:
95
95
  Downstream nodes handle all writes — issue creation, +1 comments, and PRs.
96
96
 
97
97
  - Your ONLY job is read, search, classify, and output. If you create an issue
98
- or add a comment here, the downstream verify check will fail or duplicate work.
98
+ or add a comment here, the downstream eval check will fail or duplicate work.
99
99
  skills:
100
100
  - github
101
101
  - linear
102
- verify:
102
+ eval:
103
103
  # The novelty check REQUIRES searching the issue tracker. If the node finishes
104
104
  # with 0 search tool calls, it means the agent skipped the novelty check entirely
105
105
  # and any "novel" classification is unreliable.
106
- any_tool_called:
107
- - linear_search_issues
108
- - github_search_issues
106
+ - name: novelty_search_was_performed
107
+ kind: function
108
+ rule:
109
+ any_tool_called:
110
+ - linear_search_issues
111
+ - github_search_issues
109
112
  output:
110
113
  type: object
111
114
  properties:
@@ -187,7 +190,7 @@ nodes:
187
190
  synthesize an identifier from an alert ID, commit SHA, or your own imagination. If the
188
191
  tool errors, stop and report the failure; do not invent a fallback identifier.
189
192
  **Use the exact tool names above** — do NOT use generic alternatives like `create_issue`
190
- or `create_pull_request` from ToolSearch. The verify check requires the specific tool
193
+ or `create_pull_request` from ToolSearch. The eval check requires the specific tool
191
194
  names listed here.
192
195
 
193
196
  3. Include: root cause, severity, affected services, reproduction steps, and recommended fix.
@@ -232,18 +235,21 @@ nodes:
232
235
  skills:
233
236
  - linear
234
237
  - github
235
- verify:
238
+ eval:
236
239
  # Require a real issue-tracker tool call. linear_create_issue / github_create_issue
237
240
  # for novel findings; linear_search_issues / github_search_issues for duplicate-check
238
241
  # when the issue already exists from a prior triage run. linear_add_comment covers
239
242
  # the +1 path for duplicates.
240
- any_tool_called:
241
- - linear_create_issue
242
- - github_create_issue
243
- - linear_search_issues
244
- - github_search_issues
245
- - linear_add_comment
246
- - github_add_comment
243
+ - name: issue_tracker_tool_was_called
244
+ kind: function
245
+ rule:
246
+ any_tool_called:
247
+ - linear_create_issue
248
+ - github_create_issue
249
+ - linear_search_issues
250
+ - github_search_issues
251
+ - linear_add_comment
252
+ - github_add_comment
247
253
  output:
248
254
  type: object
249
255
  properties:
@@ -411,13 +417,13 @@ nodes:
411
417
  - **Lives in the right file** (next to the source under test, in the
412
418
  project's existing test layout).
413
419
 
414
- The verify gate on this node REQUIRES `test_status` to be either
420
+ The eval gate on this node REQUIRES `test_status` to be either
415
421
  `pass` (tests ran and all passed) OR `no-framework` (the project
416
422
  genuinely has no test runner — not "I couldn't be bothered").
417
423
 
418
424
  If the project HAS a test framework, you must write tests and they
419
425
  must pass. Empty `test_files_changed` with `test_status: pass` is a
420
- lie and verify will reject it via the schema's required-fields
426
+ lie and eval will reject it via the schema's required-fields
421
427
  contract; `not-run` and `fail` will retry once with reflection and
422
428
  then halt for human attention.
423
429
 
@@ -477,14 +483,45 @@ nodes:
477
483
  - files_changed
478
484
  - test_files_changed
479
485
  - test_status
480
- verify:
486
+ eval:
481
487
  # `pass` and `no-framework` both clear the gate. `fail` and `not-run`
482
- # trigger retry. The instruction text + schema's required-fields list
483
- # are the contract that prevents `pass` with empty `test_files_changed`
484
- # — verify cannot express that conditional declaratively.
485
- output_matches:
486
- - path: test_status
487
- in: [pass, no-framework]
488
+ # trigger retry.
489
+ - name: test_status_is_pass_or_no_framework
490
+ kind: value
491
+ rule:
492
+ output_matches:
493
+ - path: test_status
494
+ in: [pass, no-framework]
495
+ # Conditional: when test_status is pass, test_files_changed MUST contain
496
+ # at least one real test file path. Empty array with status pass is a
497
+ # contract violation (the agent claimed it ran tests without actually
498
+ # writing any). value evaluators can't express the conditional
499
+ # declaratively; the judge absorbs it.
500
+ #
501
+ # Driving incident: PR #366, where the triage agent shipped a NestJS
502
+ # fix to letsoffload/offload without writing tests, even though
503
+ # *.spec.ts existed in the repo.
504
+ - name: tests_present_when_pass_claimed
505
+ kind: judge
506
+ rubric: |
507
+ You are checking the contract between test_status and test_files_changed.
508
+
509
+ Read result.data.test_status and result.data.test_files_changed.
510
+
511
+ Rules:
512
+ - If test_status is "pass": test_files_changed MUST contain at least
513
+ one real test file path (paths with extensions like .spec.ts,
514
+ .test.ts, .test.tsx, .test.py, _test.go, _spec.rb, .test.rs,
515
+ etc., or paths in a __tests__/, tests/, or spec/ directory).
516
+ An empty array, or paths that are clearly not test files
517
+ (README.md, package.json, source files), is a contract violation.
518
+ - If test_status is "no-framework": test_files_changed MAY be empty.
519
+ Pass vacuously.
520
+ - If test_status is "fail" or "not-run": this evaluator should not
521
+ run (the prior value evaluator catches that). Pass vacuously.
522
+
523
+ Respond yes if the contract is satisfied, no if it is violated.
524
+ pass_when: yes
488
525
  retry:
489
526
  max: 1
490
527
  instruction:
@@ -498,7 +535,7 @@ nodes:
498
535
  1. Push the branch to the remote.
499
536
 
500
537
  2. Create the PR using the `github_create_pr` tool. **Use this exact tool name** — do NOT
501
- use `create_pull_request` or any other variant from ToolSearch. The verify check requires
538
+ use `create_pull_request` or any other variant from ToolSearch. The eval check requires
502
539
  `github_create_pr` specifically.
503
540
 
504
541
  3. PR title MUST start with the issue identifier in brackets.
@@ -522,9 +559,12 @@ nodes:
522
559
  Return the PR URL and number.
523
560
  skills:
524
561
  - github
525
- verify:
526
- any_tool_called:
527
- - github_create_pr
562
+ eval:
563
+ - name: pr_was_created
564
+ kind: function
565
+ rule:
566
+ any_tool_called:
567
+ - github_create_pr
528
568
  retry:
529
569
  max: 1
530
570
  instruction:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.84",
3
+ "version": "0.1.86",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"
package/dist/verify.d.ts DELETED
@@ -1,25 +0,0 @@
1
- import type { NodeResult, NodeVerify, OutputMatch, ToolCall } from "./types.js";
2
- export type Resolution = {
3
- ok: true;
4
- mode: "all" | "any";
5
- values: unknown[];
6
- } | {
7
- ok: false;
8
- reason: string;
9
- };
10
- export declare function resolvePath(data: unknown, path: string): Resolution;
11
- export declare function isErrorOutput(output: unknown): boolean;
12
- export type ToolAliases = ReadonlyMap<string, ReadonlySet<string>>;
13
- export declare function checkAnyToolCalled(required: string[], toolCalls: ToolCall[], aliases?: ToolAliases): string | null;
14
- export declare function checkAllToolsCalled(required: string[], toolCalls: ToolCall[], aliases?: ToolAliases): string | null;
15
- export declare function checkNoToolCalled(forbidden: string[], toolCalls: ToolCall[], aliases?: ToolAliases): string | null;
16
- export declare function checkOutputRequired(paths: string[], data: unknown): string | null;
17
- export declare function checkOutputMatches(matches: OutputMatch[], data: unknown): string | null;
18
- /**
19
- * Evaluate every declared check and return a concatenated error string,
20
- * or null when the node passes verification (or has no verify block).
21
- *
22
- * Deterministic and side-effect free — the executor calls this synchronously
23
- * after the LLM finishes.
24
- */
25
- export declare function evaluateVerify(verify: NodeVerify | undefined, result: NodeResult, aliases?: ToolAliases): string | null;