@ozzylabs/feedradar 0.1.5 → 0.1.7

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 (86) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/dismiss.d.ts +2 -1
  3. package/dist/cli/dismiss.d.ts.map +1 -1
  4. package/dist/cli/dismiss.js +4 -1
  5. package/dist/cli/dismiss.js.map +1 -1
  6. package/dist/cli/index.d.ts.map +1 -1
  7. package/dist/cli/index.js +7 -1
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/items.d.ts +44 -0
  10. package/dist/cli/items.d.ts.map +1 -0
  11. package/dist/cli/items.js +288 -0
  12. package/dist/cli/items.js.map +1 -0
  13. package/dist/cli/research.d.ts +21 -0
  14. package/dist/cli/research.d.ts.map +1 -1
  15. package/dist/cli/research.js +54 -10
  16. package/dist/cli/research.js.map +1 -1
  17. package/dist/cli/review.d.ts +23 -0
  18. package/dist/cli/review.d.ts.map +1 -1
  19. package/dist/cli/review.js +293 -2
  20. package/dist/cli/review.js.map +1 -1
  21. package/dist/cli/source.d.ts.map +1 -1
  22. package/dist/cli/source.js +3 -0
  23. package/dist/cli/source.js.map +1 -1
  24. package/dist/cli/triage.d.ts +136 -0
  25. package/dist/cli/triage.d.ts.map +1 -0
  26. package/dist/cli/triage.js +1110 -0
  27. package/dist/cli/triage.js.map +1 -0
  28. package/dist/cli/undismiss.d.ts +30 -0
  29. package/dist/cli/undismiss.d.ts.map +1 -0
  30. package/dist/cli/undismiss.js +133 -0
  31. package/dist/cli/undismiss.js.map +1 -0
  32. package/dist/cli/watch.d.ts.map +1 -1
  33. package/dist/cli/watch.js +2 -0
  34. package/dist/cli/watch.js.map +1 -1
  35. package/dist/cli/workflow/generate-combined-with-triage.d.ts +115 -0
  36. package/dist/cli/workflow/generate-combined-with-triage.d.ts.map +1 -0
  37. package/dist/cli/workflow/generate-combined-with-triage.js +446 -0
  38. package/dist/cli/workflow/generate-combined-with-triage.js.map +1 -0
  39. package/dist/cli/workflow.d.ts +6 -5
  40. package/dist/cli/workflow.d.ts.map +1 -1
  41. package/dist/cli/workflow.js +13 -8
  42. package/dist/cli/workflow.js.map +1 -1
  43. package/dist/core/feeds/json-api.d.ts +26 -0
  44. package/dist/core/feeds/json-api.d.ts.map +1 -1
  45. package/dist/core/feeds/json-api.js +360 -223
  46. package/dist/core/feeds/json-api.js.map +1 -1
  47. package/dist/core/recipes.d.ts.map +1 -1
  48. package/dist/core/recipes.js +10 -0
  49. package/dist/core/recipes.js.map +1 -1
  50. package/dist/core/transitions.d.ts +30 -0
  51. package/dist/core/transitions.d.ts.map +1 -0
  52. package/dist/core/transitions.js +103 -0
  53. package/dist/core/transitions.js.map +1 -0
  54. package/dist/core/triage/adapter.d.ts +80 -0
  55. package/dist/core/triage/adapter.d.ts.map +1 -0
  56. package/dist/core/triage/adapter.js +128 -0
  57. package/dist/core/triage/adapter.js.map +1 -0
  58. package/dist/core/triage/index.d.ts +105 -0
  59. package/dist/core/triage/index.d.ts.map +1 -0
  60. package/dist/core/triage/index.js +246 -0
  61. package/dist/core/triage/index.js.map +1 -0
  62. package/dist/core/triage/prompt.d.ts +30 -0
  63. package/dist/core/triage/prompt.d.ts.map +1 -0
  64. package/dist/core/triage/prompt.js +157 -0
  65. package/dist/core/triage/prompt.js.map +1 -0
  66. package/dist/core/triage/response.d.ts +114 -0
  67. package/dist/core/triage/response.d.ts.map +1 -0
  68. package/dist/core/triage/response.js +188 -0
  69. package/dist/core/triage/response.js.map +1 -0
  70. package/dist/recipes/aws-whats-new.yaml +62 -7
  71. package/dist/recipes/dev-to.yaml +24 -0
  72. package/dist/schemas/item.d.ts +151 -5
  73. package/dist/schemas/item.d.ts.map +1 -1
  74. package/dist/schemas/item.js +164 -4
  75. package/dist/schemas/item.js.map +1 -1
  76. package/dist/schemas/recipe.d.ts +22 -0
  77. package/dist/schemas/recipe.d.ts.map +1 -1
  78. package/dist/schemas/recipe.js +13 -1
  79. package/dist/schemas/recipe.js.map +1 -1
  80. package/dist/schemas/source.d.ts +135 -0
  81. package/dist/schemas/source.d.ts.map +1 -1
  82. package/dist/schemas/source.js +138 -0
  83. package/dist/schemas/source.js.map +1 -1
  84. package/dist/templates/agents/AGENTS.md +36 -4
  85. package/dist/templates/workflows/combined-with-triage.template.yaml.tmpl +133 -0
  86. package/package.json +1 -1
