Skip to content

feat: workspace skills and knowledge first, a starter after linking, and the start-screen box back - #1435

Merged
sahrizvi merged 5 commits into
mainfrom
feat/workspace-starter-precedence
Oct 9, 2026
Merged

sahrizvi merged 5 commits into
mainfrom
feat/workspace-starter-precedence

Conversation

@sahrizvi

@sahrizvi sahrizvi commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Issue for this PR

No GitHub issue; tracked internally.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Three commits.

1. Skill source labels and a precedence rule.

  • Every skill the agent sees now carries source="workspace|project|built-in|personal|other", set from where altimate-code found it (altimate/skill-source.ts).
  • The skills preamble says to prefer them in that order and not to ask "which team's skill?".
  • Before this, a workspace skill, a repo skill and another team's copy looked identical. With near-duplicates the agent asked which team's policy to follow, or went exploring.
  • Listings stay in name order. An earlier version sorted by source; that pushed the built-ins behind every project skill and out of the skill tool's 50-entry list, and they were loaded less (12/30 vs 18/30).

2. The start-screen workspace box is back.

  • 0.12.6 removed it because every keybinding died once it loaded. I bisected with builds.
  • Cause: the view's root was a <Show> that renders nothing until the binding resolves. OpenTUI's Slot shows the fallback for an empty first render and renders the view again when its output changes, so each arrival mounted a fresh view that started empty and resolved again. The lines never stayed and the box remounted without end.
  • Fix: WelcomeBlock keeps a <box> root.
  • The upstream sidebar views with a <Show> root (todo, files, mcp) read shared state, so they remount once rather than loop; I left them alone.

3. A fixed-format workspace starter, and knowledge used without being asked.

  • After a link, a deterministic message: which workspace, its skills, knowledge, integrations, memory setting, and up to three prompts. No model call.
  • Shown by altimate-code link, in the TUI's post-link dialog, and from a new /workspace → Get started. Picking a prompt fills the input without sending it.
  • GET /altimate/workspace/starter serves it for the IDE extension's chat. The extension still needs to call it.
  • When the workspace has knowledge, the workspace section tells the agent to check it (ask_knowledge_base) for best practices and conventions unprompted.

How did you verify your code works?

  • Tests: new tests for each part. 13 mutation checks, all caught: removing the labels, the rule, the knowledge line, the route's stale-link check, the welcome <box> root, and so on.
  • Full suite: 9 failures and 1 error. I ran the 9 failing tests on a clean origin/main and they fail there too.
  • Routing harness, workspace asks: 11/11, against 8/12 on 0.12.6.
  • Routing harness, built-in asks: 18/30, the same as 0.12.6.
  • Over-steering: no built-in ask loaded a workspace skill.
  • Harness setup: a real linked workspace with 6 skills, plus 150 other-team near-duplicates in .claude/skills, using altimate-backend/altimate-default.
  • Knowledge: ask_knowledge_base called unprompted on 4/4 best-practice questions, against 0/4 on 0.12.6 with the tool available.
  • E2E on a test tenant:
    • headless and TTY link print the starter;
    • the route returns linked, unlinked, and 403 for a browser origin;
    • TUI: the link dialog shows the prompts, a chosen prompt fills the input, and /workspace → Get started works;
    • the welcome box renders and Ctrl+P opens the palette, checked in tmux.

Screenshots / recordings

Terminal captures from tmux are in the verification notes above. No recording.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code

https://claude.ai/code/session_018fJ3X7pcGT4R9yzjsJnqsV


Summary by cubic

Makes the linked workspace's own skills and knowledge win over near-duplicates and general advice, shows a fixed starter message after linking, and brings back the start-screen workspace box without the dead keybindings that took it out in 0.12.6.

Skills: sources and precedence

  • Every skill in the agent's listings now carries source="workspace|project|built-in|personal|other", set from where altimate-code found it.
  • workspace is only this project's own snapshot, judged on both the matched and resolved path; a session started in a subdirectory of the repository still owns its snapshot there. A symlink or configured path into another project's snapshot is other, and a project skill with the exact name of a workspace skill replaces it.
  • The skills preamble tells the agent to load the highest-source skill that fits a task and not to ask which team's skill it is.
  • Listings stay in name order; sorting by source pushed built-ins out of the skill tool's 50-entry list, and they were loaded less.

Starter, knowledge, and the welcome box

  • After a link, the TUI's post-link dialog and /workspace → Get started show the workspace's skills, knowledge, integrations, memory setting, and up to three prompts; the dialog opens at once and the starter fills in when it arrives. Prompts land below the existing rows, so a late arrival doesn't move the selection, and choosing one fills the input without sending it.
  • GET /altimate/workspace/starter serves the same message to the IDE extension's chat, which still needs to call it.
  • When the workspace has knowledge, the agent is told to check it for best practices and conventions unprompted.
  • The welcome box is registered again with a <box> root, which stops OpenTUI from remounting it endlessly and killing keybindings, and it now refreshes right away on an in-process link or unlink.

Written for commit fef6096. Summary will update on new commits.

View guided diff Turn on auto-fix

sahrizvi and others added 3 commits October 8, 2026 17:38
…refer

The skill list the agent saw rendered a workspace skill, a project skill, a
built-in and another team's copy identically. With near-duplicates in the
catalog it asked "which team's policy?" or explored the repo instead of
loading the workspace's own skill.

- Every registered skill gets a `source` (`workspace`, `project`, `built-in`,
  `personal`, `other`), decided from its location (`altimate/skill-source.ts`).
  It is on `Skill.Info`, so `/skill` returns it too.
- Both listings render it as `<skill source="…">`; a description cannot
  forge one, since `skill` is already a neutralised tag.
- The skills preamble states the order: workspace, project, built-in,
  personal, other; prefer the highest that fits and do not ask which team's
  skill to use.
- Listings stay in name order. Ordering by source moved the built-ins behind
  every project skill and out of the skill tool's 50-entry list, and they
  were loaded less (12/30 against 18/30).

Measured with the routing harness against a real workspace snapshot and 150
other-team near-duplicates in `.claude/skills`: workspace asks 11/11 (0.12.6:
8/12), built-in asks 18/30 (0.12.6: 18/30), no built-in ask loaded a
workspace skill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fJ3X7pcGT4R9yzjsJnqsV
…eybindings

0.12.5's workspace box under "What is Altimate Code" left every keybinding
(Ctrl+C, Ctrl+P, Esc) dead, and 0.12.6 took it out. Bisected with builds of
the box: the binding lookup and the polling were fine; the view's root was a
`<Show>` that renders nothing until the binding resolves. OpenTUI's `Slot`
shows the fallback for a view whose first render is empty and renders the
view again when its output changes, so the lines arriving mounted a new view
that started empty and resolved again: the lines never stayed and the box
remounted without end.

- `WelcomeBlock` keeps a `<box>` root, and the box is registered again.
- Tests mount the real slot machinery with lines that arrive after mount:
  the block shows them and mounts once; a bare `<Show>` root is pinned as
  remounting, so an OpenTUI change is noticed.

Checked in tmux on a linked project: the lines show and Ctrl+P opens the
command palette.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fJ3X7pcGT4R9yzjsJnqsV
…used unasked

After a link the user got "Done." and nothing about what the workspace
brings. And the agent searched workspace knowledge only when told to.

- `altimate/workspace/starter.ts` renders "You're working in the X
  workspace", its skills, knowledge, integrations and memory setting, and up
  to three prompts. Built from the same data as the prompt's workspace
  section, with no model call; anything not known is left out.
- `altimate-code link` prints it after every successful link (a framed note
  in a terminal, plain lines otherwise).
- TUI: the post-link dialog shows the summary and offers the prompts;
  choosing one fills the input without sending it. `/workspace` gains
  **Get started**. The start-screen box points to it.
- `GET /altimate/workspace/starter` serves it to the IDE extension's chat:
  `linked: false` for an unlinked project, 503 for a link that could not be
  confirmed, same origin rules as the other workspace routes.
- The workspace section tells the agent to check the workspace's knowledge
  (`ask_knowledge_base`) for best practices and conventions without being
  asked, when the workspace has knowledge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fJ3X7pcGT4R9yzjsJnqsV
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4bfc6370-70a2-48f0-81fc-08bd12b5302d

📥 Commits

Reviewing files that changed from the base of the PR and between b9173a2 and fef6096.


📒 Files selected for processing (24)
  • docs/docs/configure/skills.md
  • docs/docs/usage/cli.md
  • packages/opencode/src/altimate/skill-source.ts
  • packages/opencode/src/altimate/workspace/contents.ts
  • packages/opencode/src/altimate/workspace/skill-sync.ts
  • packages/opencode/src/altimate/workspace/starter.ts
  • packages/opencode/src/altimate/workspace/welcome-lines.ts
  • packages/opencode/src/cli/cmd/link.ts
  • packages/opencode/src/plugin/tui/altimate/index.ts
  • packages/opencode/src/plugin/tui/altimate/workspace-welcome.tsx
  • packages/opencode/src/plugin/tui/altimate/workspace.tsx
  • packages/opencode/src/server/server.ts
  • packages/opencode/src/session/system.ts
  • packages/opencode/src/skill/index.ts
  • packages/opencode/src/tool/skill.ts
  • packages/opencode/test/altimate/plugin/tui-plugin-list.test.ts
  • packages/opencode/test/altimate/plugin/workspace-welcome-slot.test.tsx
  • packages/opencode/test/altimate/skill-source.test.ts
  • packages/opencode/test/altimate/workspace/contents.test.ts
  • packages/opencode/test/altimate/workspace/skill-sync.test.ts
  • packages/opencode/test/altimate/workspace/starter.test.ts
  • packages/opencode/test/server/altimate-workspace-routes.test.ts
  • packages/opencode/test/session/system.test.ts
  • packages/opencode/test/skill/skill.test.ts

 _________________________________
< Please hold while I ignore you. >
 ---------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR

🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

- The starter's "skills not synced" line names `workspace refresh`, which
  pulls skills; it said `workspace sync`, which sends memory.
- The post-link TUI confirmation opens at once and the starter fills in
  when it arrives. It used to wait up to ~12s with nothing on screen.
- That wait joins only a skill sync already running (`pendingSync`); it no
  longer starts a second one when the link's sync has finished.
- `workspace` is now only this project's own snapshot, judged on both the
  matched and the resolved path. A configured path into another project's
  snapshot, or a symlink into or out of this one, is `other`.
- The built-in check anchors installed copies to `<home>/.altimate/builtin`,
  and skill-use telemetry shares the same predicate.
- Integration ids go through `clean()` now that the starter prints them to
  the terminal; `source` goes through `escapeSkillAttr`.
- Docs: a project skill with exactly a workspace skill's name replaces it.
- The start-screen box follows a link or unlink made in this process at
  once, instead of saying "not linked" under the link confirmation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fJ3X7pcGT4R9yzjsJnqsV

@ralphstodomingo ralphstodomingo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Requesting changes for one thing: when a session runs from a subdirectory of a git repo, the workspace's own skills get the label other. That is the lowest source, so the new rule tells the agent to prefer everything else over them. The rest holds up, including the welcome-box fix.

What I checked at cb94222b

  • The welcome box. I read the Slot in OpenTUI 0.3.4. renderEntry checks the view's first output inside the entry's memo. A <Show> root that fills in later makes that memo run again, which calls the plugin's renderer and mounts a new, empty view. A <box> root gives the memo nothing to re-run on. workspace-welcome-slot.test.tsx runs the real slot code with both roots.
  • The labels can't be forged. source comes from a fixed list, is set from where discovery found the file, and is escaped in the listing. classifySkillSource now uses the same built-in check, so telemetry and the listing agree.
  • The starter after a TUI link waits on the bind's sync under the same realpath key the bind uses, so pendingSync finds it. The raw-client call follows the pattern skill-ops already uses.
  • The 9 test files this PR touches pass locally (263 tests), the Marker Guard passes, and CI is green.

Blocking: in a subdirectory session, workspace skills are labelled other (skill-source.ts:60, inline). link records the project at the directory it runs in (resolveProjectIdentifier returns the realpath of that directory), and the prompt's sync writes the snapshot under Instance.directory. But skillSource counts a snapshot as the workspace's only when it belongs to projectRoot, and that is the git worktree. Take a project in repo/dbt, linked from there: the snapshot is at repo/dbt/.altimate-code/skill/_workspace, and every workspace skill is labelled other.

I checked this with the branch's own Skill.Service, using the fixture from "each skill is labelled with where it was found" with the session in repo/sub:

  • Linked at the repo root, the workspace skill is workspace.
  • Linked at repo/sub, it is other, while the repo's .claude/skills skill is project, so the rule ranks the repo's skill above the workspace's.

For this audience, a dbt project in a subfolder of a larger repo is a common layout. Counting the session directory as an owner as well fixes it (diff inline). With that change both cases come out workspace, and skill-source.test.ts, skill.test.ts and system.test.ts still pass.

Non-blocking

  • The selection jumps when the starter arrives (workspace.tsx:435, inline).
  • The Skill.state factory is now at cognitive 26, up from 23. The labelling loop could move into skill-source.ts as one call, which would also shrink the diff in this upstream-shared file.
Appendix — complexity delta

b9173a2dd9 → cb94222b40 · only functions this diff touches · advisory, not a gate.

Function File Cognitive CCN Status
Skill.state factory L436 src/skill/index.ts 23 → 26 ▲ 14 → 16 ▲ hard to follow → needs decomposition
skillSource L51 src/altimate/skill-source.ts new → 9 new → 17 new ≥15 CCN — easy
summaryOf L80 src/altimate/workspace/starter.ts new → 10 new → 10 new — easy
title L394 src/plugin/tui/altimate/workspace.tsx 2 → 4 ▲ 4 → 8 ▲ trivial
bindOrRebind L768 src/cli/cmd/link.ts 41 = 24 → 26 ▲ needs decomposition (unchanged)
runBrowserHandoff L441 src/cli/cmd/link.ts 15 = 15 → 17 ▲ moderate
classifySkillSource L26 src/tool/skill.ts 2 = 6 → 3 ▼ trivial

Summary: 32 touched · 12 rose · 4 improved · 31 new (max cognitive 10) · net cognitive Δ +86. Some rows are left out because the tool assigns their scores to the wrong function: rebindByMatchedIdentifier (5 → 20, CCN 286) and handoffFailureMessage, which this PR doesn't touch, plus the anonymous-callback rows other than Skill.state.

