@codyswann/lisa 2.174.0 → 2.175.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.
Files changed (126) hide show
  1. package/package.json +1 -1
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa/commands/monitor.md +3 -3
  5. package/plugins/lisa/rules/eager/leaf-only-lifecycle.md +2 -0
  6. package/plugins/lisa/rules/eager/observability-audit.md +15 -0
  7. package/plugins/lisa/rules/reference/config-resolution.md +35 -0
  8. package/plugins/lisa/rules/reference/intent-routing.md +8 -3
  9. package/plugins/lisa/rules/reference/leaf-only-lifecycle.md +1 -0
  10. package/plugins/lisa/rules/reference/observability-audit.md +122 -0
  11. package/plugins/lisa/skills/github-build-intake/SKILL.md +25 -0
  12. package/plugins/lisa/skills/intake/SKILL.md +1 -0
  13. package/plugins/lisa/skills/jira-build-intake/SKILL.md +19 -0
  14. package/plugins/lisa/skills/linear-build-intake/SKILL.md +19 -0
  15. package/plugins/lisa/skills/monitor/SKILL.md +27 -6
  16. package/plugins/lisa/skills/monitor/agents/openai.yaml +2 -2
  17. package/plugins/lisa/skills/ticket-triage/SKILL.md +10 -6
  18. package/plugins/lisa/skills/tracker-build-intake/SKILL.md +21 -0
  19. package/plugins/lisa/skills/tracker-verify/SKILL.md +1 -0
  20. package/plugins/lisa/skills/verify/SKILL.md +1 -1
  21. package/plugins/lisa-agy/commands/monitor.md +3 -3
  22. package/plugins/lisa-agy/plugin.json +1 -1
  23. package/plugins/lisa-agy/skills/github-build-intake/SKILL.md +25 -0
  24. package/plugins/lisa-agy/skills/intake/SKILL.md +1 -0
  25. package/plugins/lisa-agy/skills/jira-build-intake/SKILL.md +19 -0
  26. package/plugins/lisa-agy/skills/linear-build-intake/SKILL.md +19 -0
  27. package/plugins/lisa-agy/skills/monitor/SKILL.md +27 -6
  28. package/plugins/lisa-agy/skills/ticket-triage/SKILL.md +10 -6
  29. package/plugins/lisa-agy/skills/tracker-build-intake/SKILL.md +21 -0
  30. package/plugins/lisa-agy/skills/tracker-verify/SKILL.md +1 -0
  31. package/plugins/lisa-agy/skills/verify/SKILL.md +1 -1
  32. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  34. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  35. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-copilot/commands/monitor.md +3 -3
  39. package/plugins/lisa-copilot/rules/eager/leaf-only-lifecycle.md +2 -0
  40. package/plugins/lisa-copilot/rules/eager/observability-audit.md +15 -0
  41. package/plugins/lisa-copilot/rules/reference/config-resolution.md +35 -0
  42. package/plugins/lisa-copilot/rules/reference/intent-routing.md +8 -3
  43. package/plugins/lisa-copilot/rules/reference/leaf-only-lifecycle.md +1 -0
  44. package/plugins/lisa-copilot/rules/reference/observability-audit.md +122 -0
  45. package/plugins/lisa-copilot/skills/github-build-intake/SKILL.md +25 -0
  46. package/plugins/lisa-copilot/skills/intake/SKILL.md +1 -0
  47. package/plugins/lisa-copilot/skills/jira-build-intake/SKILL.md +19 -0
  48. package/plugins/lisa-copilot/skills/linear-build-intake/SKILL.md +19 -0
  49. package/plugins/lisa-copilot/skills/monitor/SKILL.md +27 -6
  50. package/plugins/lisa-copilot/skills/ticket-triage/SKILL.md +10 -6
  51. package/plugins/lisa-copilot/skills/tracker-build-intake/SKILL.md +21 -0
  52. package/plugins/lisa-copilot/skills/tracker-verify/SKILL.md +1 -0
  53. package/plugins/lisa-copilot/skills/verify/SKILL.md +1 -1
  54. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-cursor/commands/monitor.md +3 -3
  56. package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +35 -0
  57. package/plugins/lisa-cursor/rules/intent-routing-reference.mdc +8 -3
  58. package/plugins/lisa-cursor/rules/leaf-only-lifecycle-reference.mdc +1 -0
  59. package/plugins/lisa-cursor/rules/leaf-only-lifecycle.mdc +2 -0
  60. package/plugins/lisa-cursor/rules/observability-audit-reference.mdc +127 -0
  61. package/plugins/lisa-cursor/rules/observability-audit.mdc +20 -0
  62. package/plugins/lisa-cursor/skills/github-build-intake/SKILL.md +25 -0
  63. package/plugins/lisa-cursor/skills/intake/SKILL.md +1 -0
  64. package/plugins/lisa-cursor/skills/jira-build-intake/SKILL.md +19 -0
  65. package/plugins/lisa-cursor/skills/linear-build-intake/SKILL.md +19 -0
  66. package/plugins/lisa-cursor/skills/monitor/SKILL.md +27 -6
  67. package/plugins/lisa-cursor/skills/ticket-triage/SKILL.md +10 -6
  68. package/plugins/lisa-cursor/skills/tracker-build-intake/SKILL.md +21 -0
  69. package/plugins/lisa-cursor/skills/tracker-verify/SKILL.md +1 -0
  70. package/plugins/lisa-cursor/skills/verify/SKILL.md +1 -1
  71. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  73. package/plugins/lisa-expo-agy/plugin.json +1 -1
  74. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  78. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  79. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  83. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  84. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  88. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  89. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  93. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  94. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  95. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  98. package/plugins/lisa-rails-agy/plugin.json +1 -1
  99. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  100. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  103. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  104. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  105. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  106. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  108. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  109. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  110. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  111. package/plugins/src/base/commands/monitor.md +3 -3
  112. package/plugins/src/base/rules/eager/leaf-only-lifecycle.md +2 -0
  113. package/plugins/src/base/rules/eager/observability-audit.md +15 -0
  114. package/plugins/src/base/rules/reference/config-resolution.md +35 -0
  115. package/plugins/src/base/rules/reference/intent-routing.md +8 -3
  116. package/plugins/src/base/rules/reference/leaf-only-lifecycle.md +1 -0
  117. package/plugins/src/base/rules/reference/observability-audit.md +122 -0
  118. package/plugins/src/base/skills/github-build-intake/SKILL.md +25 -0
  119. package/plugins/src/base/skills/intake/SKILL.md +1 -0
  120. package/plugins/src/base/skills/jira-build-intake/SKILL.md +19 -0
  121. package/plugins/src/base/skills/linear-build-intake/SKILL.md +19 -0
  122. package/plugins/src/base/skills/monitor/SKILL.md +27 -6
  123. package/plugins/src/base/skills/ticket-triage/SKILL.md +10 -6
  124. package/plugins/src/base/skills/tracker-build-intake/SKILL.md +21 -0
  125. package/plugins/src/base/skills/tracker-verify/SKILL.md +1 -0
  126. package/plugins/src/base/skills/verify/SKILL.md +1 -1
package/package.json CHANGED
@@ -91,7 +91,7 @@
91
91
  "ws": ">=8.20.1"
92
92
  },
93
93
  "name": "@codyswann/lisa",
94
- "version": "2.174.0",
94
+ "version": "2.175.0",
95
95
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
96
96
  "main": "dist/index.js",
97
97
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.174.0",
3
+ "version": "2.175.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.174.0",
3
+ "version": "2.175.0",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  ---
2
- description: "Monitor application health across environments. Health endpoints, recent logs, error-rate spikes, performance, optional browser UAT. Routes to the stack-specific ops-specialist."
3
- argument-hint: "[environment]"
2
+ description: "Monitor application health AND audit observability completeness for the current repo, then file build-ready tickets for anomalies and instrumentation gaps. Health/logs/errors/performance via the stack ops-specialist or stack-agnostic base probing; repo-scoped, idempotent, capped; files by default, --dry-run previews."
3
+ argument-hint: "[environment] [--dry-run] [--report-only] [--all-gaps] [max_candidates=<n>]"
4
4
  ---
5
5
 
6
- Use the /lisa:monitor skill to check application health, logs, errors, and performance for the named environment. $ARGUMENTS
6
+ Use the /lisa:monitor skill to check application health, logs, errors, and performance for the named environment, audit observability completeness, and file build-ready tickets for the anomalies and gaps it finds. Use `--dry-run` to preview without filing, `--report-only` for a health/audit summary with no filing, or `--all-gaps` to also file recommended-tier gaps (session replay, product analytics). $ARGUMENTS
@@ -38,4 +38,6 @@ When a leaf reaches the true terminal `done` (the production / final-env value),
38
38
 
39
39
  Intermediate env-keyed states (`status:on-dev`, `On Stg`, etc.) remain open. Idempotent — if already closed, report and continue.
40
40
 
