Skip to content

feat: add triage package split#225

Merged
duncanleo merged 37 commits into
mainfrom
codex/triage-step-4
May 17, 2026
Merged

feat: add triage package split#225
duncanleo merged 37 commits into
mainfrom
codex/triage-step-4

Conversation

@duncanleo
Copy link
Copy Markdown
Member

@duncanleo duncanleo commented May 14, 2026

Summary

Adds the step-4 @mrtdown/triage package as an extracted slice from the large data-overhaul source PR (#177), with only the adaptation needed for this stacked branch.

This PR includes:

  • the extracted packages/triage package layout, pure helpers, LLM functions, maintenance/replay scripts, ingestion utilities, and deterministic tests
  • explicit paid/model-dependent eval wiring through test:eval, with package docs for required environment variables, model dependency, and expected cost
  • a root Vitest config so default deterministic test commands do not discover eval.test.ts
  • root command/docs updates for build:triage, test:triage, and test:eval
  • additive @mrtdown/fs repository/writer support (MRTDownRepository, MRTDownWriter, file stores, standard repositories/writers, and IdGenerator) that the extracted triage package depends on
  • review hardening for path-safe issue writes, duplicate entity detection, append-only issue safety, tool-call limits, slug validation, OpenAI key validation, and reduced raw tool logging

The @mrtdown/fs support technically belongs to the earlier file-backed repository/CLI slice, but it was present in the large source PR and is required by the extracted triage implementation. Keeping it as separate commits in this PR makes the backfill visible while avoiding another hand-written divergence from the source branch.

This PR intentionally does not include static canonical data migration, issue dataset migration, runtime removal, deploy cleanup, Pages generation, or package publishing.

Rebase Note

This branch has been rebased onto the updated main after the split-plan rework landed there. During the rebase, the package dependency/script conflicts were resolved by keeping main's Pages/build surfaces and this branch's triage package commands.

Validation

  • npm run check
  • npm run build:packages
  • npm run test:packages
  • GitHub build
  • GitHub validate
  • CodeRabbit

npm run test:eval was not run because it is model-backed and paid.

Summary by CodeRabbit

  • New Features

    • LLM-driven triage: claim extraction, issue classification, title/slug generation, translation, and cost-estimated model calls
    • Persistent and in-memory stores with safe read/write/append, writers, replaying and re-extracting impact events, and webhook ingestion CLI
    • Search/lookup tools for lines/services/stations and relative-date resolution
  • Documentation

    • Triage package README/CHANGELOG and updated developer build/test commands
  • Tests

    • Extensive unit and eval suites covering triage logic, stores, writers, tools, and LLM flows
  • Chores

    • CI/test configs and scripts updated (Vitest, workspace scripts, npm)

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Warning

Rate limit exceeded

@duncanleo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 17 minutes and 25 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2501de85-1b03-41bb-a1af-fef2b06a5a32

📥 Commits

Reviewing files that changed from the base of the PR and between 41f536f and 446ff35.

📒 Files selected for processing (13)
  • packages/fs/src/index.test.ts
  • packages/fs/src/repo/common/FileStore.ts
  • packages/fs/src/repo/issue/IssueRepository.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/fs/src/write/common/store.ts
  • packages/fs/src/write/issue/IssueWriter.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/triage/src/llm/common/MemoryStore.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/degradedServiceHeuristics.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
📝 Walkthrough

Walkthrough

Adds a new workspace package (@mrtdown/triage) with LLM-driven triage, many helpers, tools, maintenance scripts and ingest pipeline; extends packages/fs with file store/repositories/writers and ID utilities; adds tests, Vitest configs, CI path filters, and documentation/manifest updates.

Changes

Triage + FS Integration

Layer / File(s) Summary
All functional changes
packages/triage/**, packages/fs/**, vitest.config.ts, .github/workflows/validate.yml, package.json, README.md, AGENTS.md, docs/DATA_OVERHAUL_SPLIT.md
Introduces the @mrtdown/triage package (LLM clients, tools, functions, maintenance scripts, ingest pipeline, tests, configs), adds file-backed stores/repositories/writers in @mrtdown/fs (IStore/IWriteStore, FileStore/FileWriteStore, StandardRepository/Writer, MRTDownRepository, IssueRepository), many helper utilities (deriveCurrentState, computeImpactFromEvidenceClaims, reconstructClaimsFromImpactEvents, time/interval helpers), Vitest configs and CI/workflow updates, and numerous tests and docs. See diffs for per-file details.
  • Estimated code review effort:
    🎯 5 (Critical) | ⏱️ ~120 minutes

  • Possibly related PRs:

"I'm a rabbit with a tiny pen,
I stitched triage code again and again.
New stores and tools I gently sow,
Tests hop in a lined-up row.
Build the docs, then off I go 🐇"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/triage-step-4

@duncanleo duncanleo force-pushed the codex/triage-step-4 branch from 1b0c55e to f31a43c Compare May 14, 2026 11:33
@duncanleo duncanleo marked this pull request as ready for review May 14, 2026 11:40
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 91ace8bd99

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/triage/src/llm/common/MemoryStore.ts Outdated
Comment thread packages/fs/src/write/common/FileWriteStore.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 18

🧹 Nitpick comments (17)
packages/triage/src/helpers/splitIntervalByServiceHours.ts (1)

21-24: 💤 Low value

Consider extracting the service hours constant.

The service start time (05:30) at line 23 is hard-coded. If service hours might vary or need configuration, consider extracting this to a named constant at the module or package level.

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

In `@packages/triage/src/helpers/splitIntervalByServiceHours.ts` around lines 21 -
24, Extract the hard-coded service start time (05:30) into a named module-level
constant (e.g., SERVICE_START_TIME or SERVICE_HOURS_START) and use that constant
instead of inline literals in the DateTime.set call used when computing
segmentStart (the DateTime.max(start, start.set(...)) call). Define the constant
as an object or DateTime-friendly structure so it can be reused anywhere in this
module (and updated centrally), and replace any other hard-coded service hour
literals in this file to reference the new constant.
packages/triage/src/helpers/deriveCurrentState.ts (2)

65-77: 💤 Low value

Consider clarifying the impactEventIds tracking behavior.

The current implementation tracks only the last event ID per category (serviceEffect, serviceScopes, periods, causes, facilityEffect), meaning multiple events of the same type will overwrite each other. While this appears intentional, the JSDoc at line 50 ("The impact event ids that were used to derive the current state") could be more explicit about returning the latest event ID per category rather than all event IDs.

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

In `@packages/triage/src/helpers/deriveCurrentState.ts` around lines 65 - 77,
Update the JSDoc for deriveCurrentState to explicitly state that impactEventIds
(the impactEventIds object in deriveCurrentState) records only the most recent
event ID per category (serviceEffect, serviceScopes, periods, causes,
facilityEffect) rather than a list of all event IDs; mention that each property
holds the latest event id used to derive state so callers understand overwrite
behavior of impactEventIds.

168-171: ⚡ Quick win

Remove @ts-expect-error to preserve exhaustiveness checking.

The @ts-expect-error directive at line 169 suppresses TypeScript's exhaustiveness checking on the entity type. If a new entity type is added to AffectedEntity in the future, TypeScript won't flag this location. Remove the directive to restore compile-time safety — TypeScript will error if the switch becomes non-exhaustive.

♻️ Proposed fix
       default: {
-        // `@ts-expect-error` fallback case
-        throw new Error(`Unknown entity type: ${impactEvent.entity.type}`);
+        // Exhaustiveness check - TypeScript will error if cases are missing
+        const _exhaustive: never = impactEvent.entity;
+        throw new Error(`Unknown entity type: ${(_exhaustive as { type: string }).type}`);
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/triage/src/helpers/deriveCurrentState.ts` around lines 168 - 171,
Remove the suppression comment on the switch default in deriveCurrentState (the
line with "// `@ts-expect-error` fallback case"); specifically delete that
`@ts-expect-error` so TypeScript will enforce exhaustiveness for
impactEvent.entity.type in the switch and surface compile errors if new
AffectedEntity variants are added; keep the existing default behavior (throw new
Error(`Unknown entity type: ${impactEvent.entity.type}`)) so the runtime
fallback remains while the compiler forces you to add missing cases.
packages/triage/src/util/ingestContent/index.ts (2)

106-106: ⚡ Quick win

Extract hardcoded model name into a constant.

The string '@openai/gpt-5-nano' is hardcoded in two places (lines 106 and 133). This should be extracted into a module-level or package-level constant to ensure consistency and ease future model updates.

♻️ Proposed refactor
+const DEFAULT_LLM_MODEL = '@openai/gpt-5-nano';
+
 export async function ingestContent(content: IngestContent) {
   // ...
   
   const issue: Issue = {
     id: issueId,
     type: triageResult.result.issueType,
     title: translatedTitles,
     titleMeta: {
-      source: '@openai/gpt-5-nano',
+      source: DEFAULT_LLM_MODEL,
     },
   };
   
   // ...
   
   const evidence: Evidence = {
     id: IdGenerator.evidenceId(contentDateTime),
     ts: contentDateTime.toISO({ includeOffset: true }),
     type: getEvidenceType(content),
     text: getText(content),
     sourceUrl: content.url,
     render: {
       text: await translate(getText(content)),
-      source: '@openai/gpt-5-nano',
+      source: DEFAULT_LLM_MODEL,
     },
   };

Also applies to: 133-133

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

In `@packages/triage/src/util/ingestContent/index.ts` at line 106, The literal
model string '@openai/gpt-5-nano' is duplicated; extract it to a single
module-level constant (e.g., const DEFAULT_EMBEDDING_MODEL =
'@openai/gpt-5-nano') and replace both hardcoded usages with that constant (the
two occurrences around lines where source is set and the second usage). If this
constant should be package-wide, export it so other modules can reuse it; update
any references in functions that build embeddings or set the source to use
DEFAULT_EMBEDDING_MODEL to ensure consistency.

39-43: 💤 Low value

Document or resolve the timezone HACK.

The comment indicates that forcing createdAt to Asia/Singapore is a HACK. If this is a temporary workaround, consider adding a TODO with context explaining when/why this should be fixed. If it's a permanent design decision (e.g., all timestamps are normalized to a canonical timezone), update the comment to reflect that and remove the HACK label.

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

In `@packages/triage/src/util/ingestContent/index.ts` around lines 39 - 43, The
code forcibly sets createdAt to Asia/Singapore using
DateTime.fromISO(...).setZone('Asia/Singapore') and is labeled a "HACK"; replace
that ambiguous comment with an explicit decision: either add a TODO explaining
this is a temporary workaround with context and a ticket/owner reference (why
content.createdAt must be forced to that zone and when to remove it), or, if
this is intended as the canonical timezone, update the comment to state that
timestamps are normalized to 'Asia/Singapore' permanently and remove "HACK";
update the code comment near createdAt / DateTime.fromISO to reflect the chosen
option and include the unique symbols (createdAt, content.createdAt,
DateTime.fromISO, setZone) for traceability.
packages/triage/src/scripts/reExtractAndReplay.ts (1)

28-32: 💤 Low value

Extract duplicate sign-formatting logic into a helper.

The sign-formatting pattern (value >= 0 ? '+' : '')${value} is duplicated on lines 30 and 41. Consider extracting this into a local helper function to improve maintainability.

♻️ Proposed refactor
+function formatDiff(value: number): string {
+  return `${value >= 0 ? '+' : ''}${value}`;
+}
+
 for (const result of summary.results) {
   console.log(
-    `${result.issueId}: ${result.before} → ${result.after} events (${(result.diff ?? 0) >= 0 ? '+' : ''}${result.diff})`,
+    `${result.issueId}: ${result.before} → ${result.after} events (${formatDiff(result.diff ?? 0)})`,
   );
 }

 console.log(
-  `Total impact events: ${summary.totalBefore} → ${summary.totalAfter} (${summary.totalAfter - summary.totalBefore >= 0 ? '+' : ''}${summary.totalAfter - summary.totalBefore})`,
+  `Total impact events: ${summary.totalBefore} → ${summary.totalAfter} (${formatDiff(summary.totalAfter - summary.totalBefore)})`,
 );

Also applies to: 40-42

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

In `@packages/triage/src/scripts/reExtractAndReplay.ts` around lines 28 - 32, The
duplicate sign-formatting logic used for result.diff should be extracted into a
small helper (e.g., formatSigned or formatSignedNumber) that accepts a number
and returns a string with a '+' prefix for non-negative values and the raw
number otherwise; replace the inline expressions `(value >= 0 ? '+' :
'')${value}` (used when printing `${result.diff ?? 0}` in the loop over
summary.results and the second occurrence at lines 40-42) with calls to this
helper, and ensure the helper handles null/undefined by defaulting to 0.
packages/triage/src/llm/client.ts (1)

3-7: ⚡ Quick win

Consider validating the API key presence.

The function does not validate that OPENAI_API_KEY is set. While the OpenAI SDK will likely error on undefined, catching this early with a clear message improves the developer experience.

🛡️ Add explicit validation
 export function getOpenAiClient() {
+  const apiKey = process.env.OPENAI_API_KEY;
+  if (!apiKey) {
+    throw new Error('OPENAI_API_KEY environment variable is required');
+  }
   return new OpenAI({
-    apiKey: process.env.OPENAI_API_KEY,
+    apiKey,
   });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/triage/src/llm/client.ts` around lines 3 - 7, The getOpenAiClient
function doesn't check that process.env.OPENAI_API_KEY is present before
constructing a new OpenAI client; add an explicit validation at the start of
getOpenAiClient to verify OPENAI_API_KEY is set and throw (or return a clear
error) with a descriptive message (e.g., "OPENAI_API_KEY is not set") if
missing, then proceed to instantiate OpenAI with the validated key to fail fast
and provide a helpful developer message.
packages/triage/src/llm/common/MemoryStore.ts (1)

105-110: 💤 Low value

Minor: Redundant directory deletion on line 109.

The dirs Map only stores normalized paths (via toStorePath), so this.dirs.delete(path) on line 109 attempts to delete a potentially non-normalized key that was never added. Line 108 already removes the normalized path. This is harmless but redundant.

♻️ Simplify by removing the redundant operation
 delete(path: string): void {
   const p = this.toStorePath(path);
   this.files.delete(p);
   this.dirs.delete(p);
-  this.dirs.delete(path);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/triage/src/llm/common/MemoryStore.ts` around lines 105 - 110, The
delete method in MemoryStore unnecessarily calls this.dirs.delete(path) after
already deleting the normalized key; remove the redundant deletion so only the
normalized path (const p = this.toStorePath(path)) is used, i.e. keep
this.files.delete(p) and this.dirs.delete(p) and remove the extra
this.dirs.delete(path) in the delete(path: string) method that uses toStorePath.
packages/triage/src/llm/functions/translate/index.ts (1)

11-11: ⚡ Quick win

Add an explicit return type for the translation contract.

This helper is exported; returning an explicit typed result prevents accidental contract drift and improves downstream safety.

Proposed fix
+export type TranslateResult = z.infer<typeof TranslationsSchema>;
+
-export async function translate(text: string) {
+export async function translate(text: string): Promise<TranslateResult> {
@@
-  return response.output_parsed;
+  return response.output_parsed;
 }

Also applies to: 69-69

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

In `@packages/triage/src/llm/functions/translate/index.ts` at line 11, The
exported function translate currently lacks an explicit return type; add a clear
return type to its signature (e.g., Promise<string> or the actual translation
result type used in this module) to lock the translation contract and prevent
drift—update the signature of the exported translate function and any other
exported translate declaration in this file (the other occurrence around the
later definition) to return the same explicit type so callers and downstream
code have a stable, typed contract.
packages/fs/src/write/common/FileWriteStore.ts (1)

11-23: ⚖️ Poor tradeoff

No parent directory creation for write operations.

writeText and appendText will fail if parent directories don't exist. Callers must call ensureDir first, which is easy to forget and creates boilerplate.

Consider whether writeText, writeJson, and appendText should auto-create parent directories (similar to mkdir -p behavior). This would match common expectations and reduce caller burden. However, this is a design choice that affects the IWriteStore contract, so it may be intentional.

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

In `@packages/fs/src/write/common/FileWriteStore.ts` around lines 11 - 23,
writeText and appendText currently write directly to fullPath and will throw if
parent directories don't exist; update writeText, appendText (and let writeJson
continue to delegate to writeText) to ensure parent directories are created
first by calling mkdirSync(dirname(fullPath), { recursive: true }) (or the
existing ensureDir helper if present) before calling
writeFileSync/appendFileSync so writes behave like mkdir -p and callers don't
need to pre-create directories; refer to writeText, writeJson, appendText and
use dirname(fullPath) to compute the parent directory.
packages/fs/src/repo/common/StandardRepository.ts (1)

14-15: No mechanism to invalidate or reload cached data.

The loaded flag (line 15) prevents re-reading from disk once data is loaded. If the underlying files change during the process lifecycle, those changes won't be visible without restarting.

This is acceptable for CLI tools and build scripts that load data once. For long-running services, consider adding a reload() method or TTL-based invalidation if you need to pick up file changes without restarting the process.

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

In `@packages/fs/src/repo/common/StandardRepository.ts` around lines 14 - 15, The
repository caches data indefinitely using the protected Map byId and the boolean
loaded in StandardRepository, so file changes are never picked up; add a reload
mechanism (e.g., a public reload() method on StandardRepository) that clears
byId, sets loaded = false, and re-invokes the existing load logic or exposes a
way to force reload, or alternatively implement TTL-based invalidation (store a
lastLoaded timestamp and check it in getters to refresh when expired); update
methods that rely on loaded (e.g., any getAll/getById methods) to call
reload/refresh when needed.
packages/fs/src/write/common/store.ts (2)

1-32: 💤 Low value

Incomplete error handling specification.

Similar to IStore, this interface doesn't document error behavior for:

  • Write failures (permissions, disk full, path doesn't exist)
  • Whether writeJson and writeText create parent directories
  • Whether appendText creates the file if it doesn't exist
  • Whether delete should fail silently or throw if path doesn't exist

Add JSDoc comments specifying the error contract so implementations remain consistent.

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

In `@packages/fs/src/write/common/store.ts` around lines 1 - 32, The IWriteStore
interface lacks error/behavior contracts: update the JSDoc for IWriteStore and
each method (writeText, writeJson, appendText, ensureDir, delete) to state which
errors are thrown (e.g., permission, ENOSPC, ENOENT), whether parent directories
are created by writeText/writeJson (specify that implementations MUST create
parents or MUST throw), whether appendText will create the file if missing, and
the expected delete behavior (throw if path missing vs no-op). Reference the
interface name IWriteStore and each method name in the comments so
implementations follow the same error/creation semantics.

31-31: ⚡ Quick win

Optional delete method may cause inconsistent behavior.

The delete method is optional (delete?:), meaning some implementations may not support deletion. Callers must check for existence before calling, which is error-prone.

If deletion is a core requirement, make it mandatory. If it's truly optional (e.g., for immutable stores), document when and why implementations can omit it. Otherwise, callers may assume it exists and fail at runtime.

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

In `@packages/fs/src/write/common/store.ts` at line 31, The optional delete?
declaration on the store interface should be made mandatory to avoid runtime
errors: change the signature from delete?(path: string): void to delete(path:
string): void in the store interface (the symbol to modify is the delete method
on the Store interface in store.ts) so all implementations must implement
deletion; if you prefer optional behavior instead, alternatively add clear docs
to the Store interface explaining when implementations may omit delete and add
runtime guards where callers use store.delete to check existence before calling.
packages/fs/src/repo/common/FileStore.ts (1)

12-29: 💤 Low value

Consider documenting error behavior.

The methods don't handle errors from Node.js fs operations (readFileSync, readdirSync). Callers need to know whether to expect exceptions for:

  • Missing files/directories
  • Permission errors
  • Invalid paths

Either document that all errors propagate as-thrown from Node.js fs APIs, or wrap them with consistent error messages including the attempted path. The current behavior is functional but may surprise callers expecting null/undefined returns for missing files.

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

In `@packages/fs/src/repo/common/FileStore.ts` around lines 12 - 29, The FileStore
methods (readText, readJson, listDir, exists) currently let Node fs errors
bubble up without context; update each to catch filesystem exceptions and
rethrow a consistent, informative error that includes the attempted full path
(join(this.rootDir, path)) and the original error as the cause or message so
callers can distinguish missing files, permission issues, or malformed JSON;
specifically wrap errors thrown in readText (and propagated from readJson),
listDir, and exists with a descriptive message like "FileStore.<method> failed
for <fullPath>" while preserving the original error object.
packages/fs/src/repo/common/store.ts (1)

10-22: Consider async alternatives for production use.

All methods are synchronous, which will block the Node.js event loop during I/O operations. For production services handling concurrent requests, this can significantly degrade throughput and responsiveness.

If this code will be used in request-handling paths or concurrent operations, consider defining an async variant (e.g., IAsyncStore) with Promise-returning methods. The current synchronous interface is acceptable for CLI tools, build scripts, and single-threaded batch processing.

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

In `@packages/fs/src/repo/common/store.ts` around lines 10 - 22, The current Store
interface exposes synchronous methods (readText, readJson, listDir) which block
the Node.js event loop; add an asynchronous variant (e.g., IAsyncStore) that
mirrors these signatures but returns Promises (Promise<string>, Promise<T>,
Promise<string[]>) and update implementations to provide both sync and async
methods or migrate call sites to the async interface; ensure types and any
factory/constructor (store creation code) are updated to accept/return
IAsyncStore where used in request-handling or concurrent contexts so production
paths use non-blocking I/O.
packages/fs/src/write/issue/IssueWriter.ts (1)

43-50: ⚡ Quick win

The validation in getIssueDir() should enforce the complete issue ID format for consistency.

The current regex only validates the date prefix (YYYY-MM-DD), leaving the rest of the issueId unchecked. While the practical risk is limited—Node.js path.join() normalizes path separators and .. segments, and all current callers pass pre-validated Issue.id objects—stricter validation improves defensive programming and prevents accidental misuse.

Consider updating the regex to match the full pattern defined in IssueIdSchema: /^(\d{4})-(\d{2})-(\d{2})-[a-z0-9]+(?:-[a-z0-9]+)*$/, ensuring both date validity and slug format constraints are enforced consistently at the point of use.

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

In `@packages/fs/src/write/issue/IssueWriter.ts` around lines 43 - 50, getIssueDir
currently only validates the date prefix of issueId which allows malformed IDs;
update the validation in getIssueDir to use the full IssueId pattern (e.g.
/^(\d{4})-(\d{2})-(\d{2})-[a-z0-9]+(?:-[a-z0-9]+)*$/) so it matches the complete
YYYY-MM-DD-slug format, throw the same error when it fails, and keep extracting
year/month from the capture groups as before; modify the regex used in
getIssueDir to this full pattern to enforce slug constraints consistently with
IssueIdSchema.
packages/fs/src/repo/issue/IssueRepository.ts (1)

199-205: 💤 Low value

Optional refactor: avoid redundant get() call.

The loop retrieves bundles by calling this.get(result.item.issue.id), but result.item is already the full IssueBundle from this.list(). The subsequent get() call will hit the cache but is unnecessary.

♻️ Simplify by using result.item directly
-    const results: IssueBundle[] = [];
-    for (const result of searchResults) {
-      const bundle = this.get(result.item.issue.id);
-      if (bundle == null) {
-        continue;
-      }
-      results.push(bundle);
-    }
-    return results;
+    return searchResults.map((result) => result.item);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/fs/src/repo/issue/IssueRepository.ts` around lines 199 - 205, The
loop in IssueRepository.ts unnecessarily calls this.get(result.item.issue.id)
even though result.item already contains the IssueBundle from
list()/searchResults; replace the redundant cache lookup by pushing result.item
directly into results (i.e., use result.item instead of calling get()), ensuring
the pushed value matches the expected IssueBundle type and preserving any
existing null checks or filtering logic around result.item.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/fs/src/repo/common/FileStore.ts`:
- Around line 17-19: The readJson<T>(path: string) method currently calls
JSON.parse(this.readText(path)) which will throw a SyntaxError without file
context; modify readJson to wrap the parse in a try/catch, call
this.readText(path) first, then try JSON.parse on that string, and on failure
throw a new Error (or rethrow a custom error) that includes the path and the
original error message/stack (e.g., "Failed to parse JSON at {path}:
{originalError.message}") so callers can see which file caused the parse
failure; reference the readJson function in FileStore.ts and reuse
readText(path) as the source string.

In `@packages/fs/src/repo/common/store.ts`:
- Around line 4-29: Update the IStore interface JSDoc: for each method
(readText, readJson, listDir, exists) add clear `@returns` descriptions (e.g.,
readText returns file contents as a UTF-8 string, listDir returns non-dot entry
names array, exists returns boolean) and a clear `@throws/`@behavior clause that
defines error semantics (specify whether implementations MUST throw on missing
paths/invalid paths, MUST throw on I/O errors/JSON parse errors, or return a
sentinel like null — pick one policy and document it consistently). For
readJson<T> explicitly document that JSON parse errors will be thrown and that
callers are responsible for runtime validation of T (or change the signature to
return T | null/unknown if you prefer safe parsing) and mention which specific
error types (e.g., SyntaxError, FileNotFound) implementations should surface;
apply these docs to IStore so all implementations follow the same error-handling
contract.

In `@packages/fs/src/write/common/FileWriteStore.ts`:
- Around line 30-33: The delete method in FileWriteStore (delete(path: string))
currently calls rmSync(fullPath) which will throw for non-empty directories or
missing paths; update the call to rmSync to pass options { recursive: true,
force: true } so non-empty directories are removed and missing files are
ignored—locate the delete method, the fullPath variable (built with
join(this.rootDir, path)), and change the rmSync invocation accordingly.

In `@packages/fs/src/write/common/StandardWriter.ts`:
- Around line 17-20: The create method in StandardWriter currently builds a file
path from item.id without validation, allowing directory traversal; update
StandardWriter.create to validate or sanitize item.id (e.g., reject or normalize
values containing path separators, '.' or '..', or enforce the same ID
validation used by writeUnknownEntity) before calling
this.store.writeJson(join(this.dirPath, `${item.id}.json`), item); if the ID is
invalid throw a clear error, or use a safe basename/sanitization routine so
writes cannot escape this.dirPath.

In `@packages/triage/README.md`:
- Around line 26-28: The README and code reference non-public model IDs
(`gpt-5.4-mini`, `gpt-5-mini`, `gpt-5-nano`) used by
extractClaimsFromNewEvidence, triageNewEvidence, and the title/slug translation
routines; search the codebase for these exact strings, confirm whether they were
placeholders or internal names, and replace them with valid public GPT‑5 IDs
(e.g., `gpt-5.4`, `gpt-5.4-pro`, `gpt-5.5`, or `gpt-5.5-pro`) according to
desired capability/latency; update the README entries to match the chosen
official model names and ensure any code that constructs OpenAI requests (the
functions extractClaimsFromNewEvidence, triageNewEvidence, and the title/slug
translation functions) uses the same validated identifiers to avoid runtime
failures.

In `@packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts`:
- Around line 118-142: The status-signal reconciliation inside the 'disruption'
branch is dead/unused: it computes currentStatus (using
currentServiceState.periods and ClaimStatusSignal) and compares it to
claim.statusSignal but the inner switch on claim.statusSignal has empty cases;
either implement the reconciliation logic or remove it — simplest fix is to
delete the comparison and empty switch (the currentStatus variable and the if
(currentStatus !== claim.statusSignal) { ... } block) from the
computeImpactFromEvidenceClaims flow to eliminate dead code and keep behavior
unchanged.

In `@packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts`:
- Around line 126-135: The tool-call cap check currently uses ">" and then still
falls through to call tool.runner, so it does not prevent extra calls; update
the conditional around toolCallCount and TOOL_CALL_LIMIT in the block that sends
the 'function_call_output' (and the similar block later) to use ">=" (or
equivalently check toolCallCount >= TOOL_CALL_LIMIT) and ensure you
short-circuit after pushing the error output by returning/continuing (i.e., do
not invoke tool.runner) so no further tool.runner calls are made for that item
(refer to toolCallCount, TOOL_CALL_LIMIT, tool.runner, context.push and call_id
to locate both affected blocks).

In
`@packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts`:
- Around line 74-83: The filter for relevantStationCodes in FindStationsTool
currently parses startedAt with validation but uses membership.endedAt without
checking DateTime validity and uses an inclusive boundary; update the
membership.endedAt handling to mirror FindServicesTool by: when
membership.endedAt is not null parse it with DateTime.fromISO, assert
endedAt.isValid with a clear message, and change the exclusion check to if
(endedAt <= this.evidenceTs) return false so memberships that end at or before
evidenceTs are excluded; leave the startedAt logic as-is.

In `@packages/triage/src/llm/functions/translate/index.ts`:
- Line 12: Remove the raw input log call console.log('[translate] Translating
text:', text) so full payloads (variable text) are not written to logs; instead,
either delete the line or replace it with a safe log that records only
non-sensitive metadata (e.g., indicate the translate function ran and include
text length or a stable hash) and keep references to the translate routine and
the text variable for context.

In `@packages/triage/src/llm/functions/triageNewEvidence/index.ts`:
- Around line 114-123: The tool-call limit check (comparisons using
toolCallCount and TOOL_CALL_LIMIT) currently only pushes an error output into
context but does not stop execution of the tool runner; modify the control flow
in the triageNewEvidence function so that when toolCallCount > TOOL_CALL_LIMIT
you immediately skip/return before invoking the tool runner (the block that runs
the tool and produces further outputs), ensuring no further tool execution or
token usage; apply the same early-exit or skip logic to the other occurrence
around the second block (the section referenced in lines 129-140) so both places
prevent any tool invocation after the limit is reached.
- Around line 128-131: The code currently does raw JSON.parse(item.arguments),
tool.parseParams(...) and await tool.runner(params) which can throw and abort
the triage flow; wrap these steps in a try/catch that guards JSON.parse,
tool.parseParams, and tool.runner calls (references: item.arguments,
tool.parseParams, tool.runner, params) and on any error return a recoverable
tool-call output object (e.g., an error result/placeholder) instead of throwing
so the triage flow continues; ensure the catch logs the error/context and
produces the same shape the caller expects for a tool result.
- Around line 19-31: The discriminated union for result uses
z.discriminatedUnion('type', ...) but each branch uses the discriminator field
named kind, causing runtime mismatch; change the discriminator to 'kind' in the
z.discriminatedUnion call so it matches the branch objects (the schema under
result that references IssueIdSchema and IssueTypeSchema), then run tests and
ensure downstream consumers like ingestContent still read the kind field as
expected.

In `@packages/triage/src/scripts/ingestViaWebhook.ts`:
- Around line 5-10: The code trusts the MESSAGE env JSON and asserts a static
type for content (IngestContent[]) without runtime validation; add runtime
schema validation using Zod: define IngestContentSchema (discriminated union for
'twitter'/'mastodon', 'reddit', 'news-website') and MessageSchema in types.ts,
then parse JSON from process.env.MESSAGE and validate it against MessageSchema
before calling ingestContent; on validation failure log the validation errors
and exit (or throw) to avoid passing malformed data into ingestContent. Ensure
you reference MESSAGE, IngestContent/IngestContentSchema, MessageSchema, and
ingestContent when adding the validation and error handling.

In `@packages/triage/src/scripts/reExtractAndReplay.ts`:
- Around line 9-15: The call to reExtractAndReplay is not wrapped in error
handling; wrap the await reExtractAndReplay({...}) invocation in a try-catch
block so any thrown errors are caught, log the error (e.g., console.error or
your process logger) with context including that reExtractAndReplay failed and
the input args, and call process.exit(1) in the catch to ensure the script
terminates with a non-zero status; ensure you still return or handle the summary
on success inside the try.

In `@packages/triage/src/scripts/reExtractAndReplayTargets.ts`:
- Around line 51-67: The parser currently only checks for null when reading the
next token after '--issue' and '--evidence', allowing another flag (e.g.,
'--dry-run') to be consumed as an ID; update the validation in the argument
handling for '--issue' and '--evidence' (the branches that use argv[index + 1]
and then call issueIds.add/evidenceIds.add) to also reject tokens that look like
flags (e.g., start with '-' or '--') and throw a "Missing value for
--issue"/"Missing value for --evidence" error when the next token is absent or
is a flag, leaving index unchanged in that error path.

In `@packages/triage/src/scripts/replayImpactEvents.ts`:
- Line 6: The call to replayImpactEvents({ dataDir: DATA_DIR }) should be
wrapped in error handling: wrap the invocation (and await it if
replayImpactEvents returns a Promise) in a try-catch, assign the result to
summary inside the try block, and in the catch log the error (including
error.message/stack) and exit with a non-zero code; reference the
replayImpactEvents call, the summary variable and DATA_DIR when making these
changes.

In `@packages/triage/src/util/ingestContent/index.ts`:
- Around line 149-152: Wrap the writer operations in try/catch blocks to surface
and handle disk I/O failures: enclose writer.issues.create(issue) (around line
109) and the subsequent writer.issues.appendEvidence(issueBundle.issue.id,
evidence) and looped writer.issues.appendImpact(issueBundle.issue.id, impact)
calls in a single try so partial writes are detected; on catch, log the error
with context (including issue id) and either cleanup the partial state (call any
available writer.issues.delete(issue.id) or writer rollback API) or rethrow a
wrapped error to abort ingestion so callers can handle retries/cleanup. Ensure
the catch includes the original error details in the log/exception.
- Around line 49-71: Wrap the LLM calls (triageNewEvidence and
extractClaimsFromNewEvidence, plus any other functions that invoke LLMs in this
module) in try/catch blocks: catch errors from the LLM calls, log a clear error
message including the caught error (use existing logger/console), and return a
safe value (e.g., null) or short-circuit the ingestion flow rather than letting
the exception bubble. Ensure you apply this pattern around the existing
triageNewEvidence and extractClaimsFromNewEvidence calls and replicate it for
the other LLM-invoking calls in this file so failures are handled gracefully and
the function returns a consistent result.

---

Nitpick comments:
In `@packages/fs/src/repo/common/FileStore.ts`:
- Around line 12-29: The FileStore methods (readText, readJson, listDir, exists)
currently let Node fs errors bubble up without context; update each to catch
filesystem exceptions and rethrow a consistent, informative error that includes
the attempted full path (join(this.rootDir, path)) and the original error as the
cause or message so callers can distinguish missing files, permission issues, or
malformed JSON; specifically wrap errors thrown in readText (and propagated from
readJson), listDir, and exists with a descriptive message like
"FileStore.<method> failed for <fullPath>" while preserving the original error
object.

In `@packages/fs/src/repo/common/StandardRepository.ts`:
- Around line 14-15: The repository caches data indefinitely using the protected
Map byId and the boolean loaded in StandardRepository, so file changes are never
picked up; add a reload mechanism (e.g., a public reload() method on
StandardRepository) that clears byId, sets loaded = false, and re-invokes the
existing load logic or exposes a way to force reload, or alternatively implement
TTL-based invalidation (store a lastLoaded timestamp and check it in getters to
refresh when expired); update methods that rely on loaded (e.g., any
getAll/getById methods) to call reload/refresh when needed.

In `@packages/fs/src/repo/common/store.ts`:
- Around line 10-22: The current Store interface exposes synchronous methods
(readText, readJson, listDir) which block the Node.js event loop; add an
asynchronous variant (e.g., IAsyncStore) that mirrors these signatures but
returns Promises (Promise<string>, Promise<T>, Promise<string[]>) and update
implementations to provide both sync and async methods or migrate call sites to
the async interface; ensure types and any factory/constructor (store creation
code) are updated to accept/return IAsyncStore where used in request-handling or
concurrent contexts so production paths use non-blocking I/O.

In `@packages/fs/src/repo/issue/IssueRepository.ts`:
- Around line 199-205: The loop in IssueRepository.ts unnecessarily calls
this.get(result.item.issue.id) even though result.item already contains the
IssueBundle from list()/searchResults; replace the redundant cache lookup by
pushing result.item directly into results (i.e., use result.item instead of
calling get()), ensuring the pushed value matches the expected IssueBundle type
and preserving any existing null checks or filtering logic around result.item.

In `@packages/fs/src/write/common/FileWriteStore.ts`:
- Around line 11-23: writeText and appendText currently write directly to
fullPath and will throw if parent directories don't exist; update writeText,
appendText (and let writeJson continue to delegate to writeText) to ensure
parent directories are created first by calling mkdirSync(dirname(fullPath), {
recursive: true }) (or the existing ensureDir helper if present) before calling
writeFileSync/appendFileSync so writes behave like mkdir -p and callers don't
need to pre-create directories; refer to writeText, writeJson, appendText and
use dirname(fullPath) to compute the parent directory.

In `@packages/fs/src/write/common/store.ts`:
- Around line 1-32: The IWriteStore interface lacks error/behavior contracts:
update the JSDoc for IWriteStore and each method (writeText, writeJson,
appendText, ensureDir, delete) to state which errors are thrown (e.g.,
permission, ENOSPC, ENOENT), whether parent directories are created by
writeText/writeJson (specify that implementations MUST create parents or MUST
throw), whether appendText will create the file if missing, and the expected
delete behavior (throw if path missing vs no-op). Reference the interface name
IWriteStore and each method name in the comments so implementations follow the
same error/creation semantics.
- Line 31: The optional delete? declaration on the store interface should be
made mandatory to avoid runtime errors: change the signature from delete?(path:
string): void to delete(path: string): void in the store interface (the symbol
to modify is the delete method on the Store interface in store.ts) so all
implementations must implement deletion; if you prefer optional behavior
instead, alternatively add clear docs to the Store interface explaining when
implementations may omit delete and add runtime guards where callers use
store.delete to check existence before calling.

In `@packages/fs/src/write/issue/IssueWriter.ts`:
- Around line 43-50: getIssueDir currently only validates the date prefix of
issueId which allows malformed IDs; update the validation in getIssueDir to use
the full IssueId pattern (e.g.
/^(\d{4})-(\d{2})-(\d{2})-[a-z0-9]+(?:-[a-z0-9]+)*$/) so it matches the complete
YYYY-MM-DD-slug format, throw the same error when it fails, and keep extracting
year/month from the capture groups as before; modify the regex used in
getIssueDir to this full pattern to enforce slug constraints consistently with
IssueIdSchema.

In `@packages/triage/src/helpers/deriveCurrentState.ts`:
- Around line 65-77: Update the JSDoc for deriveCurrentState to explicitly state
that impactEventIds (the impactEventIds object in deriveCurrentState) records
only the most recent event ID per category (serviceEffect, serviceScopes,
periods, causes, facilityEffect) rather than a list of all event IDs; mention
that each property holds the latest event id used to derive state so callers
understand overwrite behavior of impactEventIds.
- Around line 168-171: Remove the suppression comment on the switch default in
deriveCurrentState (the line with "// `@ts-expect-error` fallback case");
specifically delete that `@ts-expect-error` so TypeScript will enforce
exhaustiveness for impactEvent.entity.type in the switch and surface compile
errors if new AffectedEntity variants are added; keep the existing default
behavior (throw new Error(`Unknown entity type: ${impactEvent.entity.type}`)) so
the runtime fallback remains while the compiler forces you to add missing cases.

In `@packages/triage/src/helpers/splitIntervalByServiceHours.ts`:
- Around line 21-24: Extract the hard-coded service start time (05:30) into a
named module-level constant (e.g., SERVICE_START_TIME or SERVICE_HOURS_START)
and use that constant instead of inline literals in the DateTime.set call used
when computing segmentStart (the DateTime.max(start, start.set(...)) call).
Define the constant as an object or DateTime-friendly structure so it can be
reused anywhere in this module (and updated centrally), and replace any other
hard-coded service hour literals in this file to reference the new constant.

In `@packages/triage/src/llm/client.ts`:
- Around line 3-7: The getOpenAiClient function doesn't check that
process.env.OPENAI_API_KEY is present before constructing a new OpenAI client;
add an explicit validation at the start of getOpenAiClient to verify
OPENAI_API_KEY is set and throw (or return a clear error) with a descriptive
message (e.g., "OPENAI_API_KEY is not set") if missing, then proceed to
instantiate OpenAI with the validated key to fail fast and provide a helpful
developer message.

In `@packages/triage/src/llm/common/MemoryStore.ts`:
- Around line 105-110: The delete method in MemoryStore unnecessarily calls
this.dirs.delete(path) after already deleting the normalized key; remove the
redundant deletion so only the normalized path (const p =
this.toStorePath(path)) is used, i.e. keep this.files.delete(p) and
this.dirs.delete(p) and remove the extra this.dirs.delete(path) in the
delete(path: string) method that uses toStorePath.

In `@packages/triage/src/llm/functions/translate/index.ts`:
- Line 11: The exported function translate currently lacks an explicit return
type; add a clear return type to its signature (e.g., Promise<string> or the
actual translation result type used in this module) to lock the translation
contract and prevent drift—update the signature of the exported translate
function and any other exported translate declaration in this file (the other
occurrence around the later definition) to return the same explicit type so
callers and downstream code have a stable, typed contract.

In `@packages/triage/src/scripts/reExtractAndReplay.ts`:
- Around line 28-32: The duplicate sign-formatting logic used for result.diff
should be extracted into a small helper (e.g., formatSigned or
formatSignedNumber) that accepts a number and returns a string with a '+' prefix
for non-negative values and the raw number otherwise; replace the inline
expressions `(value >= 0 ? '+' : '')${value}` (used when printing `${result.diff
?? 0}` in the loop over summary.results and the second occurrence at lines
40-42) with calls to this helper, and ensure the helper handles null/undefined
by defaulting to 0.

In `@packages/triage/src/util/ingestContent/index.ts`:
- Line 106: The literal model string '@openai/gpt-5-nano' is duplicated; extract
it to a single module-level constant (e.g., const DEFAULT_EMBEDDING_MODEL =
'@openai/gpt-5-nano') and replace both hardcoded usages with that constant (the
two occurrences around lines where source is set and the second usage). If this
constant should be package-wide, export it so other modules can reuse it; update
any references in functions that build embeddings or set the source to use
DEFAULT_EMBEDDING_MODEL to ensure consistency.
- Around line 39-43: The code forcibly sets createdAt to Asia/Singapore using
DateTime.fromISO(...).setZone('Asia/Singapore') and is labeled a "HACK"; replace
that ambiguous comment with an explicit decision: either add a TODO explaining
this is a temporary workaround with context and a ticket/owner reference (why
content.createdAt must be forced to that zone and when to remove it), or, if
this is intended as the canonical timezone, update the comment to state that
timestamps are normalized to 'Asia/Singapore' permanently and remove "HACK";
update the code comment near createdAt / DateTime.fromISO to reflect the chosen
option and include the unique symbols (createdAt, content.createdAt,
DateTime.fromISO, setZone) for traceability.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a6eecfff-3854-46c1-b68a-073ab86b5f2d

📥 Commits

Reviewing files that changed from the base of the PR and between ae5fa0f and 91ace8b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (84)
  • .github/workflows/validate.yml
  • AGENTS.md
  • README.md
  • docs/DATA_OVERHAUL_SPLIT.md
  • package.json
  • packages/fs/package.json
  • packages/fs/src/constants.ts
  • packages/fs/src/index.ts
  • packages/fs/src/repo/MRTDownRepository.ts
  • packages/fs/src/repo/common/FileStore.ts
  • packages/fs/src/repo/common/StandardRepository.ts
  • packages/fs/src/repo/common/dirEntries.ts
  • packages/fs/src/repo/common/store.ts
  • packages/fs/src/repo/issue/IssueRepository.ts
  • packages/fs/src/repo/landmark/LandmarkRepository.ts
  • packages/fs/src/repo/line/LineRepository.ts
  • packages/fs/src/repo/operator/OperatorRepository.ts
  • packages/fs/src/repo/service/ServiceRepository.ts
  • packages/fs/src/repo/station/StationRepository.ts
  • packages/fs/src/repo/town/TownRepository.ts
  • packages/fs/src/write/MRTDownWriter.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/fs/src/write/common/store.ts
  • packages/fs/src/write/id/IdGenerator.ts
  • packages/fs/src/write/issue/IssueWriter.ts
  • packages/triage/CHANGELOG.md
  • packages/triage/README.md
  • packages/triage/biome.json
  • packages/triage/package.json
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.test.ts
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.test.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.test.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.ts
  • packages/triage/src/helpers/deriveCurrentState.test.ts
  • packages/triage/src/helpers/deriveCurrentState.ts
  • packages/triage/src/helpers/estimateOpenAICost.ts
  • packages/triage/src/helpers/keyForAffectedEntity.ts
  • packages/triage/src/helpers/reconstructClaimsFromImpactEvents.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.test.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.ts
  • packages/triage/src/helpers/sumIntervalDuration.ts
  • packages/triage/src/index.ts
  • packages/triage/src/llm/client.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/triage/src/llm/common/MemoryStore.ts
  • packages/triage/src/llm/common/formatCurrentState.ts
  • packages/triage/src/llm/common/tool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/degradedServiceHeuristics.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/prompt.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/index.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/prompt.ts
  • packages/triage/src/llm/functions/translate/index.ts
  • packages/triage/src/llm/functions/triageNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/triageNewEvidence/index.ts
  • packages/triage/src/llm/functions/triageNewEvidence/prompt.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/FindIssuesTool.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/GetIssueTool.ts
  • packages/triage/src/maintenance/reExtractAndReplay.ts
  • packages/triage/src/maintenance/replayImpactEvents.ts
  • packages/triage/src/scripts/ingestViaWebhook.ts
  • packages/triage/src/scripts/reExtractAndReplay.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.ts
  • packages/triage/src/scripts/replayImpactEvents.ts
  • packages/triage/src/util/assert.ts
  • packages/triage/src/util/ingestContent/helpers/getSlugDateTimeFromClaims.ts
  • packages/triage/src/util/ingestContent/index.ts
  • packages/triage/src/util/ingestContent/types.ts
  • packages/triage/tsconfig.json
  • packages/triage/vitest.config.ts
  • packages/triage/vitest.eval.config.ts
  • vitest.config.ts

Comment thread packages/fs/src/repo/common/FileStore.ts
Comment thread packages/fs/src/repo/common/store.ts
Comment thread packages/fs/src/write/common/FileWriteStore.ts
Comment thread packages/fs/src/write/common/StandardWriter.ts
Comment thread packages/triage/README.md Outdated
Comment thread packages/triage/src/scripts/reExtractAndReplay.ts Outdated
Comment thread packages/triage/src/scripts/reExtractAndReplayTargets.ts
Comment thread packages/triage/src/scripts/replayImpactEvents.ts Outdated
Comment thread packages/triage/src/util/ingestContent/index.ts Outdated
Comment thread packages/triage/src/util/ingestContent/index.ts Outdated
@duncanleo
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/triage/src/helpers/estimateOpenAICost.ts (1)

99-104: 💤 Low value

Consider documenting the clamping behavior.

The defensive clamping ensures all token counts are non-negative, which is good practice. However, if cachedInputTokens exceeds inputTokens (invalid API data), the calculation silently produces uncachedInputTokens = 0 rather than detecting the inconsistency. While this scenario is unlikely with official API responses, a brief comment explaining the clamping strategy could help future maintainers understand the trade-off.

📝 Optional documentation improvement
+ // Defensive clamping: ensures non-negative values and handles edge case
+ // where cachedInputTokens might exceed inputTokens (degrades to 0 uncached)
  const cachedInputTokens = Math.max(usage.cachedInputTokens, 0);
  const uncachedInputTokens = Math.max(
    usage.inputTokens - cachedInputTokens,
    0,
  );
  const outputTokens = Math.max(usage.outputTokens, 0);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/triage/src/helpers/estimateOpenAICost.ts` around lines 99 - 104,
Document the defensive clamping around token counts: add a brief inline comment
above the calculations for cachedInputTokens, uncachedInputTokens, and
outputTokens in estimateOpenAICost explaining that Math.max(...) is used to
force non-negative token counts, that if usage.cachedInputTokens >
usage.inputTokens we intentionally clamp uncachedInputTokens to 0 (treating the
API data as inconsistent but safe) rather than throwing, and note the trade-off
(silently masking API inconsistencies vs. avoiding exceptions). Reference the
variables cachedInputTokens, uncachedInputTokens, usage.inputTokens and
usage.cachedInputTokens in the comment so future maintainers understand the
rationale.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts`:
- Around line 35-52: computeImpactFromEvidenceClaims builds an empty
result.newState but then derives the current bundle state via
deriveCurrentState(params.issueBundle) — replace or initialize result.newState
with that derived snapshot (or a shallow clone) so untouched
services/facilities/provenance are preserved, then ensure you append any IDs
from newImpactEvents to result.newState.impactEventIds (or merge the emitted
event ids into the existing array) before returning; update references in
computeImpactFromEvidenceClaims to use deriveCurrentState, newImpactEvents, and
result.newState to produce a true post-reconciliation snapshot rather than an
empty state.
- Around line 61-62: The loop over claimsByAffectedEntity currently uses
claims.at(-1) and thus discards earlier non-null fields; update the logic in
computeImpactFromEvidenceClaims so that for each [key, claims] you produce a
mergedClaim by reducing the array and for each property (e.g., effect, scopes,
causes, timeHints, and other claim fields) choose the most recent
non-null/non-undefined value rather than the trailing object as a whole, then
use mergedClaim in the downstream logic instead of claim; alternatively
implement a helper like mergeClaims(claims) called in the for loop to centralize
this per-field latest-non-null merge.
- Around line 442-459: mergeFixedEndAt currently closes a period if either side
has endAt = null; change it to preserve open-endedness by returning null when
either currentEndAt or nextEndAt is null, otherwise return the later (max) end
string; for fixedPeriodsOverlap, treat null endAt as "infinite" when comparing
ranges instead of using left.startAt <= right.startAt when either end is null —
compute effective ends (e.g., leftEnd = left.endAt ?? Infinity, rightEnd =
right.endAt ?? Infinity) and test overlap using the standard interval overlap
check (left.startAt <= rightEnd && right.startAt <= leftEnd) so open-ended
periods don’t spuriously merge or close.

In `@packages/triage/src/util/ingestContent/index.ts`:
- Around line 143-151: The issue: writer.issues.create(issue) is called too
early and can leave orphaned issues if later steps
(translation/impact/append/persist) fail; move the creation into the final
persistence block (the code that appends evidence/impact and persists the
completed issue) so the issue is only persisted after all downstream steps
succeed, or implement a rollback path that deletes the issue on any downstream
exception; update all early create calls (the current
writer.issues.create(issue) call and the similar create usages around the
append/persist blocks) to either be deferred until after append/persist
completes (inside the final persistence block) or wrapped in a try/catch that on
error calls writer.issues.delete(issue.id) to undo the premature creation.

---

Nitpick comments:
In `@packages/triage/src/helpers/estimateOpenAICost.ts`:
- Around line 99-104: Document the defensive clamping around token counts: add a
brief inline comment above the calculations for cachedInputTokens,
uncachedInputTokens, and outputTokens in estimateOpenAICost explaining that
Math.max(...) is used to force non-negative token counts, that if
usage.cachedInputTokens > usage.inputTokens we intentionally clamp
uncachedInputTokens to 0 (treating the API data as inconsistent but safe) rather
than throwing, and note the trade-off (silently masking API inconsistencies vs.
avoiding exceptions). Reference the variables cachedInputTokens,
uncachedInputTokens, usage.inputTokens and usage.cachedInputTokens in the
comment so future maintainers understand the rationale.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1738505a-f7fa-4952-91a4-f3c79e61dae0

📥 Commits

Reviewing files that changed from the base of the PR and between 91ace8b and e43470d.

📒 Files selected for processing (21)
  • packages/fs/src/index.test.ts
  • packages/fs/src/repo/common/FileStore.ts
  • packages/fs/src/repo/common/store.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/triage/README.md
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
  • packages/triage/src/helpers/estimateOpenAICost.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/triage/src/llm/common/MemoryStore.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts
  • packages/triage/src/llm/functions/translate/index.ts
  • packages/triage/src/llm/functions/triageNewEvidence/index.ts
  • packages/triage/src/scripts/ingestViaWebhook.ts
  • packages/triage/src/scripts/reExtractAndReplay.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.ts
  • packages/triage/src/scripts/replayImpactEvents.ts
  • packages/triage/src/util/ingestContent/index.ts
  • packages/triage/src/util/ingestContent/types.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/triage/README.md
🚧 Files skipped from review as they are similar to previous changes (16)
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/fs/src/repo/common/FileStore.ts
  • packages/triage/src/llm/functions/translate/index.ts
  • packages/triage/src/scripts/ingestViaWebhook.ts
  • packages/triage/src/scripts/replayImpactEvents.ts
  • packages/fs/src/repo/common/store.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/triage/src/util/ingestContent/types.ts
  • packages/triage/src/scripts/reExtractAndReplay.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts
  • packages/triage/src/llm/common/MemoryStore.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.ts
  • packages/triage/src/llm/functions/triageNewEvidence/index.ts

Comment thread packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts Outdated
Comment thread packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts Outdated
Comment thread packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
Comment thread packages/triage/src/util/ingestContent/index.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In `@packages/fs/src/write/issue/IssueWriter.ts`:
- Around line 47-54: The getIssueDir currently trusts the raw issueId and can be
abused for path traversal; change getIssueDir to parse the date prefix and a
separate slug (e.g. /^(\d{4})-(\d{2})-(\d{2})(?:-(.+))?$/), validate the slug
part against a safe pattern (allow only alphanumerics, dots, underscores,
hyphens and reject any '..' or path separators), and use only the
validated/sanitized slug (or the date-only id if no slug) when calling
join(DIR_ISSUE, year, month, safeIssueId); apply the same
validation/sanitization wherever getIssueDir is used (creation/deletion
routines) so inputs like "2025-01-15-../../etc" are rejected instead of being
joined into filesystem paths.

In `@packages/triage/src/util/ingestContent/index.ts`:
- Around line 191-215: The existing-issue path lacks rollback on partial
appends: when shouldCreateIssue is false and writer.issues.appendEvidence
succeeds but writer.issues.appendImpact throws, the code leaves partial state;
update the catch to undo partial changes for the existing-issue branch by
tracking what was appended and attempting best-effort removals (e.g., call
writer.issues.removeEvidence and writer.issues.removeImpact for each appended
impact) or, better, replace the two-step logic with a single transactional/batch
API (e.g., add or call writer.issues.appendEvidenceAndImpacts) so appendEvidence
+ appendImpact are atomic; reference shouldCreateIssue, writer.issues.create,
writer.issues.appendEvidence, writer.issues.appendImpact, writer.issues.delete,
writer.issues.removeEvidence/removeImpact (or new appendEvidenceAndImpacts) and
newImpactEvents/computeImpactFromEvidenceClaims to ensure symmetry with the
new-issue rollback.
- Around line 247-260: getEvidenceType currently returns 'statement.official'
for 'reddit', which misclassifies user-generated posts; update the function so
'reddit' returns 'report.public' to match 'twitter' and 'mastodon' (function
name: getEvidenceType, symbol: content.source). If the intent is to treat only
operator-run subreddits as official, instead implement a conditional in
getEvidenceType that inspects content.subreddit against an operatorSubreddits
list (or similar upstream flag on IngestContent) and return 'statement.official'
only for those matches, otherwise return 'report.public'.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 262ccfe3-71ff-44d5-8ffa-7b194fc2b9a6

📥 Commits

Reviewing files that changed from the base of the PR and between e43470d and d3f7d19.

📒 Files selected for processing (5)
  • packages/fs/src/write/issue/IssueWriter.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.test.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
  • packages/triage/src/helpers/deriveCurrentState.ts
  • packages/triage/src/util/ingestContent/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/triage/src/helpers/deriveCurrentState.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts

Comment thread packages/fs/src/write/issue/IssueWriter.ts
Comment thread packages/triage/src/util/ingestContent/index.ts
Comment thread packages/triage/src/util/ingestContent/index.ts Outdated
@duncanleo
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f57c71b184

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/triage/src/util/ingestContent/index.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
packages/triage/src/util/ingestContent/index.ts (4)

67-100: 💤 Low value

getText(content) is recomputed 5 times.

The switch in getText runs again on every triage / extraction / generation / translation / evidence-build call. It's cheap, but caching once at the top of the function also gives you a single canonical text to log/audit and prevents accidental drift if content is ever normalised differently across call sites.

♻️ Sketch
+  const text = getText(content);
@@
-      newEvidence: { ts: content.createdAt, text: getText(content) },
+      newEvidence: { ts: content.createdAt, text },

(apply analogously to the four other call sites)

Also applies to: 162-177

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

In `@packages/triage/src/util/ingestContent/index.ts` around lines 67 - 100,
getText(content) is being recomputed across multiple LLM steps; compute it once
and reuse to ensure a single canonical text and avoid drift. At the start of the
function, assign const text = getText(content) (or similar) and replace all
calls to getText(content) inside triageNewEvidence, extractClaimsFromNewEvidence
and the other runLlmStep/generation/translation/evidence-build call sites with
that text (use the newEvidence.text field in triageNewEvidence,
extractClaimsFromNewEvidence, etc.), and update logging to reference the single
text variable.

53-61: ⚡ Quick win

Avoid mutating the input content object.

content.createdAt = createdAt rewrites the caller's object as a side effect of normalisation. Beyond the surprise factor, it makes this function unsafe to call concurrently on a shared payload and pins IngestContent.createdAt to a non-readonly shape. Use a local for the rest of the function.

♻️ Sketch
-  const createdAt = DateTime.fromISO(content.createdAt)
-    .setZone('Asia/Singapore')
-    .toISO();
-  assert(createdAt != null, 'Expected valid createdAt');
-
-  content.createdAt = createdAt;
-  console.log('[ingestContent]', content);
+  const normalizedCreatedAt = DateTime.fromISO(content.createdAt)
+    .setZone('Asia/Singapore')
+    .toISO();
+  assert(normalizedCreatedAt != null, 'Expected valid createdAt');
+
+  const normalizedContent: IngestContent = {
+    ...content,
+    createdAt: normalizedCreatedAt,
+  };
+  console.log('[ingestContent]', normalizedContent);

…then use normalizedContent for the remainder of the function.

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

In `@packages/triage/src/util/ingestContent/index.ts` around lines 53 - 61, The
function currently mutates the caller's input by assigning content.createdAt =
createdAt; instead, create a new local object (e.g., normalizedContent) that
spreads the original content and sets createdAt to
DateTime.fromISO(content.createdAt).setZone('Asia/Singapore').toISO(), assert
it, and use normalizedContent for the rest of the function (including logging)
so the original content object is not mutated; remove the direct assignment to
content.createdAt and replace subsequent references to content with
normalizedContent.

20-28: 🏗️ Heavy lift

DATA_DIR is hardcoded relative to the source file's directory — brittle for both build output and reuse.

resolve(import.meta.dirname, '../../../../../data') bakes in a specific repo layout walked from packages/triage/src/util/ingestContent/. Anything that changes the on-disk depth of this file (compiled output under dist/, repo restructure, consumption from another workspace) silently shifts the resolved path. There's also no way for callers/tests to override the location.

Consider taking the data directory as a parameter (on ingestContent, or a constructor-like factory that builds repo/writer) or resolving it from a config/env var, with the current path as a default. This will also make ingestContent testable without touching the real data/ tree.

♻️ Sketch
-const DATA_DIR = resolve(import.meta.dirname, '../../../../../data');
-
-function createRepository(): MRTDownRepository {
-  return new MRTDownRepository({ store: new FileStore(DATA_DIR) });
-}
-
-function createWriter(): MRTDownWriter {
-  return new MRTDownWriter({ store: new FileWriteStore(DATA_DIR) });
-}
+const DEFAULT_DATA_DIR = resolve(import.meta.dirname, '../../../../../data');
+
+export interface IngestContentOptions {
+  dataDir?: string;
+}
+
+function createRepository(dataDir: string): MRTDownRepository {
+  return new MRTDownRepository({ store: new FileStore(dataDir) });
+}
+
+function createWriter(dataDir: string): MRTDownWriter {
+  return new MRTDownWriter({ store: new FileWriteStore(dataDir) });
+}
@@
-export async function ingestContent(content: IngestContent) {
+export async function ingestContent(
+  content: IngestContent,
+  { dataDir = DEFAULT_DATA_DIR }: IngestContentOptions = {},
+) {

…and update issueBundle.path = dataDir accordingly.

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

In `@packages/triage/src/util/ingestContent/index.ts` around lines 20 - 28,
Replace the hardcoded DATA_DIR by making the data directory configurable: add a
dataDir parameter (with fallback to the current resolve(import.meta.dirname,
'../../../../../data') default) to the factory/initializer that creates the
repo/writer and to ingestContent (or provide a constructor-like function), then
use that dataDir when constructing MRTDownRepository and MRTDownWriter (replace
new FileStore(DATA_DIR) / new FileWriteStore(DATA_DIR) with new
FileStore(dataDir) / new FileWriteStore(dataDir)) and ensure any assignment like
issueBundle.path = DATA_DIR is updated to issueBundle.path = dataDir so tests
and callers can override the data location (optionally support reading from an
env/config var as the default).

233-267: 💤 Low value

Add explicit return types and never-assertion defaults to getText and getEvidenceType.

Both switch statements lack explicit return types. Although IngestContent is a discriminated union and strict: true is enabled in tsconfig, TypeScript will not catch missing cases without explicit return type annotations. If content.source ever gains a new variant, these functions will silently return undefined, which then flows into evidence fields and produces invalid NDJSON.

Adding explicit return types (string for getText, EvidenceType for getEvidenceType) combined with a default never-assertion case ensures compile-time safety against future union variants.

♻️ Sketch
-function getText(content: IngestContent) {
+function getText(content: IngestContent): string {
   switch (content.source) {
     ...
+    default: {
+      const _exhaustive: never = content;
+      throw new Error(`Unhandled content source: ${JSON.stringify(_exhaustive)}`);
+    }
   }
 }

-function getEvidenceType(content: IngestContent) {
+function getEvidenceType(content: IngestContent): EvidenceType {
   switch (content.source) {
     ...
+    default: {
+      const _exhaustive: never = content;
+      throw new Error(`Unhandled content source: ${JSON.stringify(_exhaustive)}`);
+    }
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/triage/src/util/ingestContent/index.ts` around lines 233 - 267, Add
explicit return type annotations to getText (string) and getEvidenceType
(EvidenceType) and make their switch statements exhaustive by adding a
default/never branch that performs a never-assertion (e.g., use an assertNever
helper or assign content.source to a never and throw) so the compiler fails if
IngestContent gains a new source; update getEvidenceType to return the concrete
EvidenceType union and reference the functions getText and getEvidenceType in
your changes.
packages/fs/src/write/common/store.ts (1)

32-37: ⚡ Quick win

Consider whether delete should remain optional given rollback dependencies.

IssueWriter.delete(issueId) and the rollback path in restoreText(path, null) both call this.store.delete?.(...). A store that doesn't implement delete will silently no-op in both places, leaving partial state behind on rollback or making IssueWriter.delete a quiet success. If no real consumer is expected to omit delete, making it required would make the contract honest; otherwise, the call sites should fail loudly when it's missing.

♻️ Option A: make required
-  /**
-   * Delete a file or directory.
-   * `@param` path
-   * `@returns`
-   */
-  delete?(path: string): void;
+  /**
+   * Delete a file or directory. Implementations should be idempotent.
+   * `@param` path
+   */
+  delete(path: string): void;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/fs/src/write/common/store.ts` around lines 32 - 37, The interface
currently makes delete optional (delete?(path: string): void) which allows
this.store.delete?.(...) to silently no-op in IssueWriter.delete and restoreText
rollback paths; change the contract so failures are surfaced by either making
delete required (remove the ? so delete(path: string): void) and update
implementations to implement it, or keep it optional but replace the
safe-optional calls in IssueWriter.delete and restoreText (locations calling
this.store.delete?.(...)) with explicit checks that throw a clear error when
delete is undefined (e.g., if (!this.store.delete) throw new Error("store.delete
not implemented"); this.store.delete(path)); reference the optional signature,
IssueWriter.delete, restoreText(path, null), and the this.store.delete?.(...)
call sites to locate changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/fs/src/write/issue/IssueWriter.ts`:
- Around line 83-97: readOptionalText currently swallows all exceptions from
this.store.readText and returns null, which causes restoreText to delete files
on transient/read errors; change readOptionalText to only treat a "file missing"
error as null (e.g., check the thrown error.code === 'ENOENT' or the store's
missing-signal) and rethrow any other errors so rollback won't delete a valid
file; alternatively, update the store usage to call an explicit
exists/tryReadText if available and use that to decide null vs rethrow; ensure
callers like appendEvidenceAndImpacts rely on the rethrow to fail fast when
snapshot read fails.

---

Nitpick comments:
In `@packages/fs/src/write/common/store.ts`:
- Around line 32-37: The interface currently makes delete optional
(delete?(path: string): void) which allows this.store.delete?.(...) to silently
no-op in IssueWriter.delete and restoreText rollback paths; change the contract
so failures are surfaced by either making delete required (remove the ? so
delete(path: string): void) and update implementations to implement it, or keep
it optional but replace the safe-optional calls in IssueWriter.delete and
restoreText (locations calling this.store.delete?.(...)) with explicit checks
that throw a clear error when delete is undefined (e.g., if (!this.store.delete)
throw new Error("store.delete not implemented"); this.store.delete(path));
reference the optional signature, IssueWriter.delete, restoreText(path, null),
and the this.store.delete?.(...) call sites to locate changes.

In `@packages/triage/src/util/ingestContent/index.ts`:
- Around line 67-100: getText(content) is being recomputed across multiple LLM
steps; compute it once and reuse to ensure a single canonical text and avoid
drift. At the start of the function, assign const text = getText(content) (or
similar) and replace all calls to getText(content) inside triageNewEvidence,
extractClaimsFromNewEvidence and the other
runLlmStep/generation/translation/evidence-build call sites with that text (use
the newEvidence.text field in triageNewEvidence, extractClaimsFromNewEvidence,
etc.), and update logging to reference the single text variable.
- Around line 53-61: The function currently mutates the caller's input by
assigning content.createdAt = createdAt; instead, create a new local object
(e.g., normalizedContent) that spreads the original content and sets createdAt
to DateTime.fromISO(content.createdAt).setZone('Asia/Singapore').toISO(), assert
it, and use normalizedContent for the rest of the function (including logging)
so the original content object is not mutated; remove the direct assignment to
content.createdAt and replace subsequent references to content with
normalizedContent.
- Around line 20-28: Replace the hardcoded DATA_DIR by making the data directory
configurable: add a dataDir parameter (with fallback to the current
resolve(import.meta.dirname, '../../../../../data') default) to the
factory/initializer that creates the repo/writer and to ingestContent (or
provide a constructor-like function), then use that dataDir when constructing
MRTDownRepository and MRTDownWriter (replace new FileStore(DATA_DIR) / new
FileWriteStore(DATA_DIR) with new FileStore(dataDir) / new
FileWriteStore(dataDir)) and ensure any assignment like issueBundle.path =
DATA_DIR is updated to issueBundle.path = dataDir so tests and callers can
override the data location (optionally support reading from an env/config var as
the default).
- Around line 233-267: Add explicit return type annotations to getText (string)
and getEvidenceType (EvidenceType) and make their switch statements exhaustive
by adding a default/never branch that performs a never-assertion (e.g., use an
assertNever helper or assign content.source to a never and throw) so the
compiler fails if IngestContent gains a new source; update getEvidenceType to
return the concrete EvidenceType union and reference the functions getText and
getEvidenceType in your changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cb131a43-f0d5-4ac6-8b62-b9ec996894e1

📥 Commits

Reviewing files that changed from the base of the PR and between d3f7d19 and 95f6274.

📒 Files selected for processing (6)
  • packages/fs/src/index.test.ts
  • packages/fs/src/repo/common/store.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/fs/src/write/common/store.ts
  • packages/fs/src/write/issue/IssueWriter.ts
  • packages/triage/src/util/ingestContent/index.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/fs/src/repo/common/store.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/fs/src/write/common/FileWriteStore.ts

Comment thread packages/fs/src/write/issue/IssueWriter.ts
@duncanleo
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ead3a74909

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/triage/src/helpers/deriveCurrentState.ts
@duncanleo
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ae41ef738

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


const ResponseSchema = z.object({
title: z.string(),
slug: z.string(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Constrain slug output to writer-safe issue ID characters

The schema accepts any string for slug, but ingestContent later interpolates it into issueId and IssueWriter rejects slugs containing unsafe characters (for example spaces, /, or ..). In those cases issue creation throws and ingestion fails for otherwise valid evidence, so this path needs schema-level validation (or sanitization/retry) before persistence.

Useful? React with 👍 / 👎.

@duncanleo duncanleo force-pushed the codex/triage-step-4 branch from 6ae41ef to 84d1ac6 Compare May 15, 2026 17:01
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
packages/fs/src/repo/common/StandardRepository.ts (1)

11-12: ⚡ Quick win

Make the base repository abstract to enforce parser implementation.

The runtime "Not implemented" path is avoidable. Marking the class and parseItem as abstract gives compile-time enforcement.

♻️ Suggested refactor
-export class StandardRepository<T extends StandardRepositoryItem> {
+export abstract class StandardRepository<T extends StandardRepositoryItem> {
@@
-  protected parseItem(_json: unknown): T {
-    throw new Error('Not implemented');
-  }
+  protected abstract parseItem(json: unknown): T;

Also applies to: 50-52

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

In `@packages/fs/src/repo/common/StandardRepository.ts` around lines 11 - 12, Make
StandardRepository an abstract class and declare parseItem as an abstract method
to enforce compile-time implementation in subclasses instead of the runtime "Not
implemented" path; update the class declaration for StandardRepository<T extends
StandardRepositoryItem> to be abstract and change the parseItem signature (the
method currently throwing "Not implemented") to an abstract parseItem(...)
declaration so all subclasses must implement it (referencing the
StandardRepository class and its parseItem method).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/DATA_OVERHAUL_SPLIT.md`:
- Around line 7-11: Replace the accidental ATX heading token "#177" in the
sentence that references the PR with an inline form so it won't be parsed as a
heading (MD018); update the string "#177" to an inline form such as `#177` or
"PR 177" in the DATA_OVERHAUL_SPLIT paragraph where it appears so the reference
remains plain text and markdownlint no longer flags it.

In `@packages/fs/src/repo/common/StandardRepository.ts`:
- Around line 39-41: The load logic in StandardRepository is currently
overwriting entries by calling this.byId.set(item.id, item) which hides
duplicate IDs; change the logic in the method that calls parseItem (in
StandardRepository) to check for an existing id before setting (use
this.byId.has(item.id)) and throw an error (including the duplicate id and any
contextual info like the source filename or index) when a duplicate is detected
instead of replacing the earlier entry so duplicate IDs are rejected during
repository load.

In `@packages/fs/src/write/issue/IssueWriter.ts`:
- Around line 27-30: The append methods (e.g., appendEvidence) currently call
this.store.ensureDir(getIssueDir(issueId)) which can create orphan issue
folders; instead, validate the issue exists before appending by checking for the
canonical issue file (use getIssueDir(issueId) + FILE_ISSUE_JSON or the
equivalent identifier) via this.store.exists (or similar) and throw/return an
error if it does not exist; remove the ensureDir call from appendEvidence and
the other append methods referenced (the other append blocks around lines 35–38
and 48–56) so they only append when the issue.json is present.
- Around line 19-25: The create(issue: Issue) method currently clobbers existing
data by unconditionally calling this.store.writeJson and this.store.writeText
for FILE_ISSUE, FILE_ISSUE_EVIDENCE, and FILE_ISSUE_IMPACT; change it to first
detect existing state (use getIssueDir and the store's existence checks for the
issue directory or files) and if the issue already exists either throw/return an
error or skip writing, and only create the directory and write FILE_ISSUE,
FILE_ISSUE_EVIDENCE, and FILE_ISSUE_IMPACT when they do not already exist (i.e.,
use existence checks before calling this.store.ensureDir, this.store.writeJson,
and this.store.writeText so you do not truncate append-only evidence/impact
files).

In `@packages/triage/src/llm/client.ts`:
- Around line 3-6: The getOpenAiClient function must explicitly guard against a
missing OPENAI_API_KEY: before constructing new OpenAI, check
process.env.OPENAI_API_KEY and throw a clear error (or return a failure) if it's
undefined/empty so initialization fails fast; update getOpenAiClient to validate
the env var and only call new OpenAI({ apiKey: ... }) when the key is present,
referencing getOpenAiClient and the OPENAI_API_KEY env var in the change.

In
`@packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts`:
- Around line 42-43: Remove or gate the raw console.log calls that print tool
I/O in FindStationsTool.ts (specifically the logs referencing params at the
earlier call and the outputs around line 132) so sensitive user/evidence text is
not persisted; update the logging in the FindStationsTool call handler (the
method that currently logs "Calling tool with parameters:" and the later output
log) to either remove these statements or wrap them behind a strict debug flag
and redact sensitive fields before logging (only log non-sensitive metadata or a
hashed/redacted summary when debug is enabled).

---

Nitpick comments:
In `@packages/fs/src/repo/common/StandardRepository.ts`:
- Around line 11-12: Make StandardRepository an abstract class and declare
parseItem as an abstract method to enforce compile-time implementation in
subclasses instead of the runtime "Not implemented" path; update the class
declaration for StandardRepository<T extends StandardRepositoryItem> to be
abstract and change the parseItem signature (the method currently throwing "Not
implemented") to an abstract parseItem(...) declaration so all subclasses must
implement it (referencing the StandardRepository class and its parseItem
method).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 42570ea4-bd5a-4daa-a754-63f2c3fa49a3

📥 Commits

Reviewing files that changed from the base of the PR and between 95f6274 and 84d1ac6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (85)
  • .github/workflows/validate.yml
  • AGENTS.md
  • README.md
  • docs/DATA_OVERHAUL_SPLIT.md
  • package.json
  • packages/fs/package.json
  • packages/fs/src/constants.ts
  • packages/fs/src/index.test.ts
  • packages/fs/src/index.ts
  • packages/fs/src/repo/MRTDownRepository.ts
  • packages/fs/src/repo/common/FileStore.ts
  • packages/fs/src/repo/common/StandardRepository.ts
  • packages/fs/src/repo/common/dirEntries.ts
  • packages/fs/src/repo/common/store.ts
  • packages/fs/src/repo/issue/IssueRepository.ts
  • packages/fs/src/repo/landmark/LandmarkRepository.ts
  • packages/fs/src/repo/line/LineRepository.ts
  • packages/fs/src/repo/operator/OperatorRepository.ts
  • packages/fs/src/repo/service/ServiceRepository.ts
  • packages/fs/src/repo/station/StationRepository.ts
  • packages/fs/src/repo/town/TownRepository.ts
  • packages/fs/src/write/MRTDownWriter.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/fs/src/write/common/store.ts
  • packages/fs/src/write/id/IdGenerator.ts
  • packages/fs/src/write/issue/IssueWriter.ts
  • packages/triage/CHANGELOG.md
  • packages/triage/README.md
  • packages/triage/biome.json
  • packages/triage/package.json
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.test.ts
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.test.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.test.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.ts
  • packages/triage/src/helpers/deriveCurrentState.test.ts
  • packages/triage/src/helpers/deriveCurrentState.ts
  • packages/triage/src/helpers/estimateOpenAICost.ts
  • packages/triage/src/helpers/keyForAffectedEntity.ts
  • packages/triage/src/helpers/reconstructClaimsFromImpactEvents.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.test.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.ts
  • packages/triage/src/helpers/sumIntervalDuration.ts
  • packages/triage/src/index.ts
  • packages/triage/src/llm/client.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/triage/src/llm/common/MemoryStore.ts
  • packages/triage/src/llm/common/formatCurrentState.ts
  • packages/triage/src/llm/common/tool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/degradedServiceHeuristics.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/prompt.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/index.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/prompt.ts
  • packages/triage/src/llm/functions/translate/index.ts
  • packages/triage/src/llm/functions/triageNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/triageNewEvidence/index.ts
  • packages/triage/src/llm/functions/triageNewEvidence/prompt.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/FindIssuesTool.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/GetIssueTool.ts
  • packages/triage/src/maintenance/reExtractAndReplay.ts
  • packages/triage/src/maintenance/replayImpactEvents.ts
  • packages/triage/src/scripts/ingestViaWebhook.ts
  • packages/triage/src/scripts/reExtractAndReplay.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.ts
  • packages/triage/src/scripts/replayImpactEvents.ts
  • packages/triage/src/util/assert.ts
  • packages/triage/src/util/ingestContent/helpers/getSlugDateTimeFromClaims.ts
  • packages/triage/src/util/ingestContent/index.ts
  • packages/triage/src/util/ingestContent/types.ts
  • packages/triage/tsconfig.json
  • packages/triage/vitest.config.ts
  • packages/triage/vitest.eval.config.ts
  • vitest.config.ts
✅ Files skipped from review due to trivial changes (6)
  • packages/triage/vitest.config.ts
  • packages/triage/README.md
  • packages/triage/biome.json
  • vitest.config.ts
  • packages/triage/src/llm/functions/triageNewEvidence/prompt.ts
  • README.md
🚧 Files skipped from review as they are similar to previous changes (66)
  • packages/fs/src/repo/MRTDownRepository.ts
  • packages/triage/src/helpers/sumIntervalDuration.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.ts
  • packages/triage/vitest.eval.config.ts
  • packages/fs/src/repo/town/TownRepository.ts
  • packages/triage/src/helpers/keyForAffectedEntity.ts
  • packages/fs/src/repo/common/dirEntries.ts
  • packages/fs/src/repo/operator/OperatorRepository.ts
  • packages/triage/src/llm/common/tool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.test.ts
  • packages/fs/src/repo/landmark/LandmarkRepository.ts
  • packages/triage/src/index.ts
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.test.ts
  • packages/fs/src/repo/station/StationRepository.ts
  • packages/triage/package.json
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/index.ts
  • package.json
  • packages/triage/src/scripts/ingestViaWebhook.ts
  • packages/fs/src/repo/common/store.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/prompt.ts
  • packages/fs/src/write/MRTDownWriter.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/GetIssueTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.ts
  • packages/triage/src/helpers/reconstructClaimsFromImpactEvents.ts
  • packages/fs/src/repo/service/ServiceRepository.ts
  • packages/triage/src/helpers/deriveCurrentState.ts
  • packages/triage/src/scripts/reExtractAndReplay.ts
  • packages/triage/tsconfig.json
  • packages/triage/src/llm/functions/triageNewEvidence/index.ts
  • packages/triage/src/scripts/replayImpactEvents.ts
  • packages/triage/src/util/assert.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts
  • packages/fs/src/repo/common/FileStore.ts
  • .github/workflows/validate.yml
  • packages/fs/src/index.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
  • packages/triage/src/maintenance/replayImpactEvents.ts
  • packages/triage/src/llm/functions/translate/index.ts
  • packages/triage/src/maintenance/reExtractAndReplay.ts
  • packages/triage/src/helpers/estimateOpenAICost.ts
  • packages/triage/src/util/ingestContent/types.ts
  • packages/triage/src/llm/common/MemoryStore.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/FindIssuesTool.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.test.ts
  • packages/triage/src/llm/common/formatCurrentState.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.test.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.ts
  • packages/triage/src/llm/functions/triageNewEvidence/eval.test.ts
  • packages/fs/src/repo/issue/IssueRepository.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts
  • packages/fs/src/constants.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/prompt.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
  • packages/triage/src/helpers/deriveCurrentState.test.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/degradedServiceHeuristics.ts
  • packages/triage/src/util/ingestContent/index.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.test.ts
  • packages/fs/package.json

Comment thread docs/DATA_OVERHAUL_SPLIT.md Outdated
Comment thread packages/fs/src/repo/common/StandardRepository.ts
Comment thread packages/fs/src/write/issue/IssueWriter.ts
Comment thread packages/fs/src/write/issue/IssueWriter.ts Outdated
Comment thread packages/triage/src/llm/client.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts (1)

90-92: ⚡ Quick win

Deduplicate lineIds before rendering output.

Repeated memberships can emit duplicate line IDs, which adds noise to tool output.

Proposed change
-      const lineIds = relevantStationCodes.map(
-        (membership) => membership.lineId,
-      );
+      const lineIds = Array.from(
+        new Set(relevantStationCodes.map((membership) => membership.lineId)),
+      );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts`
around lines 90 - 92, The code builds lineIds from relevantStationCodes and can
include duplicates; deduplicate lineIds before using them in output to avoid
noise. In FindStationsTool.ts, after the mapping that creates lineIds from
relevantStationCodes (the const lineIds = relevantStationCodes.map((membership)
=> membership.lineId)), replace or follow it with a step that removes duplicates
(e.g., using a Set or Array.prototype.filter) so the rest of the function uses
the unique list of line IDs when rendering or returning results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/fs/src/write/issue/IssueWriter.ts`:
- Around line 67-70: The catch block in IssueWriter that calls
restoreText(evidencePath, evidenceBefore) and restoreText(impactPath,
impactBefore) can swallow the original append error if the first rollback
throws; change it so the original error is preserved and both rollback attempts
are attempted: capture the original caught error (e.g. origErr), then call
restoreText for evidence and impact in separate try/catch blocks (or otherwise
ensure each is attempted independently), log or aggregate any rollback errors
but do not replace origErr, and finally rethrow the original error; reference
IssueWriter, restoreText, evidencePath, impactPath, evidenceBefore and
impactBefore to locate and update the catch logic.

In `@packages/triage/src/helpers/reconstructClaimsFromImpactEvents.ts`:
- Around line 55-60: The code in reconstructClaimsFromImpactEvents currently
only takes the first period from a periods.set event (periodsEvent.periods[0]
assigned to newPeriod), which drops extra periods and causes incomplete
timeHints; update the handler(s) that create timeHints (the block using
periodsEvent and variable newPeriod, and the similar logic later in the same
function) to iterate over periodsEvent.periods, map each Period to a timeHint
entry, and combine them into the claim/evidence timeHints array so all periods
are preserved when replaying evidence.

---

Nitpick comments:
In
`@packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts`:
- Around line 90-92: The code builds lineIds from relevantStationCodes and can
include duplicates; deduplicate lineIds before using them in output to avoid
noise. In FindStationsTool.ts, after the mapping that creates lineIds from
relevantStationCodes (the const lineIds = relevantStationCodes.map((membership)
=> membership.lineId)), replace or follow it with a step that removes duplicates
(e.g., using a Set or Array.prototype.filter) so the rest of the function uses
the unique list of line IDs when rendering or returning results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ef9ebaa-5e2d-49f7-abee-ccfcb5d84779

📥 Commits

Reviewing files that changed from the base of the PR and between 95f6274 and 9c7bab7.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (85)
  • .github/workflows/validate.yml
  • AGENTS.md
  • README.md
  • docs/DATA_OVERHAUL_SPLIT.md
  • package.json
  • packages/fs/package.json
  • packages/fs/src/constants.ts
  • packages/fs/src/index.test.ts
  • packages/fs/src/index.ts
  • packages/fs/src/repo/MRTDownRepository.ts
  • packages/fs/src/repo/common/FileStore.ts
  • packages/fs/src/repo/common/StandardRepository.ts
  • packages/fs/src/repo/common/dirEntries.ts
  • packages/fs/src/repo/common/store.ts
  • packages/fs/src/repo/issue/IssueRepository.ts
  • packages/fs/src/repo/landmark/LandmarkRepository.ts
  • packages/fs/src/repo/line/LineRepository.ts
  • packages/fs/src/repo/operator/OperatorRepository.ts
  • packages/fs/src/repo/service/ServiceRepository.ts
  • packages/fs/src/repo/station/StationRepository.ts
  • packages/fs/src/repo/town/TownRepository.ts
  • packages/fs/src/write/MRTDownWriter.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/fs/src/write/common/store.ts
  • packages/fs/src/write/id/IdGenerator.ts
  • packages/fs/src/write/issue/IssueWriter.ts
  • packages/triage/CHANGELOG.md
  • packages/triage/README.md
  • packages/triage/biome.json
  • packages/triage/package.json
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.test.ts
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.test.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.test.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.ts
  • packages/triage/src/helpers/deriveCurrentState.test.ts
  • packages/triage/src/helpers/deriveCurrentState.ts
  • packages/triage/src/helpers/estimateOpenAICost.ts
  • packages/triage/src/helpers/keyForAffectedEntity.ts
  • packages/triage/src/helpers/reconstructClaimsFromImpactEvents.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.test.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.ts
  • packages/triage/src/helpers/sumIntervalDuration.ts
  • packages/triage/src/index.ts
  • packages/triage/src/llm/client.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/triage/src/llm/common/MemoryStore.ts
  • packages/triage/src/llm/common/formatCurrentState.ts
  • packages/triage/src/llm/common/tool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/degradedServiceHeuristics.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/prompt.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/index.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/prompt.ts
  • packages/triage/src/llm/functions/translate/index.ts
  • packages/triage/src/llm/functions/triageNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/triageNewEvidence/index.ts
  • packages/triage/src/llm/functions/triageNewEvidence/prompt.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/FindIssuesTool.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/GetIssueTool.ts
  • packages/triage/src/maintenance/reExtractAndReplay.ts
  • packages/triage/src/maintenance/replayImpactEvents.ts
  • packages/triage/src/scripts/ingestViaWebhook.ts
  • packages/triage/src/scripts/reExtractAndReplay.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.ts
  • packages/triage/src/scripts/replayImpactEvents.ts
  • packages/triage/src/util/assert.ts
  • packages/triage/src/util/ingestContent/helpers/getSlugDateTimeFromClaims.ts
  • packages/triage/src/util/ingestContent/index.ts
  • packages/triage/src/util/ingestContent/types.ts
  • packages/triage/tsconfig.json
  • packages/triage/vitest.config.ts
  • packages/triage/vitest.eval.config.ts
  • vitest.config.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/triage/biome.json
  • packages/fs/src/repo/landmark/LandmarkRepository.ts
  • README.md
  • AGENTS.md
🚧 Files skipped from review as they are similar to previous changes (67)
  • docs/DATA_OVERHAUL_SPLIT.md
  • packages/triage/vitest.config.ts
  • packages/triage/vitest.eval.config.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/prompt.ts
  • packages/triage/src/llm/functions/triageNewEvidence/prompt.ts
  • .github/workflows/validate.yml
  • vitest.config.ts
  • packages/triage/src/llm/client.ts
  • packages/triage/src/util/assert.ts
  • packages/fs/src/repo/town/TownRepository.ts
  • package.json
  • packages/triage/src/helpers/sumIntervalDuration.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.test.ts
  • packages/triage/package.json
  • packages/fs/src/repo/line/LineRepository.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.ts
  • packages/fs/src/repo/station/StationRepository.ts
  • packages/triage/src/llm/functions/triageNewEvidence/eval.test.ts
  • packages/fs/src/repo/MRTDownRepository.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.test.ts
  • packages/fs/package.json
  • packages/triage/src/util/ingestContent/helpers/getSlugDateTimeFromClaims.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/eval.test.ts
  • packages/triage/src/llm/common/tool.ts
  • packages/fs/src/constants.ts
  • packages/triage/src/llm/functions/translate/index.ts
  • packages/fs/src/repo/common/FileStore.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.test.ts
  • packages/fs/src/repo/common/StandardRepository.ts
  • packages/fs/src/index.ts
  • packages/fs/src/repo/operator/OperatorRepository.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/GetIssueTool.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
  • packages/triage/src/scripts/replayImpactEvents.ts
  • packages/fs/src/write/common/store.ts
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.ts
  • packages/triage/src/scripts/reExtractAndReplay.ts
  • packages/triage/src/llm/common/formatCurrentState.ts
  • packages/triage/src/index.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/fs/src/repo/common/dirEntries.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/prompt.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/degradedServiceHeuristics.ts
  • packages/fs/src/repo/service/ServiceRepository.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/FindIssuesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.test.ts
  • packages/fs/src/write/MRTDownWriter.ts
  • packages/triage/src/llm/common/MemoryStore.ts
  • packages/triage/src/llm/functions/triageNewEvidence/index.ts
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.test.ts
  • packages/triage/src/maintenance/replayImpactEvents.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/index.ts
  • packages/triage/src/helpers/deriveCurrentState.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.ts
  • packages/triage/src/util/ingestContent/index.ts
  • packages/triage/src/scripts/ingestViaWebhook.ts
  • packages/triage/src/util/ingestContent/types.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.ts
  • packages/triage/src/helpers/deriveCurrentState.test.ts
  • packages/triage/src/helpers/estimateOpenAICost.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.ts
  • packages/fs/src/repo/issue/IssueRepository.ts

Comment thread packages/fs/src/write/issue/IssueWriter.ts
Comment thread packages/triage/src/helpers/reconstructClaimsFromImpactEvents.ts Outdated
@duncanleo duncanleo force-pushed the codex/triage-step-4 branch from 9c7bab7 to 7595963 Compare May 16, 2026 03:17
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In `@packages/fs/src/write/id/IdGenerator.ts`:
- Around line 22-33: The evidenceId and impactEventId functions call
ulid(ts?.toMillis?.() ?? undefined) without guarding for Luxon DateTime
validity, so a DateTime with toMillis() === NaN will silently cause ulid to use
the current time; update both functions (evidenceId and impactEventId) to check
ts?.isValid === true before calling ts.toMillis(), and if ts is provided but
invalid throw a clear error (or otherwise fail fast) instead of passing NaN into
ulid, while preserving the existing behavior when ts is undefined.

In `@packages/triage/src/llm/common/MemoryStore.ts`:
- Around line 20-23: The delete() logic in MemoryStore uses toStorePath('...')
which can return '' for root, causing the descendant-cleanup check
filePath.startsWith(`${p}/`) to become startsWith('/') and silently fail; update
the MemoryStore.delete method to explicitly detect the root path (p === '') and
either disallow/root-guard by throwing or returning early before attempting
prefix cleanup, or handle the empty prefix by using a different descendant check
(e.g., match everything when p === ''), and ensure to reference toStorePath and
the delete method when making the change so the special-case for root is handled
consistently.

In
`@packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.ts`:
- Around line 390-406: getActiveServiceStationIds currently falls back to the
latest revision when no revision matches the evidence timestamp, causing false
overlaps; remove that fallback so the function returns no stations when there is
no active revision. In practice, change the revision selection in
getActiveServiceStationIds (which iterates service.revisions and computes
startedAtMs/endedAtMs against evidenceTsMs) to not use "??
service.revisions.at(-1)" and let revision be undefined if none match, so the
final return becomes an empty array via the existing revision?.path.stations
mapping. Ensure no other code assumes a non-null revision here.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a4331431-3215-44b2-9b66-865efe2121d5

📥 Commits

Reviewing files that changed from the base of the PR and between 9c7bab7 and 65cd0d5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (86)
  • .github/workflows/validate.yml
  • AGENTS.md
  • README.md
  • docs/DATA_OVERHAUL_SPLIT.md
  • package.json
  • packages/fs/package.json
  • packages/fs/src/constants.ts
  • packages/fs/src/index.test.ts
  • packages/fs/src/index.ts
  • packages/fs/src/repo/MRTDownRepository.ts
  • packages/fs/src/repo/common/FileStore.ts
  • packages/fs/src/repo/common/StandardRepository.ts
  • packages/fs/src/repo/common/dirEntries.ts
  • packages/fs/src/repo/common/store.ts
  • packages/fs/src/repo/issue/IssueRepository.ts
  • packages/fs/src/repo/landmark/LandmarkRepository.ts
  • packages/fs/src/repo/line/LineRepository.ts
  • packages/fs/src/repo/operator/OperatorRepository.ts
  • packages/fs/src/repo/service/ServiceRepository.ts
  • packages/fs/src/repo/station/StationRepository.ts
  • packages/fs/src/repo/town/TownRepository.ts
  • packages/fs/src/write/MRTDownWriter.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/fs/src/write/common/store.ts
  • packages/fs/src/write/id/IdGenerator.ts
  • packages/fs/src/write/issue/IssueWriter.ts
  • packages/triage/CHANGELOG.md
  • packages/triage/README.md
  • packages/triage/biome.json
  • packages/triage/package.json
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.test.ts
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.test.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.test.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.ts
  • packages/triage/src/helpers/deriveCurrentState.test.ts
  • packages/triage/src/helpers/deriveCurrentState.ts
  • packages/triage/src/helpers/estimateOpenAICost.ts
  • packages/triage/src/helpers/keyForAffectedEntity.ts
  • packages/triage/src/helpers/reconstructClaimsFromImpactEvents.test.ts
  • packages/triage/src/helpers/reconstructClaimsFromImpactEvents.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.test.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.ts
  • packages/triage/src/helpers/sumIntervalDuration.ts
  • packages/triage/src/index.ts
  • packages/triage/src/llm/client.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/triage/src/llm/common/MemoryStore.ts
  • packages/triage/src/llm/common/formatCurrentState.ts
  • packages/triage/src/llm/common/tool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/degradedServiceHeuristics.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/prompt.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/index.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/prompt.ts
  • packages/triage/src/llm/functions/translate/index.ts
  • packages/triage/src/llm/functions/triageNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/triageNewEvidence/index.ts
  • packages/triage/src/llm/functions/triageNewEvidence/prompt.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/FindIssuesTool.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/GetIssueTool.ts
  • packages/triage/src/maintenance/reExtractAndReplay.ts
  • packages/triage/src/maintenance/replayImpactEvents.ts
  • packages/triage/src/scripts/ingestViaWebhook.ts
  • packages/triage/src/scripts/reExtractAndReplay.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.ts
  • packages/triage/src/scripts/replayImpactEvents.ts
  • packages/triage/src/util/assert.ts
  • packages/triage/src/util/ingestContent/helpers/getSlugDateTimeFromClaims.ts
  • packages/triage/src/util/ingestContent/index.ts
  • packages/triage/src/util/ingestContent/types.ts
  • packages/triage/tsconfig.json
  • packages/triage/vitest.config.ts
  • packages/triage/vitest.eval.config.ts
  • vitest.config.ts
✅ Files skipped from review due to trivial changes (10)
  • packages/triage/vitest.config.ts
  • packages/fs/src/repo/landmark/LandmarkRepository.ts
  • vitest.config.ts
  • packages/triage/biome.json
  • docs/DATA_OVERHAUL_SPLIT.md
  • .github/workflows/validate.yml
  • README.md
  • packages/triage/src/helpers/reconstructClaimsFromImpactEvents.test.ts
  • packages/fs/src/index.ts
  • AGENTS.md
🚧 Files skipped from review as they are similar to previous changes (63)
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.test.ts
  • packages/triage/vitest.eval.config.ts
  • packages/fs/src/write/common/store.ts
  • packages/fs/src/repo/town/TownRepository.ts
  • packages/fs/src/write/MRTDownWriter.ts
  • packages/triage/src/llm/client.ts
  • packages/triage/src/llm/common/tool.ts
  • packages/triage/src/scripts/replayImpactEvents.ts
  • packages/triage/tsconfig.json
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/prompt.ts
  • packages/fs/src/repo/line/LineRepository.ts
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.test.ts
  • packages/triage/src/helpers/sumIntervalDuration.ts
  • package.json
  • packages/triage/src/util/ingestContent/helpers/getSlugDateTimeFromClaims.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/GetIssueTool.ts
  • packages/fs/src/repo/common/dirEntries.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.test.ts
  • packages/fs/src/repo/operator/OperatorRepository.ts
  • packages/fs/src/constants.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/prompt.ts
  • packages/triage/src/llm/functions/triageNewEvidence/prompt.ts
  • packages/triage/src/helpers/deriveCurrentState.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.ts
  • packages/triage/package.json
  • packages/fs/src/repo/MRTDownRepository.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.ts
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/fs/src/repo/station/StationRepository.ts
  • packages/fs/src/repo/service/ServiceRepository.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.test.ts
  • packages/fs/src/repo/common/FileStore.ts
  • packages/triage/src/llm/functions/triageNewEvidence/eval.test.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
  • packages/fs/src/repo/common/StandardRepository.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/index.ts
  • packages/triage/src/llm/functions/translate/index.ts
  • packages/triage/src/maintenance/reExtractAndReplay.ts
  • packages/triage/src/llm/functions/triageNewEvidence/index.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.ts
  • packages/triage/src/index.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/FindIssuesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts
  • packages/triage/src/scripts/reExtractAndReplay.ts
  • packages/triage/src/util/ingestContent/types.ts
  • packages/fs/package.json
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.test.ts
  • packages/fs/src/repo/issue/IssueRepository.ts
  • packages/triage/src/maintenance/replayImpactEvents.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/degradedServiceHeuristics.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts
  • packages/triage/src/llm/common/formatCurrentState.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.ts
  • packages/triage/src/helpers/deriveCurrentState.test.ts
  • packages/fs/src/index.test.ts
  • packages/triage/src/util/ingestContent/index.ts

Comment thread packages/fs/src/write/id/IdGenerator.ts
Comment thread packages/triage/src/llm/common/MemoryStore.ts
@duncanleo
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@duncanleo duncanleo force-pushed the codex/triage-step-4 branch 2 times, most recently from 9033bb7 to 0870ae3 Compare May 16, 2026 16:46
@duncanleo
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23faca3b61

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/triage/src/llm/functions/generateIssueTitleAndSlug/index.ts Outdated
Comment thread packages/triage/src/maintenance/replayImpactEvents.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts (1)

38-39: ⚡ Quick win

Avoid logging full tool output payloads.

Logging full markdown output here is high-noise and unnecessary for normal operation. Prefer concise structured metadata (e.g., lineId, result count).

Suggested patch
-    console.log('[findServices] Calling tool with parameters:', params);
+    console.log('[findServices] Calling tool', { lineId: params.lineId });
@@
-    console.log(`[findServices] Response output:\n${output}`);
+    console.log('[findServices] Response ready', {
+      lineId: params.lineId,
+      servicesMatched: Math.max(table.children.length - 1, 0),
+    });

Also applies to: 130-130

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

In
`@packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts`
around lines 38 - 39, The console.log in FindServicesTool that prints the full
params payload (e.g., the line with "console.log('[findServices] Calling tool
with parameters:', params);" inside the FindServicesTool call method) should be
replaced with a concise structured log containing only essential metadata (for
example log lineId and the number of results to be returned) to avoid noisy
markdown output; update both occurrences of this logging (the call-site in the
tool's execution method and the second occurrence around the result handling) to
emit a small object like { lineId, resultCount } or a short string message
instead of the whole params or full output payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/fs/src/repo/common/FileStore.ts`:
- Around line 12-15: Add a shared path resolver used by readText, listDir, and
exists that normalizes the input path, resolves it against this.rootDir, and
verifies the resolved path starts with the rootDir (preventing ../ escapes)
before performing any fs operation; implement a private method like
resolveAndValidate(path: string): string that uses path.resolve/path.normalize
to compute fullPath, throws an error if fullPath is outside this.rootDir, then
use that method in readText (replacing join(this.rootDir, path)), listDir, and
exists so all filesystem access is guarded by the containment check.

In `@packages/fs/src/write/issue/IssueWriter.ts`:
- Around line 93-113: getIssueDir currently only regex-validates the issueId so
impossible dates (e.g., 2026-99-99) slip through; update getIssueDir to parse
the captured year, month, and day (from tsMatch) into a Date object (or compute
normalized year/month/day) and verify the Date components match the original
year, month, and day before constructing safeIssueId and returning
join(DIR_ISSUE,...); if the date normalization does not match, throw the same
Invalid issue ID error. Ensure this check runs after extracting year/month/day
and before creating safeIssueId so functions/methods like getIssueDir, tsMatch,
safeIssueId and constant DIR_ISSUE are used to locate and fix the code.

In
`@packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts`:
- Around line 18-20: The tool's description string is incorrect: update the
public description on FindServicesTool (public description = ...) to reflect
that it searches by lineId rather than by name (e.g., "Find services by lineId"
or "Find services by lineId (lookup by lineId)"); ensure the new text matches
the actual lookup behavior used elsewhere in the class (lookups referencing
lineId) and keep the public name 'findServices' unchanged.

---

Nitpick comments:
In
`@packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts`:
- Around line 38-39: The console.log in FindServicesTool that prints the full
params payload (e.g., the line with "console.log('[findServices] Calling tool
with parameters:', params);" inside the FindServicesTool call method) should be
replaced with a concise structured log containing only essential metadata (for
example log lineId and the number of results to be returned) to avoid noisy
markdown output; update both occurrences of this logging (the call-site in the
tool's execution method and the second occurrence around the result handling) to
emit a small object like { lineId, resultCount } or a short string message
instead of the whole params or full output payload.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f4ecdbab-935a-425d-97f7-110264c0b05e

📥 Commits

Reviewing files that changed from the base of the PR and between 7d4ebcc and 41f536f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (89)
  • .github/workflows/validate.yml
  • AGENTS.md
  • README.md
  • docs/DATA_OVERHAUL_SPLIT.md
  • package.json
  • packages/fs/package.json
  • packages/fs/src/constants.ts
  • packages/fs/src/index.test.ts
  • packages/fs/src/index.ts
  • packages/fs/src/repo/MRTDownRepository.ts
  • packages/fs/src/repo/common/FileStore.ts
  • packages/fs/src/repo/common/StandardRepository.ts
  • packages/fs/src/repo/common/dirEntries.ts
  • packages/fs/src/repo/common/store.ts
  • packages/fs/src/repo/issue/IssueRepository.ts
  • packages/fs/src/repo/landmark/LandmarkRepository.ts
  • packages/fs/src/repo/line/LineRepository.ts
  • packages/fs/src/repo/operator/OperatorRepository.ts
  • packages/fs/src/repo/service/ServiceRepository.ts
  • packages/fs/src/repo/station/StationRepository.ts
  • packages/fs/src/repo/town/TownRepository.ts
  • packages/fs/src/write/MRTDownWriter.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/fs/src/write/common/store.ts
  • packages/fs/src/write/id/IdGenerator.ts
  • packages/fs/src/write/issue/IssueWriter.ts
  • packages/triage/CHANGELOG.md
  • packages/triage/README.md
  • packages/triage/biome.json
  • packages/triage/package.json
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.test.ts
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.test.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.test.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.ts
  • packages/triage/src/helpers/deriveCurrentState.test.ts
  • packages/triage/src/helpers/deriveCurrentState.ts
  • packages/triage/src/helpers/estimateOpenAICost.ts
  • packages/triage/src/helpers/keyForAffectedEntity.ts
  • packages/triage/src/helpers/reconstructClaimsFromImpactEvents.test.ts
  • packages/triage/src/helpers/reconstructClaimsFromImpactEvents.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.test.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.ts
  • packages/triage/src/helpers/sumIntervalDuration.ts
  • packages/triage/src/index.ts
  • packages/triage/src/llm/client.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/triage/src/llm/common/MemoryStore.ts
  • packages/triage/src/llm/common/formatCurrentState.ts
  • packages/triage/src/llm/common/tool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/degradedServiceHeuristics.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/prompt.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/index.test.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/index.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/prompt.ts
  • packages/triage/src/llm/functions/translate/index.ts
  • packages/triage/src/llm/functions/triageNewEvidence/eval.test.ts
  • packages/triage/src/llm/functions/triageNewEvidence/index.ts
  • packages/triage/src/llm/functions/triageNewEvidence/prompt.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/FindIssuesTool.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/GetIssueTool.ts
  • packages/triage/src/maintenance/evidenceSort.test.ts
  • packages/triage/src/maintenance/evidenceSort.ts
  • packages/triage/src/maintenance/reExtractAndReplay.ts
  • packages/triage/src/maintenance/replayImpactEvents.ts
  • packages/triage/src/scripts/ingestViaWebhook.ts
  • packages/triage/src/scripts/reExtractAndReplay.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.ts
  • packages/triage/src/scripts/replayImpactEvents.ts
  • packages/triage/src/util/assert.ts
  • packages/triage/src/util/ingestContent/helpers/getSlugDateTimeFromClaims.ts
  • packages/triage/src/util/ingestContent/index.ts
  • packages/triage/src/util/ingestContent/types.ts
  • packages/triage/tsconfig.json
  • packages/triage/vitest.config.ts
  • packages/triage/vitest.eval.config.ts
  • vitest.config.ts
✅ Files skipped from review due to trivial changes (11)
  • packages/triage/biome.json
  • vitest.config.ts
  • packages/triage/src/maintenance/evidenceSort.test.ts
  • packages/triage/src/scripts/ingestViaWebhook.ts
  • packages/triage/README.md
  • packages/triage/CHANGELOG.md
  • README.md
  • docs/DATA_OVERHAUL_SPLIT.md
  • AGENTS.md
  • packages/fs/src/index.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.test.ts
🚧 Files skipped from review as they are similar to previous changes (64)
  • packages/fs/src/repo/common/dirEntries.ts
  • packages/triage/vitest.eval.config.ts
  • packages/fs/src/repo/common/store.ts
  • packages/fs/src/repo/MRTDownRepository.ts
  • packages/fs/src/constants.ts
  • packages/fs/src/repo/service/ServiceRepository.ts
  • packages/triage/src/util/assert.ts
  • packages/fs/src/repo/landmark/LandmarkRepository.ts
  • packages/triage/src/helpers/computeStartOfDaysWithinInterval.ts
  • packages/fs/src/repo/operator/OperatorRepository.ts
  • .github/workflows/validate.yml
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.test.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/FindIssuesTool.ts
  • packages/triage/src/helpers/reconstructClaimsFromImpactEvents.test.ts
  • packages/triage/src/llm/functions/triageNewEvidence/tools/GetIssueTool.ts
  • packages/triage/src/helpers/sumIntervalDuration.ts
  • packages/fs/src/repo/station/StationRepository.ts
  • packages/triage/vitest.config.ts
  • packages/triage/src/llm/functions/generateIssueTitleAndSlug/prompt.ts
  • packages/triage/src/index.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.test.ts
  • packages/triage/tsconfig.json
  • package.json
  • packages/triage/src/helpers/keyForAffectedEntity.ts
  • packages/triage/src/scripts/replayImpactEvents.ts
  • packages/fs/src/repo/town/TownRepository.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/prompt.ts
  • packages/triage/src/llm/common/tool.ts
  • packages/triage/package.json
  • packages/triage/src/helpers/splitIntervalByServiceHours.test.ts
  • packages/fs/src/write/common/StandardWriter.ts
  • packages/triage/src/util/ingestContent/helpers/getSlugDateTimeFromClaims.ts
  • packages/fs/src/write/common/FileWriteStore.ts
  • packages/triage/src/helpers/splitIntervalByServiceHours.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/degradedServiceHeuristics.ts
  • packages/triage/src/helpers/estimateOpenAICost.ts
  • packages/fs/src/write/MRTDownWriter.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.ts
  • packages/triage/src/llm/functions/triageNewEvidence/prompt.ts
  • packages/fs/src/repo/common/StandardRepository.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.ts
  • packages/triage/src/helpers/calculateDurationWithinServiceHours.ts
  • packages/fs/src/repo/issue/IssueRepository.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/eval.test.ts
  • packages/fs/src/write/common/store.ts
  • packages/triage/src/util/ingestContent/types.ts
  • packages/triage/src/scripts/reExtractAndReplayTargets.ts
  • packages/triage/src/llm/functions/triageNewEvidence/eval.test.ts
  • packages/triage/src/helpers/deriveCurrentState.ts
  • packages/triage/src/maintenance/reExtractAndReplay.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/tools/ResolveRelativeDateTool.ts
  • packages/triage/src/llm/common/MemoryStore.ts
  • packages/triage/src/llm/common/formatCurrentState.ts
  • packages/triage/src/llm/client.ts
  • packages/triage/src/llm/functions/translate/index.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/normalizeClaimsForEvidence.ts
  • packages/triage/src/llm/common/MemoryStore.test.ts
  • packages/triage/src/llm/functions/extractClaimsFromNewEvidence/index.ts
  • packages/triage/src/scripts/reExtractAndReplay.ts
  • packages/triage/src/helpers/deriveCurrentState.test.ts
  • packages/triage/src/llm/functions/triageNewEvidence/index.ts
  • packages/triage/src/helpers/computeImpactFromEvidenceClaims.test.ts
  • packages/triage/src/util/ingestContent/index.ts

Comment thread packages/fs/src/repo/common/FileStore.ts
Comment thread packages/fs/src/write/issue/IssueWriter.ts
@duncanleo
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d18fb69b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/fs/src/write/issue/IssueWriter.ts Outdated
@duncanleo
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a85bf8f54a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/fs/src/write/common/FileWriteStore.ts
@duncanleo
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb1d4fc1a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/fs/src/write/common/StandardWriter.ts Outdated
@duncanleo duncanleo changed the title [codex] Add triage package split feat: add triage package split May 17, 2026
@duncanleo
Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fa3c159f33

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/fs/src/repo/issue/IssueRepository.ts
@duncanleo
Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@duncanleo duncanleo merged commit 4b1ca49 into main May 17, 2026
4 checks passed
@duncanleo duncanleo deleted the codex/triage-step-4 branch May 17, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant