Repository navigation
OpenConceptLab/ocl_issues#2908 | One recommendation rule: the OCL AI Assistant decides when it has analysed the current candidates, the score orders the rest - #110
Conversation
… AI Assistant decides when it has analysed the current candidates, the score orders the rest getRowRecommendation returns a row's candidate order (the AI's pick, its alternates by score, then today's score buckets), what to propose and who chose it, and how the AI and the score agree. RECOMMEND and CONDITIONAL propose the AI's pick in any score bucket; REJECT and INSUFFICIENT propose nothing, even over a Recommended score. Without an analysis of the current candidate pool (the fingerprint check Auto Match already uses), the top score is proposed if it's Recommended, as before. A pick that isn't a target-repo candidate falls back to the score rule. Not wired in yet: the panel, the grid and Auto Match follow in the next commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TGqXPeHFfYAdqAr7rmXwCv
…dates panel and the grid Match Quality, sorted by score, now follows getRowRecommendation: the OCL AI Assistant's pick and its alternates first when it has analysed the row's current candidates, then the score buckets. A summary line gives the AI's verdict, its pick and the top score, and says whether they differ; "Read analysis" opens the full explanation. Group > AI Analysis is gone. The grid gains an "OCL AI Assistant" column (agreement state, running or failed, sortable and filterable, with the other side in its tooltip) and an "OCL AI Assistant disagrees (n)" filter. Target Code shows the AI icon only on the AI's pick, comparing URLs decoded, since a code with "&" arrives encoded once or twice. Match Quality stays score-only. Badges read "OCL AI Assistant's pick" and "OCL AI Assistant alternate". New strings in en, es, zh and pt-BR. A per-row cache replaces the best-candidate-score cache and also keys on the row's analyses; the candidate-pool fingerprint is built only for rows with analyses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm
…tion rule AutoMatch's proposal for a row now comes from getRowRecommendation, with any current analysis the row has: the OCL AI Assistant's pick when it has analysed the row's current candidates, nothing on REJECT or INSUFFICIENT, and the top score if Recommended otherwise, as before. setAutoMatched and the single-algorithm batch path share one function; pickTopRowView is gone. When the run's AI step returns for a row, the row is decided again, unless the user changed it after the run set it. An analysis the user runs on one row reorders its candidates and updates the grid but changes no mapping. A withdrawn proposal no longer stays Proposed. The row's log records who chose (chosen_by: ai_assistant | score) and the verdict; a REJECT, INSUFFICIENT or a pick that isn't a candidate that leaves the row unmapped is logged as auto_match_no_proposal. closes OpenConceptLab/ocl_issues#2908, closes OpenConceptLab/ocl_issues#2333 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm
…an approval it replaces, later proposals keep the user's edits, a foreign pick isn't a target concept - A row the run decides is no longer Approved. A full run clears approvals when it starts; an AI-only run didn't, so a re-proposed row kept showing Approved. (F1.1) - A row the run already proposed and the user changed since is left alone by the run's later proposals too, such as the single-algorithm rerank sweep. The run's view of the row is updated as it applies a proposal, so the check doesn't wait for a render. (F1.2) - The AI's pick is matched by code only within its own source, so another source's concept with the same code isn't taken for a target candidate. (F1.3) - A pick that isn't a candidate is logged when the AI step returns, also when the score's proposal stays the same. (F1.4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm
paynejd
left a comment
There was a problem hiding this comment.
Codex review, pass 1 (codex-cli 0.163.0, commit 11c3394)
Verdict: findings, 4 finding(s).
- F1.1 (P1)
src/components/map-projects/MapProject.jsx:2386: An AI-only run can replace an approved mapping while retaining its approval. → Fixed in de6c38d: a row the run decides is no longer Approved, as a full run already clears approvals when it starts. Checked live with an AI-only run on an approved row. - F1.2 (P1)
src/components/map-projects/MapProject.jsx:2406: Repeated proposals bypass the protection for rows the user changed during the run. → Fixed in de6c38d: the run's later proposals, such as the single-algorithm rerank sweep, leave alone a row the user changed since the run proposed it. The run's view of the row is updated as it applies a proposal. - F1.3 (P2)
src/components/map-projects/viewBuilders.js:629: Code-only fallback can turn a non-target AI pick into a different target-repository concept. → Fixed in de6c38d: the code fallback matches only within the AI candidate's own source, with tests for both cases. Checked live with a foreign concept that shares a target code. - F1.4 (P2)
src/components/map-projects/MapProject.jsx:2418: The unchanged-proposal shortcut suppresses the required invalid-pick diagnostic. → Fixed in de6c38d: a pick that isn't a candidate goes through the apply step and is logged even when the proposal is unchanged. Checked live.
findings.json
{
"reviewer": "codex-cli 0.163.0",
"perspective": "defects",
"mode": "full",
"commit": "11c339493c05a32d9183c14be5fc820e1d64ab3c",
"verdict": "findings",
"prior": [],
"findings": [
{
"id": "F1.1",
"severity": "P1",
"tag": null,
"file": "src/components/map-projects/MapProject.jsx",
"line": 2386,
"claim": "An AI-only run can replace an approved mapping while retaining its approval.",
"scenario": "Run AutoMatch without Retrieve Candidates on selected approved rows, or with All Including Approved. AI-only runs skip clearing reviewed statuses. When the AI picks another target, proposeRowMapping changes the target and adds readyForReview but leaves reviewed intact. The grid and exports therefore treat the new, unreviewed mapping as Approved. REJECT similarly leaves an unmapped row marked Approved.",
"action": "Clear the row's approval when AutoMatch replaces or withdraws its mapping, and keep the recorded run snapshot consistent. Test both outcomes on an approved row in an AI-only run.",
"lines_delta": null,
"confidence": "high"
},
{
"id": "F1.2",
"severity": "P1",
"tag": null,
"file": "src/components/map-projects/MapProject.jsx",
"line": 2406,
"claim": "Repeated proposals bypass the protection for rows the user changed during the run.",
"scenario": "In a single-algorithm run, an early batch proposes a target. The user changes that mapping while later batches run. The subsequent bulk rerank sweep calls setAutoMatched again, unconditionally overwriting the user's edit and replacing runRowMappingsRef with that new proposal. When the AI returns, the equality guard passes and changes the mapping again. The required preservation of the user's edit fails.",
"action": "Apply the mapping-change guard before subsequent proposals from the same run, including the rerank sweep, and do not replace the saved run snapshot when a user edit is detected.",
"lines_delta": null,
"confidence": "high"
},
{
"id": "F1.3",
"severity": "P2",
"tag": null,
"file": "src/components/map-projects/viewBuilders.js",
"line": 629,
"claim": "Code-only fallback can turn a non-target AI pick into a different target-repository concept.",
"scenario": "The AI names a concept from another repository whose code also exists among the target candidates. Its concept_key does not match a target view, but the fallback ignores the repository and matches the shared code. AutoMatch then proposes that target concept as the AI's pick, potentially below Recommended, instead of applying the score fallback and recording pick_not_candidate.",
"action": "Validate repository identity as well as code during fallback resolution. A known foreign concept_key must not resolve to a target concept solely because their codes match. Add a cross-repository code-collision test.",
"lines_delta": null,
"confidence": "high"
},
{
"id": "F1.4",
"severity": "P2",
"tag": null,
"file": "src/components/map-projects/MapProject.jsx",
"line": 2418,
"claim": "The unchanged-proposal shortcut suppresses the required invalid-pick diagnostic.",
"scenario": "AutoMatch first proposes the top score. Its AI step then returns a non-candidate pick, including CONDITIONAL with a null primary. The rule correctly falls back to the same score proposal, but URL and chosenBy are unchanged, so this return prevents getAutoMatchLogExtras from recording ai_pick_not_candidate. The raw analysis is logged, but the invalid-pick fallback is not identified.",
"action": "Record the invalid-pick fallback independently of whether the mapping changes, while retaining the shortcut for redundant mapping updates. Test this through the re-decision path.",
"lines_delta": null,
"confidence": "high"
}
]
}…r hides at zero, URL-less targets are told apart, saved analyses show without the AI service - The "OCL AI Assistant disagrees" filter shows only while some row disagrees, like the Match Quality buckets, so it can't look selected while filtering nothing. (F2.1) - A mapped target without a URL (a bridge target before its lookup) is told apart by its source and code, so the run's check for a row the user changed sees a switch between two such targets. (F2.2) - The OCL AI Assistant column, its filter and the Target Code icon show on a project with analyses also where the AI Assistant isn't configured; the panel and AutoMatch already use those analyses. (F2.3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm
paynejd
left a comment
There was a problem hiding this comment.
Codex review, pass 2 (codex-cli 0.163.0, commit de6c38d)
Verdict: findings, 3 finding(s); earlier findings: F1.1 resolved, F1.2 resolved, F1.3 resolved, F1.4 resolved.
- F2.1 (P1)
src/components/map-projects/MapProject.jsx:3249: The disagreement filter stops filtering while remaining visibly selected. → Fixed in 7dbbb12: the chip shows only while some row disagrees, like the Match Quality buckets, and the filter applies only then. - F2.2 (P2)
src/components/map-projects/autoMatchRows.js:185: The mapping guard cannot distinguish different targets that lack URLs, allowing AutoMatch to overwrite a user's change. → Fixed in 7dbbb12: a mapped target without a URL is identified by its source and code (getMappingTarget), with a test for switching between two such targets. - F2.3 (P2)
src/components/map-projects/MapProject.jsx:3267: Saved analyses do not expose the AI column when the AI service is unconfigured. → Fixed in 7dbbb12: the column, its filter and the Target Code icon show on a project with analyses, configured or not. Running and retrying stay with users who can run the AI.
findings.json
{
"reviewer": "codex-cli 0.163.0",
"perspective": "defects",
"mode": "verify",
"commit": "de6c38d784ee8b7830bf2e81f14690d00082bde2",
"verdict": "findings",
"prior": [
{
"id": "F1.1",
"status": "resolved"
},
{
"id": "F1.2",
"status": "resolved"
},
{
"id": "F1.3",
"status": "resolved"
},
{
"id": "F1.4",
"status": "resolved"
}
],
"findings": [
{
"id": "F2.1",
"severity": "P1",
"tag": null,
"file": "src/components/map-projects/MapProject.jsx",
"line": 3249,
"claim": "The disagreement filter stops filtering while remaining visibly selected.",
"scenario": "Select the disagreement filter, then rerun analysis so the last disagreement becomes Agrees or Out of date. aiDisagreesCount becomes zero, bypassing the filter and displaying every otherwise-visible row, while the chip remains filled and selected with count zero. Reproduced using the actual getRows function.",
"action": "Apply the predicate whenever the filter is selected, or explicitly deactivate the filter when its count reaches zero so its displayed state matches its behavior.",
"lines_delta": null,
"confidence": "high"
},
{
"id": "F2.2",
"severity": "P2",
"tag": null,
"file": "src/components/map-projects/autoMatchRows.js",
"line": 185,
"claim": "The mapping guard cannot distinguish different targets that lack URLs, allowing AutoMatch to overwrite a user's change.",
"scenario": "A run proposes bridge target A whose lookup has not supplied an ocl_url. The user maps target B, also without a URL, keeping the same map type and approval state. Both snapshots have url:null, so isSameRowMapping returns true. When the AI returns REJECT, proposeRowMappingAfterAI removes the user's mapping to B. This was reproduced with the actual re-decision function.",
"action": "Include a stable target identity, such as source/repository plus concept code when the URL is absent, in mapping snapshots and comparisons. Add coverage for changing between two URL-less targets.",
"lines_delta": null,
"confidence": "high"
},
{
"id": "F2.3",
"severity": "P2",
"tag": null,
"file": "src/components/map-projects/MapProject.jsx",
"line": 3267,
"claim": "Saved analyses do not expose the AI column when the AI service is unconfigured.",
"scenario": "Open a project containing saved analyses after AI_ASSISTANT_API_URL has been removed or on an installation without that setting. The outer URL check hides the AI column and forces the disagreement count to zero, although the recommendation rule still uses those analyses. This violates the stated choice to show the column on projects with analyses.",
"action": "Show analysis-derived indicators independently of service configuration; gate only the ability to run or retry analysis. Apply the same distinction to the Target Code pick icon.",
"lines_delta": null,
"confidence": "high"
}
]
}…sis returns the badges to the latest, a row without candidates keeps its out-of-date analysis - Closing the full explanation after paging back to an older analysis returns the candidate badges to the latest analysis, which the order, the summary line and the grid follow. (F3.1) - A row whose candidates a refresh cleared, with saved analyses, still gets a recommendation: Out of date, with its summary line, rather than nothing. (F3.2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm
paynejd
left a comment
There was a problem hiding this comment.
Codex review, pass 3 (codex-cli 0.163.0, commit 7dbbb12)
Verdict: findings, 2 finding(s); earlier findings: F1.1 resolved, F1.2 resolved, F1.3 resolved, F1.4 resolved, F2.1 resolved, F2.2 resolved, F2.3 resolved.
- F3.1 (P1)
src/components/map-projects/Candidates.jsx:640: Closing analysis history leaves candidate badges identifying an older pick. → Fixed in 17f2698: closing the explanation returns the analysis page, and so the candidate badges, to the latest analysis. - F3.2 (P2)
src/components/map-projects/rowQuality.js:111: The recommendation cache hides saved analyses when candidate state is absent. → Fixed in 17f2698: the per-row cache computes a row that has analyses but no candidate state, so the row shows Out of date with its summary line. Covered by a new cache test.
findings.json
{
"reviewer": "codex-cli 0.163.0",
"perspective": "defects",
"mode": "verify",
"commit": "7dbbb12b4de5fd70c0e2775be310e6bd6d4935bf",
"verdict": "findings",
"prior": [
{
"id": "F1.1",
"status": "resolved"
},
{
"id": "F1.2",
"status": "resolved"
},
{
"id": "F1.3",
"status": "resolved"
},
{
"id": "F1.4",
"status": "resolved"
},
{
"id": "F2.1",
"status": "resolved"
},
{
"id": "F2.2",
"status": "resolved"
},
{
"id": "F2.3",
"status": "resolved"
}
],
"findings": [
{
"id": "F3.1",
"severity": "P1",
"tag": null,
"file": "src/components/map-projects/Candidates.jsx",
"line": 640,
"claim": "Closing analysis history leaves candidate badges identifying an older pick.",
"scenario": "An older analysis picks A and the latest picks B. Open Read analysis, navigate to the older entry, then close it. analysisPage remains on the older entry, so A retains the pick badge while the summary, pick section and grid identify B. This persists after the historical explanation is hidden.",
"action": "Reset analysisPage to the latest entry when closing the explanation, so the visible badges agree with the current recommendation.",
"lines_delta": null,
"confidence": "high"
},
{
"id": "F3.2",
"severity": "P2",
"tag": null,
"file": "src/components/map-projects/rowQuality.js",
"line": 111,
"claim": "The recommendation cache hides saved analyses when candidate state is absent.",
"scenario": "Refresh an analysed row in a single-algorithm project, and let the matching service remain throttled. Refresh deletes the row state, and the throttled response does not recreate it. This early return suppresses the saved analysis: the AI column becomes empty instead of Out of date, and the summary and Read analysis link disappear.",
"action": "Compute the recommendation for rows with saved analyses even when rowState is absent, using an empty candidate list and an unavailable fingerprint. Add coverage for this cache case.",
"lines_delta": null,
"confidence": "high"
}
]
}…e to viewBuilders.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm
paynejd
left a comment
There was a problem hiding this comment.
Simplification review (claude subagent, commit 17f2698)
4 possible cut(s):
- S1 (shrink)
src/components/map-projects/viewBuilders.js:734: AI_DISAGREEMENTS is exported but only isAIDisagreement uses it, in the same file. → Applied in 6e713b6. - S2 (shrink)
src/components/map-projects/autoMatchRows.js:162: getAutoMatchLogExtras returns the same shape from three branches; the early pick_not_candidate branch only differs by a flag. → Declined: three short branches read more clearly than the combined spread. - S3 (dup-test)
src/components/map-projects/__tests__/recommendationRule.test.js:232: The INSUFFICIENT test repeats the REJECT test at line 214 with a different verdict string; the code path is the same table lookup. → Declined: the acceptance criteria ask for a test per row of the ticket's table, and REJECT and INSUFFICIENT are separate rows. - S4 (dup-test)
src/components/map-projects/__tests__/recommendationRule.test.js:185: The Low Ranked pick test asserts the same proposal, chosenBy, agreement and pickRank as the different-pick test at line 168, with only the score data changed. → Declined: it pins "the AI's pick, in any score bucket" with a Low Ranked pick.
Codex check of commits 7dbbb12..6e713b6 (codex-cli 0.163.0): the pass 3 fixes and the S1 cut.
Verdict: clean.
simplify.json and check.json
{
"reviewer": "claude subagent",
"perspective": "simplify",
"mode": "full",
"commit": "17f26981d73a6b15e17f57986368aed7b29cb4b0",
"verdict": "findings",
"prior": [],
"findings": [
{
"id": "S1",
"severity": null,
"tag": "shrink",
"file": "src/components/map-projects/viewBuilders.js",
"line": 734,
"claim": "AI_DISAGREEMENTS is exported but only isAIDisagreement uses it, in the same file.",
"action": "Make it a non-exported const, or inline the array into isAIDisagreement.",
"lines_delta": -1,
"confidence": "high",
"scenario": null
},
{
"id": "S2",
"severity": null,
"tag": "shrink",
"file": "src/components/map-projects/autoMatchRows.js",
"line": 162,
"claim": "getAutoMatchLogExtras returns the same shape from three branches; the early pick_not_candidate branch only differs by a flag.",
"action": "One return: { chosen_by, ...(chosenBy === 'ai_assistant' || agreement === 'pick_not_candidate' ? { verdict } : {}), ...(chosenBy === 'ai_assistant' && confidence ? { confidence } : {}), ...(agreement === 'pick_not_candidate' ? { ai_pick_not_candidate: true } : {}) }, or drop the log key if no consumer reads it.",
"lines_delta": -3,
"confidence": "low",
"scenario": null
},
{
"id": "S3",
"severity": null,
"tag": "dup-test",
"file": "src/components/map-projects/__tests__/recommendationRule.test.js",
"line": 232,
"claim": "The INSUFFICIENT test repeats the REJECT test at line 214 with a different verdict string; the code path is the same table lookup.",
"action": "Fold into one parameterised test over ['REJECT','INSUFFICIENT'] checking agreement no_match/not_enough.",
"lines_delta": -6,
"confidence": "medium",
"scenario": null
},
{
"id": "S4",
"severity": null,
"tag": "dup-test",
"file": "src/components/map-projects/__tests__/recommendationRule.test.js",
"line": 185,
"claim": "The Low Ranked pick test asserts the same proposal, chosenBy, agreement and pickRank as the different-pick test at line 168, with only the score data changed.",
"action": "Merge into the line 168 test by adding the Low Ranked data as a second case.",
"lines_delta": -6,
"confidence": "low",
"scenario": null
}
]
}{
"reviewer": "codex-cli 0.163.0",
"perspective": "defects",
"mode": "cuts",
"commit": "6e713b64f44ee400c0d0b004ddb0ba6a19ba4d27",
"verdict": "clean",
"prior": [
{
"id": "F3.1",
"status": "resolved"
},
{
"id": "F3.2",
"status": "resolved"
}
],
"findings": []
}…nceptLab/ocl_online#465) Conflicts: the autoMatchRows import list and the assistant-naming test's key list; both keep main's entries and add this PR's. The run snapshot now follows main's runRows, which are worked out before the run starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm
paynejd
left a comment
There was a problem hiding this comment.
Codex check of commits 6e713b6..e5e5ce6 (codex-cli 0.163.0, commit e5e5ce6): the merge of main, which brings in the AutoMatch dialog rework (OpenConceptLab/ocl_online#465). Only the two import and key lists conflicted. The check looked for places where main's run-row selection and status clearing combine wrongly with this PR's run snapshot and proposals.
Verdict: clean. Lint and CI pass, as do 603 unit and 6 integration tests, and the production build succeeds.
merge-check.json
{
"reviewer": "codex-cli 0.163.0",
"perspective": "defects",
"mode": "cuts",
"commit": "e5e5ce6c53c0850d316a0fdd142a18113e0580b2",
"verdict": "clean",
"prior": [],
"findings": []
}…nt, token segregation) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Tried this locally on a project whose first row has a precomputed analysis. The grid behaves as the rule says. Row 1 is mapped to CIEL:168270 (the top score). The OCL AI Assistant picked CIEL:1065, but the candidates have changed since, so the column reads "Out of date" and Target Code has no AI icon. That's correct under the rule, but two things make that row easy to miss (screenshots in the next comment):
- The "Out of date" tooltip doesn't name the pick. Other states put "the other side" in the tooltip; this one only says the score decides. In the grid you can't tell the AI preferred something else without opening the row.
- "Out of date" rows aren't in the "OCL AI Assistant disagrees" filter. These are the rows where an older analysis disagreed and nobody has looked again, so they're arguably the ones a reviewer most wants to find. If keeping them out is deliberate, a separate "Out of date (n)" filter would help.
Neither blocks the PR.
|
|
||
| // The tooltip names the other side: the top score, and where the pick ranks. | ||
| const getStatusTooltip = (t, status, recommendation, candidatesScore) => { | ||
| if(status === 'out_of_date') return t('map_project.ai_out_of_date_help') |
There was a problem hiding this comment.
The out-of-date tooltip could include the old pick and its rank, as different_pick does, e.g. "Picked CIEL:1065 (rank 3); the candidates changed since, so the score decides". recommendation.pick and pickRank are still set for out-of-date analyses, so the data is already here.
| } | ||
|
|
||
| // The agreement states the grid's "OCL AI Assistant disagrees" filter shows. | ||
| const AI_DISAGREEMENTS = ['different_pick', 'pick_not_candidate', 'no_match', 'not_enough'] |
There was a problem hiding this comment.
AI_DISAGREEMENTS leaves out out_of_date, even when the old pick differs from the mapped target. Worth either counting those rows here (when pick isn't the top) or giving them their own filter.


Implements OpenConceptLab/ocl_issues#2908 and closes OpenConceptLab/ocl_issues#2333. The reranker refactor and the broader reasoning layer stay in OpenConceptLab/ocl_issues#2593.
The rule. If the OCL AI Assistant has analysed the row's current candidates, it decides what is proposed, and the score orders everything else. If it hasn't, the score decides, as before. The rule is one function,
getRowRecommendationinviewBuilders.js. The panel's order, the grid's columns and filter, and AutoMatch's proposal all read its result, so the logic lives in one place.Commits
getRowRecommendation), with a test for each row of the ticket's table. The replay over saved projects is on the ticket (aggregates only).chosen_by(ai_assistant|score) and the verdict. A row left unmapped by REJECT, by INSUFFICIENT, or by a pick that isn't a candidate is logged asauto_match_no_proposal.Choices where the ticket is silent
Also fixed on the way
&reaches the grid encoded once (%26) from one place and twice (%2526) from another. The Target Code icon compares URLs decoded.Tests
recommendationRule.test.js(40 tests) androwQuality.test.js(the new per-row cache).npm run test:allgives 603 unit and 6 integration tests, all passing, after merging main (e5e5ce6). CI's lint passes.mainwere built and run against a local mock API serving a read-only copy of a 20-row production project with current analyses. This checked:chosen_by: ai_assistant.Review
Docs
The rule is documented in the Mapper pipeline docs, an acceptance criterion. They're private team docs.
Still open
🤖 Generated with Claude Code
https://claude.ai/code/session_01HvdynUEUxPByhEuU5iFvtm