41
+ Duplicate closeout is a narrow terminal exception: build intake may close a claimed item without a PR only when `ticket-triage` returns `DUPLICATE_ALREADY_FIXED` with a canonical item reference and empirical base-branch proof. Close it through provider duplicate semantics, not as completed build work. `BLOCKED`, ambiguous, duplicate-of-open, and other human-owned dispositions are not auto-closed.
42
+
41
43
  Full vendor mechanics + the state machine in prose: [reference/leaf-only-lifecycle.md](../reference/leaf-only-lifecycle.md).
@@ -0,0 +1,15 @@
1
+ # Observability Audit (load-bearing)
2
+
3
+ The **audit + file** arm of `lisa:monitor`. On top of its existing live-signal sweep, `monitor` audits how well the **current repo** is instrumented and files build-ready tickets for what it finds. `monitor` is **manual** (no cron) and **files only** — it never fixes; the `intake` / `tracker-build-intake` cron implements what it files.
4
+
5
+ ## Invariants
6
+
7
+ - **Repo-scoped two ways.** Audit only the rubric dimensions in scope for the repo's detected profile (`frontend` / `backend` / `infra` — a frontend repo never evaluates backend tracing), and stamp every filed ticket `repo:<CURRENT_REPO>` as a single-repo leaf (resolve the repo via the `config-resolution` ladder; cannot resolve → report, do not file).
8
+ - **Two finding types → build-ready leaves.** A live signal over the conservative bar → a `Bug` leaf; an in-scope MISSING instrumentation dimension (gap, e.g. "no DB/query analytics") → a `Task`/`Improvement` leaf. Always via `lisa:tracker-write` with `build_ready: true` (never a vendor write skill directly); never an Epic/container (gate S15).
9
+ - **Conservative by default.** Only high-signal anomalies (over the documented thresholds) and `core` missing dimensions are filed. `--all-gaps` widens gap filing to `recommended` tiers; nothing lowers the anomaly bar.
10
+ - **Idempotent.** Every ticket carries a `<!-- lisa-monitor-finding: <fingerprint> -->` sentinel; search-before-create (including closed tickets) means a re-run never duplicates a live or just-resolved finding.
11
+ - **Capped.** At most `monitor.maxCandidates` tickets per run (default 20), highest-severity first; report filed-vs-dropped (and list the dropped) — never silently truncate.
12
+ - **Gate-passing.** Each ticket is a real authored artifact: three-audience description, Gherkin AC, single-repo, Target Backend Environment, and a Validation Journey with a unique kebab-case `[EVIDENCE: <name>]` marker — so `tracker-validate` (S1–S15) accepts it. A finding that cannot be made into a credible ticket is reported, not filed.
13
+ - **Verify guard.** When `monitor` is the post-deploy step of `lisa:verify` it runs **report-only** — Verify invokes it as `lisa:monitor <env> --report-only`, so it never files there. Filing is a standalone-only action.
14
+
15
+ Full reference (profile detection, the rubric table, anomaly thresholds, ticket templates, idempotency contract, the cap, dry-run/report-only semantics): [reference/observability-audit.md](../reference/observability-audit.md).
@@ -156,6 +156,18 @@ fi
156
156
  "staleAfterHours": 2,
157
157
  "maxCandidates": 100
158
158
  }
159
+ },
160
+
161
+ "monitor": {
162
+ "maxCandidates": 20,
163
+ "gapTiers": "core",
164
+ "backoffHours": 24,
165
+ "thresholds": {
166
+ "sentryMinEvents24h": 10,
167
+ "errorRateSpikeMultiplier": 2,
168
+ "p95LatencyMs": 1000,
169
+ "xrayFaultRatePct": 5
170
+ }
159
171
  }
160
172
  }