@@ -0,0 +1,188 @@
1
+ import { z } from "zod";
2
+ import { TriageDecisionValueSchema } from "../../schemas/item.js";
3
+ /**
4
+ * Triage response parser + validator (ADR-0018 §W4).
5
+ *
6
+ * The agent returns a JSON array (one entry per input item) on stdout. This
7
+ * module turns that raw string into a `Map<itemId, ValidatedTriageEntry>`,
8
+ * applying the safety rules from ADR-0018:
9
+ *
10
+ * 1. **Strict JSON parse.** Total parse failure is reported to the caller as
11
+ * a `TriageResponseParseError`; the caller (adapter.ts) decides whether
12
+ * to retry the agent invocation or fall back to all-unsure.
13
+ * 2. **Schema validate per entry.** Entries that fail Zod parse become
14
+ * `unsure` entries with a synthesized reason; only the malformed entry is
15
+ * downgraded, the rest of the array is kept.
16
+ * 3. **Hallucinated id reject.** Entries whose `id` is not in the input set
17
+ * are dropped from the result entirely (the caller's full-coverage check
18
+ * will turn the missing items into `unsure` entries with reason
19
+ * `"agent-omitted"`). Storing a hallucinated id on disk would corrupt the
20
+ * items index.
21
+ * 4. **Duplicate id reject.** When the agent emits two entries for the same
22
+ * id, the **first** is kept and the duplicate triggers a warning. This is
23
+ * safer than overwriting — agents that hallucinate duplicates often emit
24
+ * contradictory decisions, and the first is at least likely to reflect
25
+ * the policy more directly.
26
+ * 5. **Confidence threshold demotion.** Entries below
27
+ * `policy.confidenceThreshold` are demoted to `decision: "unsure"`. The
28
+ * original confidence is preserved so downstream feedback analysis can
29
+ * correlate "low confidence + demoted" outcomes.
30
+ * 6. **Digest without group → unsure.** A `decision: "digest"` entry without
31
+ * a non-empty `group` is structurally invalid (the digest CLI needs the
32
+ * key to collect siblings). We demote rather than reject so the operator
33
+ * still gets a record.
34
+ *
35
+ * The output is a `Map`, not an array, so the adapter can do O(1) coverage
36
+ * checks against the input id set.
37
+ */
38
+ /**
39
+ * Raw schema for one element of the agent's JSON response. We accept any
40
+ * shape that has the four required fields (id / decision / confidence /
41
+ * reason) and an optional group; unknown fields are dropped silently so the
42
+ * agent has room to add metadata without breaking parse.
43
+ *
44
+ * `decision` is parsed via `TriageDecisionValueSchema` so the same enum is
45
+ * shared with `TriageDecisionSchema` on the item — any drift would be caught
46
+ * at this boundary instead of corrupting the items index.
47
+ */
48
+ const AgentEntrySchema = z.object({
49
+ id: z.string().min(1),
50
+ decision: TriageDecisionValueSchema,
51
+ confidence: z.number().min(0).max(1),
52
+ reason: z.string().min(1),
53
+ group: z.string().min(1).optional(),
54
+ });
55
+ /**
56
+ * The whole response body is just an array of entries. We extract this as a
57
+ * named schema so the malformed-array error message stays consistent across
58
+ * test cases.
59
+ */
60
+ const AgentResponseSchema = z.array(AgentEntrySchema);
61
+ /**
62
+ * Thrown by `parseTriageResponse` when the agent's stdout could not be
63
+ * parsed as JSON at all, or when the top-level value is not an array of
64
+ * entries. The adapter catches this and treats it as a total-fallback
65
+ * situation (every item becomes `triaged_unsure`, `fallback: true`).
66
+ *
67
+ * Per-entry validation failures (one entry malformed but the array parses)
68
+ * do NOT throw — they are recorded in `warnings[]` and the malformed entry
69
+ * is dropped so the rest of the array still applies.
70
+ */
71
+ export class TriageResponseParseError extends Error {
72
+ constructor(message) {
73
+ super(message);
74
+ this.name = "TriageResponseParseError";
75
+ }
76
+ }
77
+ /**
78
+ * Best-effort JSON extraction from agent stdout.
79
+ *
80
+ * Cheap models occasionally wrap their JSON in Markdown code fences
81
+ * (```json ... ```) or prepend / append a sentence even when instructed not
82
+ * to. We strip a leading / trailing code fence and locate the outermost
83
+ * `[ ... ]` slice so the JSON.parse call has a fighting chance. If neither
84
+ * heuristic helps, we let `JSON.parse` fail and propagate the error.
85
+ */
86
+ function extractJsonArrayPayload(raw) {
87
+ const trimmed = raw.trim();
88
+ // Strip a single ```json / ``` ... ``` fence if present.
89
+ const fenceMatch = trimmed.match(/^```(?:json)?\s*\n([\s\S]*?)\n```$/);
90
+ const fenced = fenceMatch ? fenceMatch[1].trim() : trimmed;
91
+ // If the agent already emitted a clean array, return it directly.
92
+ if (fenced.startsWith("[")) {
93
+ return fenced;
94
+ }
95
+ // Otherwise locate the first `[` ... last `]` slice. This is intentionally
96
+ // greedy: cheap-model preamble usually sits before `[`, so trimming to the
97
+ // outermost brackets recovers the array.
98
+ const first = fenced.indexOf("[");
99
+ const last = fenced.lastIndexOf("]");
100
+ if (first === -1 || last === -1 || last <= first) {
101
+ return fenced;
102
+ }
103
+ return fenced.slice(first, last + 1);
104
+ }
105
+ /**
106
+ * Parse and validate the agent's triage response against the input item set
107
+ * and per-source policy.
108
+ *
109
+ * `inputItems` is consulted for two reasons: (a) hallucinated-id reject — we
110
+ * only accept ids that appear in the input set, and (b) the caller (adapter)
111
+ * uses the returned `entries` Map to figure out which items the agent
112
+ * omitted entirely (those become `unsure` with reason `"agent-omitted"`).
113
+ */
114
+ export function parseTriageResponse(raw, inputItems, policy) {
115
+ const payload = extractJsonArrayPayload(raw);
116
+ let parsedJson;
117
+ try {
118
+ parsedJson = JSON.parse(payload);
119
+ }
120
+ catch (err) {
121
+ const message = err instanceof Error ? err.message : String(err);
122
+ throw new TriageResponseParseError(`triage response is not valid JSON: ${message}`);
123
+ }
124
+ const arrayResult = AgentResponseSchema.safeParse(parsedJson);
125
+ if (!arrayResult.success) {
126
+ // If the top-level shape failed (e.g. agent returned an object instead of
127
+ // an array), bail to the total-fallback path. Per-entry shape errors are
128
+ // handled below (we still get a partial array there).
129
+ if (!Array.isArray(parsedJson)) {
130
+ throw new TriageResponseParseError(`triage response top-level value is not an array: ${arrayResult.error.message}`);
131
+ }
132
+ }
133
+ const inputIds = new Set(inputItems.map((i) => i.id));
134
+ const entries = new Map();
135
+ const warnings = [];
136
+ // Iterate the raw parsed JSON (which we know is an array at this point) so
137
+ // we can per-entry validate and gather warnings without aborting on the
138
+ // first malformed entry. We re-run AgentEntrySchema per element to get
139
+ // precise error messages.
140
+ const rawArray = Array.isArray(parsedJson) ? parsedJson : [];
141
+ for (let idx = 0; idx < rawArray.length; idx++) {
142
+ const entryResult = AgentEntrySchema.safeParse(rawArray[idx]);
143
+ if (!entryResult.success) {
144
+ warnings.push(`entry[${idx}] failed schema validation: ${entryResult.error.issues
145
+ .map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
146
+ .join("; ")}`);
147
+ continue;
148
+ }
149
+ const entry = entryResult.data;
150
+ if (!inputIds.has(entry.id)) {
151
+ warnings.push(`entry[${idx}] references unknown id "${entry.id}" (hallucinated, rejected)`);
152
+ continue;
153
+ }
154
+ if (entries.has(entry.id)) {
155
+ warnings.push(`entry[${idx}] duplicates id "${entry.id}" (kept first, ignored)`);
156
+ continue;
157
+ }
158
+ // Apply demotion rules. We track the original decision in the warning so
159
+ // the operator (or feedback CLI) can see why a low-confidence research
160
+ // decision became unsure.
161
+ let decision = entry.decision;
162
+ let reason = entry.reason;
163
+ let demoted = false;
164
+ if (decision === "digest" && (entry.group === undefined || entry.group.trim() === "")) {
165
+ decision = "unsure";
166
+ reason = `digest decision without group key (demoted from "digest"): ${entry.reason}`;
167
+ demoted = true;
168
+ warnings.push(`entry[${idx}] "${entry.id}" demoted: digest without group key`);
169
+ }
170
+ if (decision !== "unsure" && entry.confidence < policy.confidenceThreshold) {
171
+ const original = decision;
172
+ decision = "unsure";
173
+ reason = `confidence ${entry.confidence.toFixed(2)} below threshold ${policy.confidenceThreshold.toFixed(2)} (demoted from "${original}"): ${entry.reason}`;
174
+ demoted = true;
175
+ warnings.push(`entry[${idx}] "${entry.id}" demoted: confidence ${entry.confidence.toFixed(2)} < threshold ${policy.confidenceThreshold.toFixed(2)}`);
176
+ }
177
+ entries.set(entry.id, {
178
+ id: entry.id,
179
+ decision,
180
+ confidence: entry.confidence,
181
+ reason,
182
+ group: decision === "digest" ? entry.group : undefined,
183
+ demoted,
184
+ });
185
+ }
186
+ return { entries, warnings };
187
+ }
188
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../../src/core/triage/response.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,11 @@
1
1
  name: AWS What's New
2
2
  description: AWS What's New announcements (page-based JSON API, full-history backfill capable)
3
3
  kind: json-api
4
- url: https://aws.amazon.com/api/dirs/items/search?item.directoryId=whats-new&sort_by=item.additionalFields.postDateTime&sort_order=desc&size=100&page=0&item.locale=en_US
4
+ # `whats-new-v2` is the active directoryId that backs the current
5
+ # aws.amazon.com/new/ landing page. The legacy `whats-new` directoryId is
6
+ # frozen at 2024-05-17 (16,281 items) and no longer ingests new posts. We
7
+ # discovered v2 by grepping the rendered HTML of /new/ for `whats-new-v2`.
8
+ url: https://aws.amazon.com/api/dirs/items/search?item.directoryId=whats-new-v2&sort_by=item.additionalFields.postDateTime&sort_order=desc&size=100&page=0&item.locale=en_US
5
9
  tags:
6
10
  - aws
7
11
  - cloud
@@ -33,13 +37,35 @@ pagination:
33
37
  start: 0
34
38
  pageSize: 100
35
39
  pageSizeParam: size
36
- # 200 pages * 100 items/page = ~20,000 items, well above the ~16,281 total
37
- # observed in 2026-05. `--backfill --max-pages N` can narrow further.
38
- maxPages: 200
39
- # `metadata.totalHits` is the AWS-reported total; the adapter uses it to
40
- # short-circuit backfill once `ceil(totalHits / pageSize)` pages have been
41
- # walked (ADR-0012 §D2 backfill early-stop hint).
40
+ # PER-FACET inner cap (ADR-0017). The 10,000-item offset cap is
41
+ # circumvented by the year-by-year facet sweep below each year holds
42
+ # at most ~2,345 items (2024), which fits in ≤24 pages at pageSize=100.
43
+ # 30 leaves ~25% headroom for the largest year. Previous value 250 was
44
+ # dictated by full-history sweep on a single axis (PR #232) and is
45
+ # superseded by the facet sweep approach in ADR-0017. `--backfill
46
+ # --max-pages N` applies to this inner cap, not the outer year axis.
47
+ maxPages: 30
48
+ # `metadata.totalHits` is the AWS-reported total per facet value; the
49
+ # adapter uses it to short-circuit backfill once `ceil(totalHits /
50
+ # pageSize)` pages have been walked (ADR-0012 §D2 backfill early-stop
51
+ # hint). With the year facet active, each year reports its own
52
+ # totalHits (~2,000-2,300 for active years).
42
53
  totalPath: $.metadata.totalHits
54
+ # Facet sweep (ADR-0017). The AWS dirs API enforces a hard
55
+ # `(page + 1) * size <= 10000` cap, making the upper ~half of the catalog
56
+ # (totalHits 21,834 vs reachable 10,000) unreachable via inner pagination
57
+ # alone. Sweeping year-by-year recovers full coverage with zero gap: each
58
+ # year holds well under 10,000 items, so the inner traversal never bumps
59
+ # the AWS-side limit. Range covers 2004 (AWS's first announcement year)
60
+ # through the current year; out-of-range years simply return 0 items and
61
+ # the inner loop terminates immediately.
62
+ facets:
63
+ year:
64
+ type: range
65
+ range: [2004, 2026]
66
+ step: 1
67
+ param: tags.id
68
+ template: "whats-new-v2#year#{}"
43
69
  jsonSelectors:
44
70
  # Each result is wrapped in `{ "item": { ... } }`, so we dereference one
45
71
  # extra level inside `items[*].item` to keep the per-element selectors
@@ -59,3 +85,32 @@ jsonSelectors:
59
85
  summary: $.additionalFields.postBody
60
86
  publisherId: $.id
61
87
  trustLevel: untrusted
88
+ # Default triage policy bundled with this recipe (ADR-0018 §W3 / #241).
89
+ # Propagates onto `sources/<id>.yaml > triagePolicy:` when the user runs
90
+ # `radar source add <id> --recipe aws-whats-new`. The rules block is
91
+ # free-form markdown — see docs/user-guide.md "triage workflow" §policy
92
+ # 書き方ガイド for tuning tips. Cheap-model channel (gemini-2.5-flash-lite)
93
+ # keeps per-source triage cost well below $0.01/month even at AWS's volume
94
+ # (~700 items/month at peak).
95
+ triagePolicy:
96
+ agent: gemini-cli
97
+ confidenceThreshold: 0.7
98
+ rules: |
99
+ 重要 (research):
100
+ - 新サービス / 新機能の GA, Preview ローンチ
101
+ - 価格改定 / 料金体系変更
102
+ - リブランド・ブランド統合 (例: QuickSight → Quick)
103
+ - セキュリティ関連 (脆弱性対応、認証方式変更)
104
+ - メジャーな API 変更 / 既存サービスの semantics 変更
105
+ 集約 (digest):
106
+ - 既存サービスへの incremental な機能追加 (UI option, filter 追加)
107
+ group: ui-incremental
108
+ - 連携サービス追加 (新 connector, 新 integration)
109
+ group: integrations
110
+ - performance 改善・SLA 向上 announcement
111
+ group: performance-sla
112
+ 除外 (dismiss):
113
+ - リージョン拡張のみ (例: "now available in <region>")
114
+ - SDK バージョン bump
115
+ - ドキュメント更新通知
116
+ - 既存リソースの minor 設定追加 (例: "now supports tagging")
@@ -38,3 +38,27 @@ pagination:
38
38
  # `jsonSelectors` omitted intentionally: the default chain (ADR-0012 §D2 /