if (matchedProject !== null || realProject !== null) {
const ours = (project: string | null, root: string | undefined) =>
project !== null && root !== undefined && path.resolve(project) === path.resolve(root)
return ours(matchedProject, ctx.projectRoot) && ours(realProject, ctx.realProjectRoot ?? ctx.projectRoot)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A snapshot counts as the workspace's only when it belongs to projectRoot, which Skill.state sets to the git worktree. But link records the project at the directory it runs in, and the prompt's sync writes the snapshot under Instance.directory. So a project linked from repo/dbt has its snapshot at repo/dbt/.altimate-code/skill/_workspace, and every skill in it is labelled other. The rule then ranks the repo's own .claude/skills above it.

I reproduced this with the "each skill is labelled with where it was found" fixture, with the session in repo/sub and the snapshot written there: workspace-one came out other and project-one came out project.

Counting the session directory as an owner as well fixes it. With this change both cases are workspace, and the existing tests still pass:

// SkillSourceContext: directory?: string; realDirectory?: string
const ours = (project: string | null, roots: (string | undefined)[]) =>
  project !== null && roots.some((root) => root !== undefined && path.resolve(project) === path.resolve(root))
return ours(matchedProject, [ctx.projectRoot, ctx.directory]) &&
  ours(realProject, [ctx.realProjectRoot ?? ctx.projectRoot, ctx.realDirectory ?? ctx.directory])
  ? "workspace"
  : "other"

In Skill.state, pass directory: ctx.directory and realDirectory: resolve(ctx.directory). A test like the one in skill.test.ts, with the session and snapshot in a subdirectory, would pin it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in fef6096. The session directory now counts as an owner of the snapshot alongside the project root, as you suggested (skillSource takes directory/realDirectory, passed from ctx.directory in Skill.state). Added your case to skill.test.ts (session and snapshot in sub/: workspace-one → workspace, project-one → project); it fails without the change.

title={title()}
options={options()}
current={props.manageUrl ? "open" : "done"}
current={props.starter()?.prompts.length ? "prompt:0" : props.manageUrl ? "open" : "done"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking. DialogSelect moves the selection whenever current changes (dialog-select.tsx:228), and the prompts are added above the existing rows. The starter can take a few seconds to arrive: up to 5 s for the sync, then the route's own waits. If someone has already moved to "Continue editing in browser" by then, the cursor jumps to the first prompt, so pressing Enter fills the input instead of opening the browser. The selection is tracked by row index, so adding rows above it shifts it too. Moving to the first prompt only when the user hasn't moved yet would avoid both.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in fef6096, and reproduced first: moving to Done before the starter arrived, then Enter, filled the input. Two changes: the prompts now go below the existing rows, so a late arrival can't shift the index-tracked selection; and the cursor jumps to the first prompt only if the user hasn't moved off the default row. That also needed current as a createMemo, because the dialog re-applies current on every notification and a plain getter pulled the cursor back as soon as the user moved. Checked in tmux: moved → Enter on Done closes with an empty input; not moved → it lands on the first prompt.

…ace's

Review on the starter and skill-label change:

- A project linked from a subdirectory of its repository keeps its snapshot
  under the session directory, while the registry takes the git worktree as
  the project. Its skills were labelled `other`, the lowest source. The
  session directory now also counts as an owner of the snapshot.
- The post-link dialog no longer pulls the cursor off a row the user moved to
  before the starter arrived. The prompts are added below the existing rows,
  so a late arrival cannot shift the selection, and `current` is a memo,
  because the dialog re-applies it on every notification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fJ3X7pcGT4R9yzjsJnqsV

@ralphstodomingo ralphstodomingo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving at fef6096f. Both points from my last review are fixed.

  • A subdirectory session's snapshot is now labelled workspace. skillSource counts the session directory as an owner as well as the project root, on both the matched and the resolved path. The new skill.test.ts case runs the real registry with the session and snapshot in sub/. When I removed the directory from the owners, it failed, along with the new skill-source.test.ts case.
  • The dialog no longer moves the cursor under the user. The prompts now come after Done. DialogSelect groups rows by category in insertion order, so they land below the existing rows and can't shift the index-tracked selection. Making current a memo is the right call. onMove sets movedEarly, and with a plain getter that change would have re-fired the dialog's on(() => props.current) effect and pulled the cursor back to the default row. The programmatic moves don't trip it: the first move is to the default row, and the jump to the first prompt happens after the starter has arrived.

The 9 test files this PR touches pass locally (265 tests), the Marker Guard passes, and CI is green.

Appendix — complexity delta

b9173a2dd9 → fef6096f61 · only functions this diff touches · advisory, not a gate.

Function File Cognitive CCN Status
Skill.state factory L436 src/skill/index.ts 23 → 26 ▲ 14 → 16 ▲ hard to follow → needs decomposition
skillSource L56 src/altimate/skill-source.ts new → 9 new → 18 new ≥15 CCN — easy
summaryOf L80 src/altimate/workspace/starter.ts new → 10 new → 10 new — easy
title L394 src/plugin/tui/altimate/workspace.tsx 2 → 4 ▲ 4 → 8 ▲ trivial
bindOrRebind L768 src/cli/cmd/link.ts 41 = 24 → 26 ▲ needs decomposition (unchanged)
runBrowserHandoff L441 src/cli/cmd/link.ts 15 = 15 → 17 ▲ moderate
classifySkillSource L26 src/tool/skill.ts 2 = 6 → 3 ▼ trivial

Summary: 34 touched · 12 rose · 6 improved · 32 new (max cognitive 10) · net cognitive Δ +84. As in the last round, rebindByMatchedIdentifier and handoffFailureMessage are left out because the tool assigns other functions' scores to them (this PR doesn't touch either), and so are the anonymous-callback rows other than Skill.state.

@sahrizvi
sahrizvi marked this pull request as ready for review October 9, 2026 13:53
@sahrizvi
sahrizvi merged commit 714de61 into main Oct 9, 2026
24 of 25 checks passed
@altimate-harness-bot

Copy link
Copy Markdown
Contributor

🔁 @sahrizvi — this change was promoted to the help-docs hub: https://github.com/AltimateAI/help-docs/pull/119 — please review/merge to keep the docs in sync.

@kilo-code-bot

kilo-code-bot Bot commented Oct 9, 2026

Copy link
Copy Markdown

Code Review Summary

The review did not run because the selected model is no longer available.

Choose another model in Kilo Code review settings: https://app.kilo.ai/code-reviews

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.

2 participants