161
173
  ```
@@ -360,6 +372,29 @@ documented defaults, so existing projects need no config change.
360
372
  | `intake.repair.staleAfterHours` | no | `2` | How long an in-progress item (build `claimed`, PRD `in_review`) may show no observable activity before repair-intake treats it as stalled and resumes it. `blocked` items are judged on blocker/answer state, not this threshold. Overridable per-run via `stale_after=<dur>` in `$ARGUMENTS` (which always wins). The same value is the default backoff window for loop-prevention notes. |
361
373
  | `intake.repair.maxCandidates` | no | `100` | Upper bound on how many stuck items repair-intake enumerates while searching for the first actionable one. Bounds scan cost. Overridable per-run via `max_candidates=<n>`. |
362
374
 
375
+ ### Monitor audit config (`monitor`)
376
+
377
+ `lisa:monitor`'s audit-and-file arm reads an optional top-level `monitor` block. Every key is
378
+ **optional** — a missing block inherits the documented defaults, so existing projects need no
379
+ config change. The role SEMANTICS (what counts as an anomaly or gap, how findings become tickets)
380
+ are fixed like every other lifecycle behavior; only these thresholds and caps are tunable. Full
381
+ contract: the `observability-audit` rule.
382
+
383
+ | Key | Required | Default | Notes |
384
+ |-----|----------|---------|-------|
385
+ | `monitor.maxCandidates` | no | `20` | Cap on tickets filed per standalone run (`core`/high-severity first). Overridable per-run via `max_candidates=<n>` in `$ARGUMENTS`, which always wins. |
386
+ | `monitor.gapTiers` | no | `core` | Which gap tier files tickets by default: `core` (operationally load-bearing dimensions only) or `all` (also `recommended`). The `--all-gaps` run flag forces `all` for that invocation. |
387
+ | `monitor.backoffHours` | no | `24` | How long after a finding's ticket is closed/resolved to keep suppressing a re-file (the recently-resolved dedup window), so a just-fixed regression isn't re-filed before its signal drains. Distinct from `intake.repair.staleAfterHours` (2h). |
388
+ | `monitor.thresholds.sentryMinEvents24h` | no | `10` | Minimum 24h event count for an unresolved Sentry error to be fileable. |
389
+ | `monitor.thresholds.errorRateSpikeMultiplier` | no | `2` | Error rate must be ≥ this × the prior-window baseline (and above an absolute floor) to file. |
390
+ | `monitor.thresholds.p95LatencyMs` | no | `1000` | p95 latency at/above this (or up ≥ 50% vs prior window) is a fileable regression. |
391
+ | `monitor.thresholds.xrayFaultRatePct` | no | `5` | X-Ray fault traces above this % of traces in the window is a fileable anomaly. |
392
+
393
+ Resolution order matches every other key: `$ARGUMENTS` override → `.lisa.config.local.json` →
394
+ `.lisa.config.json` → built-in default. `monitor` files only within the current repo (type-scoped
395
+ rubric + `repo:<name>` single-repo leaves); it never fixes — the `intake` cron implements what it
396
+ files.
397
+
363
398
  ### Intake assignee filter (`intake.assignee`)
364
399
 
365
400
  The optional intake assignee filter narrows **ready-item selection only**. It never assigns or
@@ -314,11 +314,16 @@ Sequence:
314
314
 
315
315
  ### Monitor
316
316
 
317
- Purpose: Check application health and operational status. Can be invoked standalone or as part of Verify.
317
+ Purpose: Check application health and operational status, **audit observability completeness**, and (standalone only) **file build-ready tickets** for anomalies and instrumentation gaps. Repo-scoped and manual — there is no cron for Monitor. Can be invoked standalone or as the post-deploy step of Verify.
318
318
 
319
319
  Sequence:
320
- 1. `ops-specialist` -- health checks, log inspection, error monitoring, performance analysis
321
- 2. Report findings, escalate if action needed
320
+ 1. **Discover** -- stack-agnostic detection of the repo's observability profile (`frontend` / `backend` / `infra`) and which observability tools are actually wired (read manifests/config; probe only with available credentials). See the `observability-audit` rule.
321
+ 2. **Collect live signals** -- `ops-specialist` (Expo/Rails) or inline base probing (Sentry CLI/REST, CloudWatch, X-Ray, Playwright MCP) for health, logs, errors, performance. An observation is a fileable **anomaly** only when it clears the conservative thresholds in `observability-audit`.
322
+ 3. **Audit completeness** -- score the repo against the `observability-audit` rubric for its profile; each in-scope MISSING dimension is a **gap** finding (`core` always; `recommended` only with `--all-gaps`).
323
+ 4. **Report** -- health/anomaly summary + audit table. Always produced.
324
+ 5. **File (standalone only; SKIP under `--report-only`/`--dry-run` and when invoked from Verify)** -- for each anomaly and gap, dedupe by fingerprint (sentinel + search-before-create incl. closed tickets), then file a single-repo build-ready leaf via `tracker-write` (`build_ready: true`) — a `Bug` for an anomaly, a `Task`/`Improvement` for a gap — stamped `repo:<current>`, capped at `max_candidates` (default 20, `core`/high-severity first; list any dropped). Default files; `--dry-run` previews without filing. `lisa:verify` invokes monitor as `lisa:monitor <env> --report-only`, so the post-deploy check never files. Escalate anything not made into a ticket.
325
+
326
+ The `observability-audit` rule owns the profile detection, rubric, anomaly thresholds, ticket templates (gate-passing), fingerprint/idempotency contract, the cap, and the Verify report-only guard. Monitor **files only** — the `intake` / `tracker-build-intake` cron implements what it files.
322
327
 
323
328
  ## Tracker Entry Point (JIRA, GitHub Issues, or Linear)
324
329
 
@@ -104,6 +104,7 @@ This action is **terminal-only**:
104
104
  - Intermediate env-keyed states such as `status:on-dev`, `status:on-stg`, `On Dev`, or `On Stg` remain open / unresolved / active. They are deployment waypoints, not terminal completion.
105
105
  - A single-environment project whose `done` resolves to one value treats that value as terminal. In this repo, `production: main` means `status:done` / `Done` is terminal.
106
106
  - A multi-environment project treats only the production / final environment's `done` value as terminal unless the project explicitly configures `done` as a single string. Do not close native work items at lower environments.
107
+ - Duplicate closeout is a narrow terminal exception: build intake may close a claimed item without a PR only when `ticket-triage` returns `DUPLICATE_ALREADY_FIXED` with a canonical item reference and empirical proof that the canonical fix is present on the relevant base branch. That closeout uses the provider's duplicate semantics (`Duplicate` resolution, duplicate/canceled state, or GitHub not-planned close with a duplicate link/comment), not the normal "completed build" reason. `BLOCKED`, ambiguous, duplicate-of-open, and other human-owned dispositions are not auto-closed.
107
108
  - The native finalization must be idempotent. If the item is already closed / completed / resolved, report that and continue.
108
109
  - If a provider exposes no native close / archive operation, or a project has not configured the native Done state, record a capability-aware no-op or setup error according to the vendor skill. Do not invent a state name.
109
110
 
@@ -0,0 +1,122 @@
1
+ # Observability Audit (load-bearing)
2
+
3
+ This rule is the single source of truth for the **audit + file** arm of the `lisa:monitor` skill. The `monitor` skill collects live signals (errors, logs, performance, health) via the stack `ops-specialist` exactly as before; this rule adds two things on top, both **repo-scoped**:
4
+
5
+ 1. **Audit** — score the repo against an observability-completeness rubric for its detected type, so a frontend repo is never dinged for missing backend tracing.
6
+ 2. **File** — turn both *anomalies* (a real bad live signal) and *gaps* (an in-scope observability dimension that isn't wired) into **build-ready leaf tickets** so the existing `intake` / `tracker-build-intake` cron picks them up and fixes them. `monitor` files only; it never fixes.
7
+
8
+ `monitor` is **manual** — there is no cron for it. It is run by a human against a named environment.
9
+
10
+ ## Scope discipline (why this is repo-scoped)
11
+
12
+ `monitor` runs **inside one repo** and audits/files **only for that repo**. Two mechanisms enforce it:
13
+
14
+ - **Type-scoped rubric.** Only the dimensions in-scope for the detected repo type are audited (table below). A frontend-only repo never evaluates X-Ray tracing or DB-query analytics; a backend repo never evaluates Web Vitals or session replay.
15
+ - **`repo:<name>` stamping.** Every filed ticket is a single-repo leaf stamped `repo:<CURRENT_REPO>` (per `repo-scope-split`). Resolve `CURRENT_REPO` with the standard ladder from `config-resolution` ("Current-repo resolution"): config `repo` → `github.repo` → `basename` of the git remote. If it cannot be resolved, **report and skip filing** rather than filing un-scoped tickets.
16
+
17
+ ## Repo-type detection (stack-agnostic)
18
+
19
+ `monitor` is a base skill that ships to every project, so detection must not assume a stack plugin is present. Detect the repo's observability **profile** by reading manifests and key files — never by assuming. Classify into one or more of `frontend`, `backend`, `infra`. A repo may be more than one (a Rails or Next.js app is `frontend`+`backend` = "fullstack"); audit the union of the matched profiles' in-scope dimensions.
20
+
21
+ | Profile | Positive signals (any) |
22
+ |---|---|
23
+ | `frontend` | `package.json` deps incl. `expo`, `react-native`, `next`, `react`, `vue`, `@angular/core`, `svelte`, `vite`; an `app.json` / `app/` route dir / `public/index.html`; a web/mobile build script |
24
+ | `backend` | `package.json` deps incl. `@nestjs/core`, `express`, `fastify`, `koa`, `@apollo/server`; a `serverless.yml` / Lambda handlers; a `Gemfile` with `rails`; a `Procfile`; an API/server entrypoint |
25
+ | `infra` | `aws-cdk-lib` + `cdk.json`; Terraform `*.tf`; Pulumi; a `serverless.yml` with no application code; a deploy-only repo |
26
+
27
+ When the Lisa stack overlay is present its mapping is a strong prior (`expo`→frontend, `nestjs`→backend, `cdk`→infra, `rails`→frontend+backend, `typescript`→infer from the signals above), but always confirm against the actual files — the overlay is a hint, the files are truth.
28
+
29
+ ## The observability-completeness rubric
30
+
31
+ Each dimension is **in scope** (`●`) or **out of scope** (`–`) per profile, and carries a **tier**: `core` (a blind spot here is a real operational risk → file when missing) or `recommended` (valuable but optional → file only when `--all-gaps` is passed; otherwise report as a WARN row, do not ticket).
32
+
33
+ | Dimension | Tier | FE | BE | Infra | Detect via (presence ⇒ wired) |
34
+ |---|---|:--:|:--:|:--:|---|
35
+ | Error monitoring | core | ● | ● | – | `@sentry/*`, `.sentryclirc`, Sentry DSN in `.env*`; Bugsnag/Rollbar deps |
36
+ | Structured logging | core | ● | ● | ● | `pino`/`winston`/`bunyan`; Rails `lograge`; CloudWatch log groups exist |
37
+ | Distributed tracing | core | – | ● | ● | `aws-xray-sdk*`, OpenTelemetry deps/gems; X-Ray traces present |
38
+ | Metrics & alarms | core | – | ● | ● | CloudWatch custom metrics + `describe-alarms` returns alarms; Datadog/Prometheus |
39
+ | Health checks | core | ● | ● | – | a `/health`/`/up` endpoint; NestJS Terminus; uptime check config |
40
+ | RUM / Web Vitals | recommended | ● | – | – | Sentry performance/browser tracing, `web-vitals`, Firebase Performance |
41
+ | Product analytics | recommended | ● | – | – | `posthog-js`/`posthog-node`, Firebase Analytics, Amplitude, Segment |
42
+ | Session replay / frustration | recommended | ● | – | – | Jam, PostHog session replay, LogRocket, Sentry replay |
43
+ | DB / query analytics | recommended | – | ● | ● | slow-query logging, RDS/Aurora Performance Insights, query-timing middleware |
44
+ | Synthetic / smoke UAT | recommended | ● | ● | – | Playwright/Cypress e2e, k6/Artillery load scripts, synthetic canaries |
45
+
46
+ **Detection is read-then-probe, never assume.** For each in-scope dimension: read `package.json` deps + scripts (and `Gemfile`, `serverless.yml`, `docker-compose.yml`, `config/initializers/*`, `.env*`, `.sentryclirc`, `cdk.json`), then — only when credentials/tooling are already available — probe the live source (`command -v sentry-cli`, `aws sts get-caller-identity` before any `aws` call). Mirror the `ops-specialist` "Project Discovery" table where the stack overlay exists. **Absence of every signal for an in-scope dimension is the gap finding.** A dimension that is present but unreachable this run (e.g. PostHog wired but no API key available) is reported `PRESENT (unverified)`, **not** a gap.
47
+
48
+ ### Tools Lisa does not wire
49
+
50
+ PostHog, Jam, and Firebase have **no Lisa MCP**. Detect them statically (deps/config) and pull their data only where a first-party API + credentials are already present in the environment (e.g. the PostHog API with a project key). Never invent credentials, and never treat "Lisa has no MCP for it" as "the repo lacks it" — judge by the repo's own deps/config.
51
+
52
+ ## Live-signal anomalies (conservative thresholds)
53
+
54
+ Collect live signals via the stack `ops-specialist` when present (Expo/Rails), else via inline base probing (Sentry CLI/REST, `aws logs`, `aws cloudwatch`, `aws xray`, the Playwright MCP for client-side console/network). An observation becomes a **fileable anomaly** only when it clears the conservative bar — high-signal only, to keep the Ready queue clean. Defaults below (the keys live under `monitor.thresholds.*` in `config-resolution`; per-run nothing overrides them — tune in config):
55
+
56
+ | Signal | Fileable when |
57
+ |---|---|
58
+ | Sentry issue | `is:unresolved` **and** `level:error|fatal` **and** events in last 24h ≥ `sentryMinEvents24h` (default 10). Skip resolved/ignored/muted and below-floor noise. |
59
+ | Error-rate spike | error rate ≥ `errorRateSpikeMultiplier`× (default 2×) the prior-window baseline **and** above an absolute floor (not 1-of-2 requests). |
60
+ | Latency regression | p95 ≥ `p95LatencyMs` (default 1000ms) sustained, **or** p95 up ≥ 50% vs the prior window. |
61
+ | CloudWatch alarm | any alarm in `ALARM` state. |
62
+ | X-Ray fault rate | fault traces > `xrayFaultRatePct` (default 5%) of traces in the window. |
63
+ | Client-side (Playwright) | repeated console `error` or a 5xx network response on a smoke flow. |
64
+
65
+ Everything below the bar is reported (so the human sees it) but **not** ticketed. `--all-gaps` does not lower anomaly thresholds — it only widens *gap* tiers.
66
+
67
+ ## Two finding types → build-ready leaf tickets
68
+
69
+ Both finding types are filed through the vendor-neutral `lisa:tracker-write` shim with `build_ready: true` (never a vendor write skill directly — that is what keeps the destination switchable). Both are **leaf** work units (`Bug` for anomalies, `Task`/`Improvement` for gaps) — never an Epic or container (gate S15). Each must pass the `tracker-validate` gates S1–S15, so each ticket is a real authored artifact, not a template dump.
70
+
71
+ ### Required fields (so the gates pass)
72
+
73
+ - **Three-audience description** (S3): coding-assistant (technical: stack trace / Sentry link / occurrence count / the missing dimension and how to wire it), developer (where it surfaces, suspected cause/affected files, the fix skill to reach for), stakeholder (user/SLO impact).
74
+ - **Gherkin acceptance criteria** (S4).
75
+ - **Single repo** (S10): stamped `repo:<CURRENT_REPO>`; scope the description to this repo only.
76
+ - **Target Backend Environment** (S8) + **Validation Journey** carrying at least one unique kebab-case `[EVIDENCE: <name>]` marker (S11/S14) — both anomaly fixes and gap wiring are runtime changes, so both need an env and a journey that proves the fix. The bracketed `[EVIDENCE: <name>]` form is what the validator scans for; a bare `EVIDENCE:` line fails S14. Prefer two markers (a success and an error/edge case).
77
+ - **Relationship search before write** (S13) — doubles as the dedup guard (next section).
78
+ - **No parent/Epic required.** These are build-ready standalone leaves; S7's parent requirement is waived for a `build_ready: true` leaf (the leaf carve-out in `leaf-only-lifecycle` / `tracker-validate`). Do not fabricate an Epic parent.
79
+ - A **priority** ordered by tier and severity: `core` gap / high-event anomaly → higher; `recommended` gap → lower.
80
+
81
+ ### Anomaly ticket (type: Bug)
82
+
83
+ Title: `[observability] <signal summary> (<source>)` — e.g. `[observability] Unhandled TypeError in CheckoutScreen (Sentry)`. The coding-assistant section carries the Sentry/CloudWatch/X-Ray link, fingerprint, first/last seen, and occurrence count. AC: *Given* the repro context, *When* the action runs, *Then* the source reports zero new events for the fingerprint. The fix-skill hint points at `root-cause-analysis` / `reproduce-bug`.
84
+
85
+ ### Gap ticket (type: Task or Improvement)
86
+
87
+ Title: `[observability-gap] Add <dimension> (<profile>)` — e.g. `[observability-gap] Add distributed tracing (backend)`. The description states which dimension is missing, **why the audit needed it** (the blind spot it leaves), and how to wire it — chaining the existing fix skills where they apply (`parity-sentry-sdk-setup` for error monitoring; the nestjs `typeorm-patterns` skill's `observability-patterns` reference for X-Ray/CloudWatch DB-query tracing; OTel→X-Ray for tracing). AC: *Given* the app runs in `<env>`, *When* the relevant signal occurs, *Then* the newly-wired dimension captures it (dashboard/endpoint/trace shows data).
88
+
89
+ ## Idempotency (do not re-file the same finding)
90
+
91
+ A manual re-run next week must not duplicate last week's tickets. Mirror the `repair-intake` marker discipline:
92
+
93
+ 1. **Stable fingerprint per finding.**
94
+ - Anomaly: `sha1("<source>:<stable-signature>:<CURRENT_REPO>")` (12 hex chars). `stable-signature` is the Sentry issue short-id (or culprit), the CloudWatch alarm name, or `errorType@location` — something that survives between runs, **never** the human title or occurrence count.
95
+ - Gap: the literal `gap:<dimension>:<CURRENT_REPO>` (a dimension is missing-or-not; no hash needed).
96
+ 2. **Sentinel in the body.** Embed `<!-- lisa-monitor-finding: <fingerprint> -->` in every filed ticket.
97
+ 3. **Search before create** (this IS gate S13). Before filing, search the tracker for the fingerprint string. The search **MUST include closed/resolved tickets** (`gh issue list --search <fp> --state all` for GitHub; JQL with no status filter for JIRA; all states for Linear) — otherwise a just-closed match is invisible and the backoff below can't fire. If an **open** ticket carries the fingerprint → skip (optionally link). If a ticket carrying it was closed **within the recently-resolved backoff window** (`monitor.backoffHours`, default **24h** — matching the 24h Sentry event window; this is **not** the 2h `intake.repair.staleAfterHours`) → skip, to avoid re-filing a just-fixed regression before its signal has drained. Only file when no live or recently-resolved match exists.
98
+
99
+ ## The cap
100
+
101
+ File **at most `max_candidates` tickets per run** (default **20**; config `monitor.maxCandidates`; per-run `max_candidates=<n>` always wins). Rank candidates before applying the cap — `core` gaps and highest-occurrence anomalies first — so the cap drops the least-important findings, never the most. When the cap truncates, **list every dropped finding** in the report (title + fingerprint, not just a count) — silent truncation reads as "all clear" when it isn't, and a high-signal anomaly that fell just below the cap must still be visible to the human.
102
+
103
+ ## Filing modes and the Verify guard
104
+
105
+ - **Default (standalone `monitor <env>`): files.** A plain manual run audits, reports, dedupes, and files up to the cap.
106
+ - **`--dry-run`:** does everything except call `tracker-write` — prints exactly which tickets *would* be filed (title, type, fingerprint, dedup verdict) so the human can preview.
107
+ - **`--report-only`:** produces the health/audit summary only — no filing and no would-file analysis. This is the mode Verify uses (below).
108
+ - **Invoked as `lisa:verify`'s post-deploy step: never files — enforced mechanically, not inferred.** `lisa:verify` step 6 invokes monitor as `lisa:monitor <env> --report-only`; monitor must never file when `--report-only` (or `--dry-run`) is set. This is load-bearing **today** — Verify already routes its remote verification through the `monitor` skill, so without the passed flag the new file-by-default behavior would create tickets during every verify run. As a belt-and-suspenders default, if monitor is invoked via the Skill tool from within a Verify flow and no filing flag was passed, treat it as `--report-only` rather than filing.
109
+
110
+ ## Output
111
+
112
+ A single report: the health/anomaly summary (existing monitor behavior) + an audit table (each in-scope dimension with `OK` / `WARN` / `MISSING` / `PRESENT (unverified)`) + a filing summary (tickets filed with their refs and fingerprints, tickets skipped as duplicates, and the dropped count if the cap truncated). In `--dry-run`, the filing summary lists would-file tickets instead.
113
+
114
+ ## Rules
115
+
116
+ - File only — never fix. The `intake` / `tracker-build-intake` cron implements what `monitor` files.
117
+ - Always through `tracker-write` with `build_ready: true`; never a vendor write skill directly.
118
+ - Conservative by default — high-signal anomalies and `core` missing dimensions only. `--all-gaps` widens gap tiers; nothing lowers the anomaly bar.
119
+ - Repo-scoped always — type-scoped rubric + `repo:<CURRENT_REPO>` single-repo leaves. Cannot resolve the repo → report, do not file.
120
+ - Idempotent always — fingerprint sentinel + search-before-create; a re-run never duplicates a live or just-resolved finding.
121
+ - Never file without a real, gate-passing description and Validation Journey. A finding that cannot be made into a credible ticket is reported, not filed.
122
+ - Read config with `jq` (local-overrides-global) per `config-resolution`; never hand-parse JSON. Never run an `aws`/`sentry-cli` probe without first confirming credentials.
@@ -270,9 +270,30 @@ Wait for `lisa:github-agent` to return. Capture its outcome:
270
270
 
271
271
  - **Success** — the build flow completed and a PR exists; evidence posted. The PR may already be **merged** or still **open** (auto-merge enabled, awaiting checks/merge). "Success" means the build work is sound — it does **not** assert the change reached an environment. The env transition in 3d gates on the PR actually being merged; an open PR does not advance the issue to a `done` env status.
272
272
  - **Blocked by github-verify pre-flight gate** — `lisa:github-agent` itself relabels the issue to `status:blocked` (or removes `$CLAIMED` and reassigns to the original author). This is correct and expected — let it stand. Record and move on.
273
+ - **Duplicate already fixed** — `lisa:github-agent` / `lisa:ticket-triage` returned `DUPLICATE_ALREADY_FIXED` with a canonical issue reference and empirical base-branch evidence. Post the triage finding, ensure the native `duplicates <canonical>` relationship exists when GitHub exposes it (otherwise leave an explicit cross-reference comment/body link), remove `$CLAIMED`, add the terminal `$DONE` label, close the issue with `gh issue close --reason "not planned"`, and do not open a PR. If the canonical fix is merged but not yet on the production branch, the close comment must say the production error can recur until the canonical issue promotes and that recurrence is tracked by the canonical issue; do not reopen this duplicate for that recurrence.
273
274
  - **Blocked by ticket-triage ambiguities** — `lisa:github-agent` posts findings and stops. The issue stays in `$CLAIMED`. Surface to human; do not auto-relabel. Record under "Errors".
274
275
  - **Errored** — exception, missing config, etc. Leave the issue in `$CLAIMED` for human investigation. Record under "Errors".
275
276
 
277
+ #### 3c.1 Close duplicate already fixed
278
+
279
+ Run this only when the returned triage verdict is exactly `DUPLICATE_ALREADY_FIXED`.
280
+
281
+ 1. Verify the structured result includes a canonical issue reference, the canonical PR/commit, and empirical evidence that the canonical fix is present on the base branch. If any piece is missing, treat the outcome as Held instead of closing.
282
+ 2. Post or preserve the triage-finding comment that explains why this issue is a duplicate and names the canonical issue.
283
+ 3. Ensure a native `duplicates <canonical>` link exists when GitHub exposes issue relationships; if this installation cannot create that relationship, leave an explicit issue cross-reference comment/body link and record the limitation in the summary.
284
+ 4. Resolve terminal `$DONE` exactly as in Phase 3d. For a single-env repo, `$DONE` is terminal; for env-keyed config, only the production/final value is terminal.
285
+ 5. Replace `$CLAIMED` with `$DONE`, then close the issue as duplicate/not-planned:
286
+
287
+ ```bash
288
+ gh issue edit <number> --repo <org>/<repo> --remove-label "$CLAIMED" --add-label "$DONE"
289
+ gh issue comment <number> --repo <org>/<repo> --body "[claude-build-intake] Closed as duplicate of <canonical>. Canonical fix: <PR-or-commit>. Evidence: <base-branch-proof>."
290
+ gh issue close <number> --repo <org>/<repo> --reason "not planned"
291
+ ```
292
+
293
+ If the canonical fix is merged but not yet present on the production branch, append the production-promotion caveat to the close comment: the production error can recur until the canonical issue promotes, and recurrence is tracked by the canonical issue rather than by reopening this duplicate.
294
+
295
+ This path is distinct from `BLOCKED`: ambiguity, open blockers, and duplicate-of-open findings remain held for human action and must not be auto-closed.
296
+
276
297
  #### 3d. Transition to $DONE (only after the PR is merged)
277
298
 
278
299
  A `done` env state (`status:on-dev`, `status:on-stg`, or the terminal value) asserts that the code has actually reached that environment. Never set it for a PR that is merely open: auto-merge can be blocked indefinitely (a required rebase / `BEHIND` branch, failing checks, an unaddressed review), and the change may never land. Relabeling an issue `status:on-stg` on an open PR makes it *claim* a deploy that never happened. Transition only after confirming the PR merged.
@@ -336,6 +357,8 @@ Issues processed: <n>
336
357
  - <org>/<repo>#<number> <title> — build-ready on a parent/container; moved $READY → $CLAIMED without invoking lisa:github-agent; lifecycle-repair comment posted
337
358
  - Skipped (active blockers): <n>
338
359
  - <org>/<repo>#<number> <title> — waiting on <blocker refs>
360
+ - Duplicate already fixed (closed as duplicate): <n>
361
+ - <org>/<repo>#<number> <title> — duplicate of <canonical>; no PR opened
339
362
  - Blocked (pre-flight verify failed): <n>
340
363
  - <org>/<repo>#<number> <title> — see issue comments
341
364
  - Held (triage found ambiguities): <n>
@@ -352,6 +375,7 @@ Total PRs opened: <n>
352
375
  - **Dependency hold runs before leaf claim**: explicit `Blocked by:` relationships are resolved after container repair is ruled out but before `$READY → $CLAIMED`; active blockers leave the leaf candidate in `$READY` and are reported as skipped, not blocked.
353
376
  - **Claim-first ordering**: `$CLAIMED` set BEFORE `lisa:github-agent` invocation for leaves; containers are also moved to `$CLAIMED` to leave the ready pickup queue, but are not dispatched.
354
377
  - **No writes outside the lifecycle**: this skill only relabels `$READY → $CLAIMED` and `$CLAIMED → $DONE`. For containers, `$READY → $CLAIMED` is a lifecycle repair, not a direct build claim. Every other label change is owned by `lisa:github-agent`.
378
+ - **Duplicate terminal exception**: `DUPLICATE_ALREADY_FIXED` is the only triage outcome that may close a claimed item without a PR from this cycle. It must include a canonical issue reference and empirical base-branch evidence, and it closes as duplicate/not-planned rather than as completed build work.
355
379
  - **Terminal native closure**: after `$CLAIMED → $DONE`, close the GitHub issue only when `$DONE` is the true terminal done value per `leaf-only-lifecycle`; intermediate env labels stay open.
356
380
  - **One item per cycle**: per-issue exceptions are caught and recorded, then the cycle exits. The scheduler owns retrying or moving on to the next ready item.
357
381
  - **Single cycle per repo**: do not run two `lisa:github-build-intake` cycles in parallel against the same repo — concurrent claims could race. The scheduling layer is responsible for serialization.
@@ -378,6 +402,7 @@ If the repo has not adopted the `status:*` label namespace, this skill cannot ru
378
402
  - Never bypass `lisa:github-agent` to do build work directly. `lisa:github-agent` owns the per-issue lifecycle.
379
403
  - Never auto-transition past `$DONE`. Downstream labels (terminal `status:done`, etc.) are owned by QA / PM / merge automation.
380
404
  - Never close a GitHub issue at intermediate env states (`status:on-dev`, `status:on-stg`, or configured equivalents). Native close happens only at the terminal `done` value.
405
+ - Never auto-close a `BLOCKED`, ambiguous, or duplicate-of-open issue. Auto-close is allowed only for `DUPLICATE_ALREADY_FIXED`.
381
406
  - If the issue has no Validation Journey or no sign-in credentials, `lisa:github-agent`'s pre-flight verify will catch it — **don't try to fix the issue from here**.
382
407
  - On any unexpected response from `lisa:github-agent` (status it doesn't claim, missing PR URL on success), record as Error and surface — never assume.
383
408
  - Never pick an arbitrary env for `$DONE` resolution. If `done` is a map and env is ambiguous, fail loudly.
@@ -105,6 +105,7 @@ The single-item skills (`lisa:plan`, `lisa:implement`) and the per-vendor batch
105
105
  - GitHub PRDs → `lisa:github-prd-intake` handles per-item: claim (relabel issue to `prd-in-review`), dry-run validate, branch to `prd-blocked` or `prd-ticketed` (with clarifying-question comments posted directly on the PRD issue), coverage audit
106
106
  - JIRA tickets → `lisa:jira-build-intake` handles per-item: claim, dispatch to `lisa:jira-agent`, transition to On Dev on success
107
107
  - GitHub build issues (when `tracker = github`) → `lisa:tracker-build-intake` → `lisa:github-build-intake` handles per-item: optional ready-queue assignee filtering, claim (relabel to `status:in-progress`), dispatch to `lisa:github-agent`, relabel to `status:on-dev` on success
108
+ - Duplicate-already-fixed build tickets are the one build-intake closeout exception: when `ticket-triage` returns `DUPLICATE_ALREADY_FIXED` with a canonical item and empirical base-branch evidence, the vendor build-intake skill closes the claimed ticket as a duplicate without opening a PR. This does not apply to `BLOCKED`, ambiguous, duplicate-of-open, or otherwise human-owned terminal dispositions.
108
109
  - **Closing the PRD loop:** beyond claiming one Ready PRD, every PRD scanner also runs the closure rollup (`ticketed → shipped`, Phase 3f) and **dispatches `lisa:verify-prd` for one shipped PRD** (Phase 3g) each cycle — so a shipped PRD does not sit unverified. On pass the PRD goes `shipped → verified`; on fail it is re-opened `shipped → ticketed` with **build-ready fix tickets** that auto-build and trigger a re-verify (never `blocked`). The scanner only dispatches; `lisa:verify-prd` owns the transition (per the `prd-lifecycle-rollup` rule's "Closing the loop" section), and the self-healing loop continues until the PRD verifies.
109
110
  5. **Stop after one item** — a claimed Ready item, a safe-blocked container, or a per-item error ends the *ready-claim* portion of the cycle. The per-vendor PRD scanner still runs its rollup and one verify-prd dispatch. Remaining Ready items stay untouched for later scheduler invocations.
110
111
  6. **Summary report** — the single processed/skipped/error item, total processed, total errors. Before returning, record intake usage on the persisted cycle-summary artifact via `lisa:usage-accounting` so the summary carries a direct `intake` entry in the canonical `## Lisa Usage` section. If the claimed / skipped work item's parent-child graph is already known, prefer `record_and_rollup` so ancestor totals refresh in the same cycle; otherwise still write the direct entry, and if runtime usage is unavailable, use `source: unavailable` with nullable token/cost fields instead of skipping the row.
@@ -196,9 +196,25 @@ Invoke the `lisa:jira-agent` (existing per-ticket lifecycle agent) with the tick
196
196
  Wait for `lisa:jira-agent` to return. Capture its outcome:
197
197
  - **Success** — the build flow completed and a PR exists; evidence posted. The PR may already be **merged** or still **open** (auto-merge enabled, awaiting checks/merge). "Success" means the build work is sound — it does **not** assert the change reached an environment. The env transition in 3d gates on the PR actually being merged; an open PR does not advance the ticket to a `done` env status.
198
198
  - **Blocked by jira-verify pre-flight gate** — `lisa:jira-agent` itself transitions the ticket to `Blocked` and reassigns to Reporter. This is correct and expected — let it stand. Record the outcome and move on.
199
+ - **Duplicate already fixed** — `lisa:jira-agent` / `lisa:ticket-triage` returned `DUPLICATE_ALREADY_FIXED` with a canonical ticket reference and empirical base-branch evidence. Post the triage finding, ensure the native `duplicates <canonical>` link exists, transition to the terminal `$DONE` status with resolution `Duplicate`, and do not open a PR. If the canonical fix is merged but not yet on the production branch, the close comment must say the production error can recur until the canonical ticket promotes and that recurrence is tracked by the canonical ticket; do not reopen this duplicate for that recurrence.
199
200
  - **Blocked by ticket-triage ambiguities** — `lisa:jira-agent` posts findings and stops. The ticket stays in `$CLAIMED`. Surface to human; do not auto-transition. Record under "Errors" with reason `"Triage found ambiguities — see comments on <ticket-key>"`.
200
201
  - **Errored** — exception, missing config, etc. Leave the ticket in `$CLAIMED` for human investigation. Record under "Errors" with the exception summary.
201
202
 
203
+ #### 3c.1 Close duplicate already fixed
204
+
205
+ Run this only when the returned triage verdict is exactly `DUPLICATE_ALREADY_FIXED`.
206
+
207
+ 1. Verify the structured result includes a canonical ticket reference, the canonical PR/commit, and empirical evidence that the canonical fix is present on the base branch. If any piece is missing, treat the outcome as Held instead of closing.
208
+ 2. Post or preserve the triage-finding comment that explains why this ticket is a duplicate and names the canonical ticket.
209
+ 3. Ensure the native `duplicates <canonical>` link exists through `lisa:atlassian-access`.
210
+ 4. Resolve the terminal `$DONE` value exactly as in Phase 3d. For env-keyed workflows, duplicate closeout uses the production/final done status, not an intermediate `On Dev`/`On Stg` waypoint.
211
+ 5. Transition to `$DONE` with JIRA resolution `Duplicate`. If `acli` cannot set resolution on transition, use the Atlassian REST transition-with-fields path exposed by `lisa:atlassian-access`, or a documented follow-up edit that sets the resolution immediately after transition. Do not report success with an empty resolution when the workflow requires one.
212
+ 6. Post a close comment naming the canonical ticket, PR/commit, and base-branch evidence.
213
+
214
+ If the canonical fix is merged but not yet present on the production branch, append the production-promotion caveat to the close comment: the production error can recur until the canonical ticket promotes, and recurrence is tracked by the canonical ticket rather than by reopening this duplicate.
215
+
216
+ This path is distinct from `BLOCKED`: ambiguity, open blockers, and duplicate-of-open findings remain held for human action and must not be auto-closed.
217
+
202
218
  #### 3d. Transition to $DONE (only after the PR is merged)
203
219
 
204
220
  A `done` env status (`On Dev`, `On Stg`, or the terminal value) asserts that the code has actually reached that environment. Never set it for a PR that is merely open: auto-merge can be blocked indefinitely (a required rebase / `BEHIND` branch, failing checks, an unaddressed review), and the change may never land. Setting `On Stg` on an open PR makes a ticket *claim* a deploy that never happened. Transition only after confirming the PR merged.
@@ -249,6 +265,8 @@ Tickets processed: <n>
249
265
  - <ticket-key> <summary> → PR <URL> (mergeStateStatus: <state>)
250
266
  - Skipped (container — leaf-only-lifecycle): <n>
251
267
  - <ticket-key> <summary> — build-ready on a parent with open child work; lifecycle-repair comment posted
268
+ - Duplicate already fixed (closed as Duplicate): <n>
269
+ - <ticket-key> <summary> — duplicate of <canonical>; no PR opened
252
270
  - Blocked (pre-flight verify failed): <n>
253
271
  - <ticket-key> <summary> — see ticket comments
254
272
  - Held (triage found ambiguities): <n>
@@ -264,6 +282,7 @@ Total PRs opened: <n>
264
282
  - **Leaf-only claim gate runs first**: Phase 3a classifies each candidate before any claim; a container with open child work (or a childless Epic) is skipped/safe-blocked, never claimed (the `leaf-only-lifecycle` rule's claim-time arm). The safe-block comment is idempotent — a re-entrant cycle does not re-post it.
265
283
  - **Claim-first ordering**: `$CLAIMED` set BEFORE `lisa:jira-agent` invocation — no double-pickup.
266
284
  - **No writes outside the lifecycle**: this skill only transitions `$READY → $CLAIMED` and `$CLAIMED → $DONE`, then verifies terminal native resolution when `$DONE` is the true terminal state per `leaf-only-lifecycle`. Every other status change is owned by `lisa:jira-agent` (which suggests transitions but only auto-transitions on the verify-FAIL path).
285
+ - **Duplicate terminal exception**: `DUPLICATE_ALREADY_FIXED` is the only triage outcome that may close a claimed ticket without a PR from this cycle. It must include a canonical ticket reference and empirical base-branch evidence, and it resolves as Duplicate rather than as completed build work.
267
286
  - **Terminal native closure**: for terminal `$DONE`, the resulting JIRA issue must be in a resolved / closed state (`statusCategory = Done` and resolution set when required). Intermediate env statuses stay unresolved / open.
268
287
  - **One item per cycle**: per-ticket exceptions are caught and recorded, then the cycle exits. The scheduler owns retrying or moving on to the next ready item.
269
288
  - **Single cycle per query**: do not run two `lisa:jira-build-intake` cycles concurrently against overlapping queries — concurrent claims could race. The scheduling layer (when added) is responsible for serialization.
@@ -207,9 +207,25 @@ Invoke `lisa:linear-agent` (per-Issue lifecycle agent) with the Issue identifier
207
207
  Wait for the agent to return. Capture its outcome:
208
208
  - **Success** — the build flow completed and a PR exists; evidence posted. The PR may already be **merged** or still **open** (auto-merge enabled, awaiting checks/merge). "Success" means the build work is sound — it does **not** assert the change reached an environment. The env transition in 3d gates on the PR actually being merged; an open PR does not advance the Issue to a `done` env status.
209
209
  - **Blocked by linear-verify pre-flight gate** — `lisa:linear-agent` itself relabels to `status:blocked` and assigns to creator. Let it stand. Record and move on.
210
+ - **Duplicate already fixed** — `lisa:linear-agent` / `lisa:ticket-triage` returned `DUPLICATE_ALREADY_FIXED` with a canonical Issue reference and empirical base-branch evidence. Post the triage finding, ensure the native `duplicates <canonical>` relationship exists when Linear exposes it (otherwise leave an explicit relation/comment reference), apply the terminal `$DONE` label, move the native Issue to the configured canceled-as-duplicate or completed terminal state, and do not open a PR. If the canonical fix is merged but not yet on the production branch, the close comment must say the production error can recur until the canonical Issue promotes and that recurrence is tracked by the canonical Issue; do not reopen this duplicate for that recurrence.
210
211
  - **Blocked by ticket-triage ambiguities** — agent posts findings and stops. The Issue stays at `$CLAIMED`. Surface to human; do not auto-transition. Record under "Errors".
211
212
  - **Errored** — exception, missing config, etc. Leave at `$CLAIMED`. Record with exception summary.
212
213
 
214
+ #### 3c.1 Close duplicate already fixed
215
+
216
+ Run this only when the returned triage verdict is exactly `DUPLICATE_ALREADY_FIXED`.
217
+
218
+ 1. Verify the structured result includes a canonical Issue reference, the canonical PR/commit, and empirical evidence that the canonical fix is present on the base branch. If any piece is missing, treat the outcome as Held instead of closing.
219
+ 2. Post or preserve the triage-finding comment that explains why this Issue is a duplicate and names the canonical Issue.
220
+ 3. Ensure a native `duplicates <canonical>` relationship exists when Linear exposes one; if this workspace cannot create that relationship, leave an explicit relation/comment reference and record the limitation in the summary.
221
+ 4. Resolve the terminal `$DONE` value exactly as in Phase 3d. For env-keyed workflows, duplicate closeout uses the production/final done label, not an intermediate `status:on-dev`/`status:on-stg` waypoint.
222
+ 5. Update labels by removing `$CLAIMED` and adding terminal `$DONE`, then move the native Linear state to the configured canceled-as-duplicate state. If no duplicate/canceled state is configured, use the configured terminal completed state only when that is the project's duplicate-close convention; otherwise record setup as an Error rather than inventing a state.
223
+ 6. Post a close comment naming the canonical Issue, PR/commit, and base-branch evidence.
224
+
225
+ If the canonical fix is merged but not yet present on the production branch, append the production-promotion caveat to the close comment: the production error can recur until the canonical Issue promotes, and recurrence is tracked by the canonical Issue rather than by reopening this duplicate.
226
+
227
+ This path is distinct from `BLOCKED`: ambiguity, open blockers, and duplicate-of-open findings remain held for human action and must not be auto-closed.
228
+
213
229
  #### 3d. Relabel to $DONE (only after the PR is merged)
214
230
 
215
231
  A `done` env state (`status:on-dev`, `status:on-stg`, or the terminal value) asserts that the code has actually reached that environment. Never set it for a PR that is merely open: auto-merge can be blocked indefinitely (a required rebase / `BEHIND` branch, failing checks, an unaddressed review), and the change may never land. Relabeling an Issue `status:on-stg` on an open PR makes it *claim* a deploy that never happened. Transition only after confirming the PR merged.
@@ -260,6 +276,8 @@ Issues processed: <n>
260
276
  - <ID> <title> → PR <URL> (mergeStateStatus: <state>)
261
277
  - Skipped (container — leaf-only-lifecycle): <n>
262
278
  - <ID> <title> — build-ready on a parent with open child work; lifecycle-repair comment posted
279
+ - Duplicate already fixed (closed as duplicate): <n>
280
+ - <ID> <title> — duplicate of <canonical>; no PR opened
263
281
  - status:blocked (pre-flight verify failed): <n>
264
282
  - <ID> <title> — see Issue comments
265
283
  - Held (triage found ambiguities): <n>
@@ -275,6 +293,7 @@ Total PRs opened: <n>
275
293
  - **Leaf-only claim gate runs first**: Phase 3a classifies each candidate before any claim; a container with open child work (or a childless Epic) is skipped/safe-blocked, never claimed (the `leaf-only-lifecycle` rule's claim-time arm). The safe-block comment is idempotent — a re-entrant cycle does not re-post it.
276
294
  - **Claim-first ordering**: `$CLAIMED` set BEFORE agent invocation — no double-pickup.
277
295
  - **No writes outside the lifecycle**: this skill only adds/removes `$READY`, `$CLAIMED`, `$DONE`, plus terminal-only native state completion required by `leaf-only-lifecycle`. Every other label change (and non-terminal native state change) is owned by the agent or `lisa:linear-evidence`.
296
+ - **Duplicate terminal exception**: `DUPLICATE_ALREADY_FIXED` is the only triage outcome that may close a claimed Issue without a PR from this cycle. It must include a canonical Issue reference and empirical base-branch evidence, and it closes through the configured duplicate/canceled terminal path rather than as completed build work.
278
297
  - **Terminal native closure**: after the `$DONE` label is applied, move the Linear Issue to a native completed state only when `$DONE` is the true terminal done value; intermediate env labels stay open / active.
279
298
  - **One item per cycle**: per-Issue exceptions are caught and recorded, then the cycle exits. The scheduler owns retrying or moving on to the next ready item.
280
299
  - **Single cycle per team**: do not run two concurrent cycles against the same team — concurrent claims could race.
@@ -1,12 +1,19 @@
1
1
  ---
2
2
  name: monitor
3
- description: "Monitor application health across environments. Checks health endpoints, recent logs (CloudWatch / Sentry / browser console), error-rate spikes, performance hotspots, pending migrations, and runs Playwright smoke flows when relevant. Routes to the stack-specific ops-specialist agent (Expo, Rails, etc.). Also invoked as the post-deploy step of the lisa:verify skill."
4
- allowed-tools: ["Skill", "Bash", "Read", "Grep"]
3
+ description: "Monitor application health AND audit observability completeness for the current repo, then file build-ready tickets for what it finds. Checks health endpoints, recent logs (CloudWatch / Sentry / browser console), error-rate spikes, performance hotspots, pending migrations, and Playwright smoke flows via the stack-specific ops-specialist (Expo, Rails) or stack-agnostic base probing (any stack, incl. NestJS / CDK). Audits the repo against an observability-completeness rubric scoped to its type (frontend / backend / infra) and flags instrumentation gaps (e.g. no distributed tracing, no DB/query analytics). For each high-signal anomaly and each in-scope missing dimension it files a single-repo, build-ready leaf ticket via tracker-write (idempotent, capped, repo-scoped) so the intake cron implements it — monitor files only, it never fixes. Manual (no cron); files by default, `--dry-run` previews. Conservative by default. Also invoked as the post-deploy step of lisa:verify, where it runs report-only (never files)."
4
+ allowed-tools: ["Skill", "Bash", "Read", "Grep", "Glob"]
5
5
  ---
6
6
 
7
7
  # Monitor: $ARGUMENTS
8
8
 
9
- Spot-check application health in the named environment (`dev` / `staging` / `prod`). Useful both reactively (after a deploy, after a Sentry alert, before pushing a hot change) and as the post-deploy verification step inside `lisa:verify`.
9
+ Spot-check application health, **audit observability completeness**, and **file build-ready tickets** for the problems and instrumentation gaps it finds — all scoped to the current repo. Useful reactively (after a deploy, after a Sentry alert, before pushing a hot change), as a periodic manual observability sweep, and as the post-deploy verification step inside `lisa:verify`.
10
+
11
+ **Arguments:** `<environment>` (`dev` / `staging` / `prod`) `[--dry-run] [--report-only] [--all-gaps] [max_candidates=<n>]`.
12
+
13
+ - `--dry-run` — audit and report which tickets *would* be filed, but create nothing.
14
+ - `--report-only` — health/audit summary only; no filing and no would-file analysis. This is the mode `lisa:verify` passes for its post-deploy check, so monitor never files during a verify run.
15
+ - `--all-gaps` — also file `recommended`-tier gaps (session replay, product analytics, etc.), not just `core`. Does not change anomaly thresholds.
16
+ - `max_candidates=<n>` — cap tickets filed this run (default 20; config `monitor.maxCandidates`).
10
17
 
11
18
  ## Orchestration: agent team
12
19
 
@@ -31,7 +38,9 @@ Treat the first successful lead-spawn request (or, on the Codex fallback, the fi
31
38
 
32
39
  ## Flow
33
40
 
34
- Execute the **Monitor** sub-flow as defined in the `intent-routing` rule (loaded via the lisa plugin). The Monitor sub-flow delegates to a stack-specific `ops-specialist` agent (Expo, Rails, etc.), which composes the underlying ops skills:
41
+ Execute the **Monitor** sub-flow as defined in the `intent-routing` rule (loaded via the lisa plugin): **discover collect live signals audit completeness → report → file (standalone only)**. The `observability-audit` rule owns the profile detection, the completeness rubric, the conservative anomaly thresholds, the gate-passing ticket templates, the fingerprint/idempotency contract, the cap, and the Verify report-only guard — follow it; do not restate it here.
42
+
43
+ **Live-signal collection** delegates to a stack-specific `ops-specialist` agent when the stack overlay ships one. The **Expo** ops-specialist composes the full set below:
35
44
 
36
45
  - `ops-verify-health` — health endpoints
37
46
  - `ops-check-logs` — CloudWatch / browser console / device / Serverless logs
@@ -41,8 +50,20 @@ Execute the **Monitor** sub-flow as defined in the `intent-routing` rule (loaded
41
50
  - `ops-db-ops` — pending migrations, replication lag
42
51
  - `ops-deploy` — deploy status / rollback readiness
43
52
 
44
- The agent decides which subset to run based on the env, the situation, and any extra context provided. The rule contains the canonical decision logic.
53
+ The **Rails** ops-specialist composes a different subset (`ops-run-local`, `ops-deploy`, `ops-check-logs`, `ops-verify-jobs`, `ops-verify-telemetry` — X-Ray/CloudWatch traces and metrics via `ops-verify-telemetry`). When no `ops-specialist` overlay is present (e.g. NestJS, CDK, or a generic TypeScript repo), fall back to **stack-agnostic base probing** — read manifests/config to discover what's wired, then probe live sources directly (Sentry CLI/REST, `aws logs` / `aws cloudwatch` / `aws xray`, the Playwright MCP for client-side console/network), exactly as the `observability-audit` "read-then-probe" detection prescribes. The agent decides which subset to run based on the env, the repo profile, and any extra context.
54
+
55
+ ## Ticket filing (standalone only)
56
+
57
+ After report, file what was found — **only when run standalone**, never under `--report-only`/`--dry-run` and never when nested inside `lisa:verify` (which passes `--report-only`):
58
+
59
+ - **Anomalies** (live signals over the conservative bar) → `Bug` leaves. **Gaps** (in-scope MISSING rubric dimensions) → `Task`/`Improvement` leaves.
60
+ - Every ticket is filed via the vendor-neutral `lisa:tracker-write` shim with `build_ready: true` (never a vendor write skill directly), as a **single-repo leaf** stamped `repo:<current>`, with a real three-audience description, Gherkin AC, Target Backend Environment, and a Validation Journey + `EVIDENCE:` marker so it passes the `tracker-validate` gates.
61
+ - **Idempotent:** embed the `<!-- lisa-monitor-finding: <fingerprint> -->` sentinel and search-before-create; never duplicate a live or just-resolved finding.
62
+ - **Capped** at `max_candidates` (default 20), `core`/high-severity first; report how many were filed vs dropped.
63
+ - **`--dry-run`** previews would-file tickets and creates nothing. **`--all-gaps`** widens gap filing to `recommended` tiers.
64
+
65
+ `monitor` files only. The `intake` / `tracker-build-intake` cron picks the ready tickets up and implements them.
45
66
 
46
67
  ## Output
47
68
 
48
- A health summary with the relevant findings (failures, warnings, no-issue confirmations). For post-deploy verification (when called from `lisa:verify`), the summary becomes evidence on the originating work item.
69
+ A single report: the health/anomaly summary (failures, warnings, no-issue confirmations) + the observability audit table (each in-scope dimension as `OK` / `WARN` / `MISSING` / `PRESENT (unverified)`) + the filing summary (tickets filed with refs + fingerprints, duplicates skipped, dropped count if the cap truncated; or would-file tickets under `--dry-run`). For post-deploy verification (when called from `lisa:verify`), the report-only summary becomes evidence on the originating work item.
@@ -1,4 +1,4 @@
1
1
  display_name: "Monitor"
2
- short_description: "Monitor application health across environments"
2
+ short_description: "Monitor application health AND audit observability completeness for the current repo, then file build-ready tickets for what it finds"
3
3
  default_prompt:
4
- - "Use $monitor: Monitor application health across environments."
4
+ - "Use $monitor: Monitor application health AND audit observability completeness for the current repo, then file build-ready tickets for what it finds."
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ticket-triage
3
- description: "Analytical triage gate for tickets in the configured destination tracker (JIRA, GitHub Issues, or Linear). Detects requirement ambiguities, identifies edge cases from codebase analysis, and plans verification methodology. Posts findings to the ticket and produces a verdict (BLOCKED/PASSED_WITH_FINDINGS/PASSED) that gates whether implementation can proceed. Vendor-neutral: the caller (jira-agent or github-agent) is responsible for fetching the ticket via lisa:tracker-read, running the pre-flight gate via lisa:tracker-verify, and posting findings via the matching vendor comment tool."
3
+ description: "Analytical triage gate for tickets in the configured destination tracker (JIRA, GitHub Issues, or Linear). Detects requirement ambiguities, identifies edge cases from codebase analysis, and plans verification methodology. Posts findings to the ticket and produces a verdict (DUPLICATE_ALREADY_FIXED/BLOCKED/PASSED_WITH_FINDINGS/PASSED) that gates whether implementation can proceed. Vendor-neutral: the caller (jira-agent or github-agent) is responsible for fetching the ticket via lisa:tracker-read, running the pre-flight gate via lisa:tracker-verify, and posting findings via the matching vendor comment tool."
4
4
  allowed-tools: ["Read", "Glob", "Grep", "Bash"]
5
5
  ---
6
6
 
@@ -48,11 +48,13 @@ From the context bundle, evaluate relationships before analyzing this ticket in
48
48
 
49
49
  - **Open blockers (`is blocked by`)**: if any blocker is not `Done` or its linked PR is not merged, raise an ambiguity: "Blocker {KEY} is not shipped — work cannot meaningfully start." This is an automatic `BLOCKED` verdict unless the human confirms the blocker state is acceptable.
50
50
  - **Epic siblings in progress**: if a sibling under the same epic is `In Progress` / `In Review` with a different assignee and overlapping scope, raise it as an edge case in Phase 4 ("Duplicate-work risk with {KEY}").
51
- - **`duplicates` / `is duplicated by` links**: if this ticket is a duplicate of an open ticket, verdict is `BLOCKED` with the recommendation to close as duplicate rather than implement.
51
+ - **`duplicates` / `is duplicated by` links**:
52
+ - If this ticket is a duplicate of an open canonical ticket whose fix is not yet merged into the base branch, verdict is `BLOCKED` with the recommendation to close as duplicate manually rather than implement.
53
+ - If this ticket is a duplicate of canonical work that is already merged/deployed, verdict is `DUPLICATE_ALREADY_FIXED`. This verdict must carry the canonical ticket reference, the canonical PR/commit reference, and empirical evidence that the canonical fix is present on the relevant base branch. Never emit this verdict from a name/label match alone.
52
54
  - **`relates to` links with shipped PRs**: flag the PRs in the verification methodology (Phase 5) as prior art worth reviewing before writing new code.
53
55
 
54
56
  Do not re-fetch tickets — the bundle already has the context.
55
- If Phase 1.5 finds an automatic blocker condition (`is blocked by` not shipped, or duplicate-of-open), emit `BLOCKED` immediately and skip to Phase 6 output formatting.
57
+ If Phase 1.5 finds an automatic blocker condition (`is blocked by` not shipped, or duplicate-of-open), emit `BLOCKED` immediately and skip to Phase 6 output formatting. If it finds a duplicate whose canonical fix is empirically present on the base branch, emit `DUPLICATE_ALREADY_FIXED` immediately and skip to Phase 6 output formatting.
56
58
 
57
59
  ## Phase 2 -- Cross-Repo Awareness
58
60
 
@@ -142,6 +144,7 @@ Every verification method must be specific enough that an automated agent could
142
144
  Evaluate the findings and produce exactly one verdict:
143
145
 
144
146
  - **`NOT_RELEVANT`** -- No relevant code was found in this repository (Phase 1). The caller should add the triage label and skip implementation in this repo.
147
+ - **`DUPLICATE_ALREADY_FIXED`** -- This ticket duplicates canonical work whose fix is already merged/deployed and empirically confirmed present on the relevant base branch. Work MUST NOT proceed. The caller must post the triage finding, ensure the native `duplicates <canonical>` link exists when the tracker supports one, and return the structured canonical reference/evidence to build intake for terminal duplicate closeout.
145
148
  - **`BLOCKED`** -- Blocking conditions were found in Phase 0 (missing required description content), Phase 1.5 (open blockers, duplicate-of-open), and/or Phase 3 (ambiguities). Work MUST NOT proceed until resolved by a human. When the block is from Phase 0, the caller (jira-agent) MUST transition the ticket to `Blocked` and reassign to the Reporter — not just leave it in place. For Phase 1.5 / Phase 3 blocks, post findings, add the triage label, and STOP.
146
149
  - **`PASSED_WITH_FINDINGS`** -- No ambiguities, but edge cases or verification findings were identified. Work can proceed. The caller should post findings and add the triage label.
147
150
  - **`PASSED`** -- No ambiguities, edge cases, or verification gaps found. Work can proceed. The caller should add the triage label.
@@ -149,7 +152,7 @@ Evaluate the findings and produce exactly one verdict:
149
152
  Output format:
150
153
 
151
154
  ```text
152
- ## Verdict: [NOT_RELEVANT | BLOCKED | PASSED_WITH_FINDINGS | PASSED]
155
+ ## Verdict: [NOT_RELEVANT | DUPLICATE_ALREADY_FIXED | BLOCKED | PASSED_WITH_FINDINGS | PASSED]
153
156
 
154
157
  **Ambiguities found:** [count]
155
158
  **Edge cases identified:** [count]
@@ -172,11 +175,12 @@ Structure all output with clear section headers so the caller can parse and post
172
175
  ### Verification Methodology
173
176
  [Phase 5 table, or "No acceptance criteria to verify."]
174
177
 
175
- ## Verdict: [NOT_RELEVANT | BLOCKED | PASSED_WITH_FINDINGS | PASSED]
178
+ ## Verdict: [NOT_RELEVANT | DUPLICATE_ALREADY_FIXED | BLOCKED | PASSED_WITH_FINDINGS | PASSED]
176
179
  ```
177
180
 
178
181
  The caller is responsible for:
179
182
  1. Posting the findings as comments on the ticket (using whatever Jira mechanism is available)
180
183
  2. Adding the `claude-triaged-{repo}` label to the ticket
181
184
  3. If `BLOCKED` due to Phase 0 (missing required description content): transitioning the ticket to `Blocked`, reassigning to the **Reporter**, posting a comment listing the missing requirements, and stopping all work.
182
- 4. If `BLOCKED` due to Phase 1.5 (open blockers, duplicate-of-open) or Phase 3 (ambiguities): stopping all work and reporting to the human; do NOT auto-transition status in these cases.
185
+ 4. If `DUPLICATE_ALREADY_FIXED`: return the canonical ticket reference and empirical base-branch evidence to build intake so it can close the ticket as a terminal duplicate without opening a PR.
186
+ 5. If `BLOCKED` due to Phase 1.5 (open blockers, duplicate-of-open) or Phase 3 (ambiguities): stopping all work and reporting to the human; do NOT auto-transition status in these cases.