39
39
  # #174) handles the dev.to shape end-to-end.
40
40
  trustLevel: untrusted
41
+ # Default triage policy bundled with this recipe (ADR-0018 §W3 / #241).
42
+ # dev.to mixes technical deep-dives with marketing posts and "hello world"
43
+ # tutorials, so the rules below bias toward research for framework /
44
+ # performance / security topics, digest tutorial round-ups, and dismiss
45
+ # personal promo / intro content. Adjust per workspace by overriding the
46
+ # generated `sources/<id>.yaml > triagePolicy.rules:` block.
47
+ triagePolicy:
48
+ agent: gemini-cli
49
+ confidenceThreshold: 0.7
50
+ rules: |
51
+ 重要 (research):
52
+ - 新フレームワーク / 新ツールの紹介 / GA 発表記事
53
+ - performance benchmark / architecture deep-dive
54
+ - security 脆弱性報告 / CVE 解説
55
+ - メジャーアップデート (vN.0 リリース解説)
56
+ 集約 (digest):
57
+ - tutorial 系の週次 round-up (関連トピック数本まとめ)
58
+ group: tutorial-roundup
59
+ - "Top N libraries for X" のようなキュレーション記事
60
+ group: curation
61
+ 除外 (dismiss):
62
+ - 個人ブログ宣伝 / SaaS プロダクト promo
63
+ - 入門記事 ("hello world", "what is X" 系)
64
+ - "I built X in N hours" 系の体験記
@@ -1,21 +1,139 @@
1
1
  import { z } from "zod";
2
2
  /**
3
- * Item status state machine (ADR-0008).
3
+ * Item status state machine (ADR-0008 + ADR-0018).
4
+ *
5
+ * Original 4-state machine:
4
6
  *
5
7
  * detected ──► (dismissed | researched) ──► reviewed
6
8
  *
7
- * - `detected`: watch run emitted the item after filter
8
- * - `dismissed`: user decided not to research (terminal)
9
+ * Triage extension (ADR-0018) adds 3 intermediate states. `triaged_dismiss`
10
+ * is intentionally **not** a separate status it is collapsed into existing
11
+ * `dismissed` with the `dismissedBy: "human" | "triage_<agent>"` sub-field
12
+ * recording the origin. Total status count: 4 → 7.
13
+ *
14
+ * detected
15
+ * ├── triage ──► triaged_research ──► researched ──► reviewed
16
+ * ├── triage ──► triaged_digest ──► researched (digest 合流)
17
+ * ├── triage ──► triaged_unsure ──► (human loop) ──► research / dismiss
18
+ * └── triage ──► dismissed (dismissedBy: triage_<agent>)
19
+ *
20
+ * dismissed ──► detected (via `radar undismiss`)
21
+ *
22
+ * Status semantics (per ADR-0018 §W-B):
23
+ *
24
+ * - `detected`: watch run emitted the item after filter, triage not yet run
25
+ * - `triaged_research`: triage classified as research-worthy
26
+ * - `triaged_digest`: triage classified as digest candidate (group key in `triage.group`)
27
+ * - `triaged_unsure`: triage confidence below threshold; human judgment needed
9
28
  * - `researched`: research report written
10
29
  * - `reviewed`: research report reviewed (terminal happy path)
30
+ * - `dismissed`: human or triage agent decided not to research (terminal,
31
+ * reversible via `radar undismiss`; origin in `dismissedBy`)
11
32
  */
12
33
  export declare const ItemStatusSchema: z.ZodEnum<{
13
34
  detected: "detected";
14
- dismissed: "dismissed";
35
+ triaged_research: "triaged_research";
36
+ triaged_digest: "triaged_digest";
37
+ triaged_unsure: "triaged_unsure";
15
38
  researched: "researched";
16
39
  reviewed: "reviewed";
40
+ dismissed: "dismissed";
17
41
  }>;
18
42
  export type ItemStatus = z.infer<typeof ItemStatusSchema>;
43
+ /**
44
+ * Decision values produced by a triage agent (ADR-0018).
45
+ *
46
+ * - `research`: item is research-worthy on its own
47
+ * - `digest`: item belongs to a group worth summarizing as a single digest
48
+ * - `dismiss`: item is not research-worthy; collapse into `dismissed`
49
+ * - `unsure`: triage agent's confidence is below `confidenceThreshold`; defer
50
+ * to human judgment
51
+ */
52
+ export declare const TriageDecisionValueSchema: z.ZodEnum<{
53
+ research: "research";
54
+ digest: "digest";
55
+ dismiss: "dismiss";
56
+ unsure: "unsure";
57
+ }>;
58
+ export type TriageDecisionValue = z.infer<typeof TriageDecisionValueSchema>;
59
+ /**
60
+ * One feedback datapoint on a prior triage decision (ADR-0018 §W5).
61
+ *
62
+ * `radar triage feedback <item-id> --correct | --wrong [--reason <text>]`
63
+ * appends to the array. Stored as a list so multiple reviewers (or the same
64
+ * reviewer revisiting later) can leave independent verdicts without
65
+ * overwriting each other; downstream stats aggregations decide how to
66
+ * combine them.
67
+ */
68
+ export declare const TriageFeedbackSchema: z.ZodObject<{
69
+ correct: z.ZodBoolean;
70
+ reason: z.ZodOptional<z.ZodString>;
71
+ feedbackAt: z.ZodString;
72
+ }, z.core.$strip>;
73
+ export type TriageFeedback = z.infer<typeof TriageFeedbackSchema>;
74
+ /**
75
+ * Triage decision attached to an item by a triage agent (ADR-0018 §W2 / §W5).
76
+ *
77
+ * Schema rationale (post-review #238 W-I):
78
+ *
79
+ * - The outer field on `Item` is the **singular** `triage` (not `triages`).
80
+ * This preserves the option of moving to a multi-agent shape (`triage: {
81
+ * decisions: [...], consensus: ... }`) later via a Zod union without
82
+ * renaming the field. PR-1 ships the single-agent inner shape only;
83
+ * multi-agent extension is a future PR and explicitly NOT in scope.
84
+ * - `decision` is a discriminated string, not a nested object, so the same
85
+ * union-extension pattern can reuse the field name on the inner decision
86
+ * list without ambiguity.
87
+ * - `feedback` is an array, not a single object — multiple human reviewers
88
+ * can leave independent verdicts (see `TriageFeedbackSchema` docstring).
89
+ */
90
+ export declare const TriageDecisionSchema: z.ZodObject<{
91
+ decision: z.ZodEnum<{
92
+ research: "research";
93
+ digest: "digest";
94
+ dismiss: "dismiss";
95
+ unsure: "unsure";
96
+ }>;
97
+ confidence: z.ZodNumber;
98
+ reason: z.ZodString;
99
+ group: z.ZodOptional<z.ZodString>;
100
+ agent: z.ZodString;
101
+ triagedAt: z.ZodString;
102
+ feedback: z.ZodDefault<z.ZodArray<z.ZodObject<{
103
+ correct: z.ZodBoolean;
104
+ reason: z.ZodOptional<z.ZodString>;
105
+ feedbackAt: z.ZodString;
106
+ }, z.core.$strip>>>;
107
+ }, z.core.$strip>;
108
+ export type TriageDecision = z.infer<typeof TriageDecisionSchema>;
109
+ /**
110
+ * Origin of a `dismissed` decision (ADR-0018 §W2 / §W6).
111
+ *
112
+ * Distinguishes human dismiss from triage-agent dismiss so `radar undismiss`
113
+ * can apply the right safety behavior: triage-origin dismisses are reversible
114
+ * without a flag, human-origin dismisses require `--force` (= confirms the
115
+ * user is overriding their own prior decision, not just an agent's).
116
+ *
117
+ * Defined as a string enum (not a union with `AgentIdSchema`) because the
118
+ * `triage_` prefix lets schema validation reject malformed values like
119
+ * `"claude-code"` (missing prefix) that would silently collide with the
120
+ * `human` case.
121
+ *
122
+ * Maintenance note: this enum mirrors `AgentIdSchema` (`src/schemas/research.ts`)
123
+ * with a `triage_` prefix. When a new agent adapter is added, both enums
124
+ * must be updated in lockstep. A test (`tests/schemas/item.test.ts`)
125
+ * iterates each `AgentIdSchema` value and asserts the corresponding
126
+ * `triage_<agent>` variant parses; that test will fail loudly if the two
127
+ * lists drift out of sync.
128
+ */
129
+ export declare const DismissedBySchema: z.ZodEnum<{
130
+ human: "human";
131
+ "triage_claude-code": "triage_claude-code";
132
+ "triage_codex-cli": "triage_codex-cli";
133
+ "triage_gemini-cli": "triage_gemini-cli";
134
+ triage_copilot: "triage_copilot";
135
+ }>;
136
+ export type DismissedBy = z.infer<typeof DismissedBySchema>;
19
137
  export declare const ItemSchema: z.ZodObject<{
20
138
  id: z.ZodString;
21
139
  sourceId: z.ZodString;
@@ -28,11 +146,39 @@ export declare const ItemSchema: z.ZodObject<{
28
146
  matchedKeywords: z.ZodDefault<z.ZodArray<z.ZodString>>;
29
147
  status: z.ZodDefault<z.ZodEnum<{
30
148
  detected: "detected";
31
- dismissed: "dismissed";
149
+ triaged_research: "triaged_research";
150
+ triaged_digest: "triaged_digest";
151
+ triaged_unsure: "triaged_unsure";
32
152
  researched: "researched";
33
153
  reviewed: "reviewed";
154
+ dismissed: "dismissed";
34
155
  }>>;
35
156
  injectionFlags: z.ZodDefault<z.ZodArray<z.ZodString>>;
157
+ triage: z.ZodOptional<z.ZodObject<{
158
+ decision: z.ZodEnum<{
159
+ research: "research";
160
+ digest: "digest";
161
+ dismiss: "dismiss";
162
+ unsure: "unsure";
163
+ }>;
164
+ confidence: z.ZodNumber;
165
+ reason: z.ZodString;
166
+ group: z.ZodOptional<z.ZodString>;
167
+ agent: z.ZodString;
168
+ triagedAt: z.ZodString;
169
+ feedback: z.ZodDefault<z.ZodArray<z.ZodObject<{
170
+ correct: z.ZodBoolean;
171
+ reason: z.ZodOptional<z.ZodString>;
172
+ feedbackAt: z.ZodString;
173
+ }, z.core.$strip>>>;
174
+ }, z.core.$strip>>;
175
+ dismissedBy: z.ZodOptional<z.ZodEnum<{
176
+ human: "human";
177
+ "triage_claude-code": "triage_claude-code";
178
+ "triage_codex-cli": "triage_codex-cli";
179
+ "triage_gemini-cli": "triage_gemini-cli";
180
+ triage_copilot: "triage_copilot";
181
+ }>>;
36
182
  }, z.core.$strip>;
37
183
  export type Item = z.infer<typeof ItemSchema>;
38
184
  //# sourceMappingURL=item.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"item.d.ts","sourceRoot":"","sources":["../../src/schemas/item.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB;;;;;EAA8D,CAAC;AAC5F,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;iBAoBrB,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC"}
1
+ {"version":3,"file":"item.d.ts","sourceRoot":"","sources":["../../src/schemas/item.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;EAQ3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB;;;;;EAAsD,CAAC;AAC7F,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB;;;;iBAW/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;iBAmC/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,iBAAiB;;;;;;EAM5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyCrB,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC"}