Skip to content

refactor(office): retire bundled OfficeCLI for native office-* skills (#1273 PR4) - #1494

Merged
Astro-Han merged 45 commits into
devfrom
feat/1273-office-route-switchover
Jul 9, 2026
Merged

refactor(office): retire bundled OfficeCLI for native office-* skills (#1273 PR4)#1494
Astro-Han merged 45 commits into
devfrom
feat/1273-office-route-switchover

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

Full retirement of the bundled OfficeCLI in favor of the native python office-* skills (office-docx / office-xlsx / office-pptx / office-pdf). This is the switchover that follows PR3's dark-launch (#1491).

The change lands as four atomic concerns, one per commit, plus follow-up commits that harden the shell artifact-capture parser in response to the multi-reviewer rounds:

  1. routing + skills — promote office-* from preview to default in the core pawwork.txt guidance and each SKILL.md; delete the upstream-synced officecli-* skill bundle and the coupled morph-ppt / morph-ppt-3d skills (which cannot run without the officecli binary).
  2. bundling + scripts + CI — remove the officecli bundled-tool entry, prepare-officecli / sync-officecli-skills (+ tests), the officecli-bump auto-sync workflow, and the Prepare OfficeCLI steps / bundle smoke assertions in build.yml and desktop-smoke.yml. Retarget the release and workflow contract tests to the remaining bundled tool, uv.
  3. tool-layer — remove the officecli command-parsing branch from the shell artifact-capture subsystem (target inference, OFFICECLI_SKIP_UPDATE wiring), keeping only the generic write-detection helpers the office-* path uses via declared outputs and auto-discovery.
  4. docs + site — drop the OfficeCLI attribution from README / CN README / marketing site / llms.txt / THIRD_PARTY_NOTICES while keeping the "handles Office files locally" capability claim.

Because commit 3 makes the generic shell artifact-capture path the sole route for office deliverables, the review rounds focused on its parser edge cases. Follow-up commits close the gaps that surfaced: versioned python3.12 generators, heredoc body vs. opener-line redirects, null-device redirects, hyphenated and backslash-quoted heredoc delimiters (<<PY-END, <<\PY), uv run subcommand gating that skips option values (--directory build), literal % filenames vs. %VAR%, unquoted POSIX escaped-space output paths (-o Quarterly\ Report.docx) while preserving Windows C:\out\report.docx separators, per-output counting so capturing one of several dynamic outputs cannot mask a sibling that escaped the cwd scan, and a heredoc-aware line-continuation strip so a trailing \ inside a heredoc body cannot swallow the following command from the side-effect audit. Each fix ships with a regression test.

Why

The office-* skills reach the same Word/Excel/PPT capability with thin, native python tooling (python-docx, openpyxl, an svg→pptx converter, bundled Chromium printToPDF) and no vendored binary or upstream sync contract. Keeping officecli in parallel is exactly the "stand up a parallel system" debt the project guards against. This is a deliberate, accepted regression: the ~5,300 lines of themed design knowledge that shipped inside officecli-pitch-deck / financial-model / academic-paper / word-form / data-dashboard go away with the bundle. The office-route and ppt-quality eval harnesses under packages/opencode/script/ are kept intact as the evidence base for choosing native over officecli.

Related Issue

#1273

Human Review Status

Pending

Review Focus

  • The tool-layer diff (commit 3 + the follow-up hardening commits): confirm the generic write-detection helpers still cover the office-* declared-output and auto-discovery paths now that the officecli-specific inference is gone, and that the parser edge-case fixes hold together (heredoc bounds, uv/python generator detection, dynamic-output counting, escaped-space paths).
  • That the two retained eval harnesses referencing officecli are intentional (decision evidence), not a missed removal.

Risk Notes

  • Accepted capability regression: themed design knowledge from the deleted officecli skills is gone (see Why).
  • Platform/packaging surface touched: build.yml and desktop-smoke.yml no longer prepare or smoke-check an officecli binary; uv remains bundled and its Prepare step / release contract are unchanged in behavior.
  • office-pdf degrades gracefully where printToPDF isn't wired; that wiring is a separate follow-up PR, out of scope here.

How To Verify

opencode typecheck (tsgo --noEmit): clean
desktop-electron typecheck (tsgo -b): clean
app typecheck (tsgo -b): clean
artifact-capture core (shell-office-artifacts + shell-artifact-orchestrator + shell-write-heuristic): 253 pass / 0 fail
full opencode test/tool suite: 782 pass / 1 skip, plus 2 pre-existing unrelated failures (anonymous websearch-quota tests, not in this diff)
desktop-electron release-workflow-contract.test.ts: 10 pass / 0 fail
orphan-ref sweep (skillsTarballSha256, officeCliTarget, prepareOfficeCli, isOfficeCliOutputPath, officeCliTargets, nonOfficeCliCommandText): none
officecli reference sweep: only the two eval harnesses + skill.test.ts removal-guard remain

skills-surface snap fixture: rename only (synthetic seed names, unchanged rendering logic and DOM assertions) — no product UI change, no snap regen needed.

Screenshots or Recordings

No product UI change. Site comparison-table cell swaps the "OfficeCLI" label for the same checkmark pattern the other capability rows already use.

Summary by CodeRabbit

  • Bug Fixes
    • Improved detection of Office deliverables (including PDFs) and generator-style output intent, leading to more accurate artifact “uncaptured” handling.
  • Documentation
    • Updated README, localized docs, website copy, and FAQs to remove bundled OfficeCLI references and clarify that Office files are read/written locally.
  • Tests
    • Updated desktop smoke/e2e and skills/session-related tests to use the new office-* skill naming.
  • Chores
    • Removed bundled OfficeCLI tooling and related automation; adjusted desktop workflow sequencing to prepare uv instead.

@Astro-Han Astro-Han added the task Narrow execution, audit, spike, migration, tracking, or upstream follow-up work label Jul 8, 2026
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Astro-Han, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 58dde839-140c-4de8-a25a-ac709d20171b

📥 Commits

Reviewing files that changed from the base of the PR and between 953a822 and 784bc39.

📒 Files selected for processing (2)
  • packages/opencode/src/tool/shell-office-artifacts.ts
  • packages/opencode/test/tool/shell-office-artifacts.test.ts
📝 Walkthrough

Walkthrough

This PR removes OfficeCLI-specific workflows, scripts, eval routes, and reference assets, while switching shell artifact handling to generic office output paths, renaming bundled skills to office-*, and updating docs and tests to match the new flow.

Changes

OfficeCLI cutover and office- migration*

Layer / File(s) Summary
Workflow and bundle cleanup
.github/workflows/build.yml, .github/workflows/desktop-smoke.yml, packages/desktop-electron/bundled-tools.json, packages/desktop-electron/package.json, packages/desktop-electron/scripts/release-workflow-contract.test.ts, packages/opencode/test/github/*.test.ts, packages/desktop-electron/scripts/prepare-uv.ts, packages/desktop-electron/tsconfig.release.json, packages/desktop-electron/scripts/prepare-officecli.ts, packages/desktop-electron/scripts/prepare-officecli.test.ts, packages/desktop-electron/scripts/sync-officecli-skills.ts, packages/desktop-electron/scripts/sync-officecli-skills.test.ts
Removes OfficeCLI preparation from workflows, deletes the bump workflow, drops the OfficeCLI bundle entry and sync script wiring, and updates workflow tests and pin assertions.
Docs, copy, and skill renames
README.md, README_CN.md, THIRD_PARTY_NOTICES.md, site/public/llms.txt, site/src/components/Home.astro, site/src/i18n.ts, packages/app/e2e/snap/skills-surface.snap.ts, packages/app/src/pages/skills/skill-presentation.ts, packages/opencode/test/session/session-skill.test.ts, packages/opencode/test/skill/skill.test.ts, packages/opencode/script/office-route-eval/*, packages/opencode/script/ppt-quality-eval/fixtures/*, packages/opencode/script/ppt-quality-eval/route-skills/svg-pptx-native/VENDORED.md
Rewrites product copy and notices to describe local Office handling, and renames seeded skills and assertions from officecli-* to office-* across the app and tests.

Shell office output migration

Layer / File(s) Summary
Office output helpers
packages/opencode/src/tool/shell-office-artifacts.ts, packages/opencode/test/tool/shell-office-artifacts.test.ts
Replaces OfficeCLI-target parsing with office output-path classification, generator detection, command segmentation, and generator-text filtering, and rewrites the helper tests around exact and discoverable outputs.
Shell orchestration and write detection
packages/opencode/src/tool/shell-artifact-orchestrator.ts, packages/opencode/src/tool/shell-write-heuristic.ts, packages/opencode/src/tool/shell.ts, packages/opencode/src/tool/shell-output-capture.ts, packages/opencode/test/tool/shell-artifact-orchestrator.test.ts, packages/opencode/test/tool/shell-write-heuristic.test.ts, packages/opencode/test/tool/shell.test.ts, packages/opencode/test/tool/shell-prompt.test.ts, packages/opencode/test/util/env.test.ts, packages/opencode/src/session/prompt.ts, packages/opencode/src/session/prompt/pawwork.txt, packages/opencode/src/tool/shell/prompt.ts, packages/opencode/src/util/env.ts, packages/opencode/src/util/uv-mirror.ts
Rewires artifact orchestration and write detection around parsed office outputs, generator-aware side effects, and the new capture rules, and updates shell wiring, environment, prompts, and tests.
Integration coverage
packages/opencode/test/tool/shell-artifact-orchestrator.test.ts
Adds coverage for exact -o capture, Python .save() outputs, chained side effects, and fallback discovery when the command does not expose the output path.

Eval routes and reference removals

Layer / File(s) Summary
Eval routes and PPT tooling
packages/opencode/package.json, packages/opencode/script/office-route-eval/*, packages/opencode/script/ppt-quality-eval/*, packages/opencode/script/ppt-quality-eval/scripts/check_pptx_assets.py, packages/opencode/script/ppt-quality-eval/route-skills/svg-pptx-native/scripts/svg_to_pptx/**, packages/opencode/script/ppt-quality-eval/route-templates/**
Removes the Office and PPT quality eval scripts, their fixtures, reports, route skills, support scripts, and the SVG-to-PPTX native conversion modules and package assembly code.
Morph-ppt reference content
skills/morph-ppt/**
Deletes the morph-ppt skill docs, reference helpers, style references, and OfficeCLI-based build scripts.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested labels: bug, enhancement, ci, P1, harness

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the core sections, but it omits the required Checklist block and all required checkbox confirmations. Add the full Checklist section from the template with the required boxes checked and ensure all required review/verification items are explicitly completed.
Docstring Coverage ⚠️ Warning Docstring coverage is 56.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: retiring bundled OfficeCLI in favor of native office-* skills.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/1273-office-route-switchover

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.

@github-actions github-actions Bot added ci Continuous integration / GitHub Actions app Application behavior and product flows ui Design system and user interface platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority labels Jul 8, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested priority: P2 (includes user-path files (packages/app/src/pages/skills/skill-presentation.ts)).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Astro-Han added 4 commits July 8, 2026 23:34
…skill bundle

Promote the native python office-* skills (office-docx/xlsx/pptx/pdf) from
dark-launched preview to the default route: rewrite their SKILL.md descriptions
and the core pawwork.txt guidance to point at them directly, and delete the
upstream-synced officecli-* skill bundle plus the coupled morph-ppt / morph-ppt-3d
skills, which cannot function without the officecli binary.

Update the skill discovery test to assert the office-* skills are present and to
guard that no officecli-*/morph-ppt skill can be re-vendored by a future sync.
… steps

Remove the officecli entry from bundled-tools.json and its download/prepare path,
delete prepare-officecli and sync-officecli-skills (plus tests), and drop the
officecli-bump auto-sync workflow. Remove the Prepare OfficeCLI steps and bundle
smoke assertions from build.yml and desktop-smoke.yml, and retarget the release
and workflow contract tests to the remaining bundled tool (uv).

uv stays bundled as the Python runtime the office-* skills depend on.
The shell artifact-capture subsystem special-cased officecli commands to infer
.docx/.xlsx/.pptx write targets. With officecli gone, that branch is dead: drop
the officecli command matchers, target inference, and OFFICECLI_SKIP_UPDATE env
wiring, keeping only the generic write-detection helpers the office-* skills rely
on through declared outputs and auto-discovery.

Rename isOfficeCliOutputPath to isOfficeOutputPath and prune the now-unused deps
from the orchestrator. Update the shell/env/heuristic tests accordingly and delete
the tests that only covered the removed officecli behavior.
PawWork still handles Word/Excel/PowerPoint locally, now through the native
office-* skills instead of the bundled OfficeCLI binary. Update the README, CN
README, marketing site copy, comparison table, and llms.txt to state local Office
handling without the OfficeCLI attribution, and remove the OfficeCLI section and
its copyright line from THIRD_PARTY_NOTICES.md since it is no longer bundled.

Rename the officecli-* seeds in the skills-surface snap fixture and its comment to
office-* so the gallery fixture stays representative; rendering logic is unchanged.
@Astro-Han
Astro-Han force-pushed the feat/1273-office-route-switchover branch from e66003e to 2feb4fb Compare July 8, 2026 15:36
Astro-Han added 3 commits July 8, 2026 23:45
The office-* SKILL.md bodies still told the model 'dark-launched preview / does
not replace the default path / only run when explicitly routed here / hard switch
lands with PR 4'. With this PR being that routing switch, those guards contradict
the promoted frontmatter and pawwork.txt and would make the model avoid the only
remaining Office route. Drop the preview paragraphs from all four skills and the
dark-launch caveat from office-pdf's print-fallback note.

Surfaced by Codex, Pi (GLM-5.2), and ChatGPT code review.
- office-route-eval read manifest.officecli.version, which throws now that the
  officecli entry is gone; degrade to "removed" so the retained evidence eval
  still runs.
- .pdf is a promoted Office deliverable (already listed in the expected_outputs
  description), so add it to officeOutputExtensions for consistent artifact
  detection and binary handling.
- rewrap a uv-mirror comment that split 'explicitly configured' across lines.

Surfaced by Codex, Pi (GLM-5.2), and ChatGPT code review.
office-* skills generate files via `uv run python ... -o out.pptx`, which
isLikelyWriteCommand did not flag (no shell redirect). With officecli's
exact-target capture removed, those artifacts escaped turn-file / artifact
tracking unless the model happened to set expected_outputs. Treat a named
office output path (.docx/.xlsx/.pptx/.pdf) on a python/uv/uvx command as the
write signal so artifact auto-discovery runs; test/lint commands like
`uv run pytest` name no such path and stay read-only.

Closes the last shared P2 from Codex / Pi (GLM-5.2) / ChatGPT review.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
packages/opencode/script/office-route-eval/eval.ts (1)

37-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Update OfficeRouteManifest type to reflect optional officecli.

The runtime code now safely handles a missing officecli key (lines 259), but the type definition at lines 37–41 still declares officecli as a required field. Since JSON.parse returns any, this won't cause a compile error, but the type is now misleading and could cause confusion for future maintainers.

♻️ Make the officecli field optional in the manifest type
   officecli: {
     manifestVersion: string
     binaryPath: string
     binaryVersion?: string
-  }
+  }?

Also applies to: 254-260

🤖 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/opencode/script/office-route-eval/eval.ts` around lines 37 - 41,
Update the OfficeRouteManifest type so officecli is optional instead of
required, matching the runtime handling in the manifest parsing logic. Adjust
the type definition near OfficeRouteManifest and keep the existing fallback
behavior in the JSON.parse-based code path that checks for a missing officecli
key, so the type accurately reflects the shape accepted by OfficeRouteEval.
🤖 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.

Nitpick comments:
In `@packages/opencode/script/office-route-eval/eval.ts`:
- Around line 37-41: Update the OfficeRouteManifest type so officecli is
optional instead of required, matching the runtime handling in the manifest
parsing logic. Adjust the type definition near OfficeRouteManifest and keep the
existing fallback behavior in the JSON.parse-based code path that checks for a
missing officecli key, so the type accurately reflects the shape accepted by
OfficeRouteEval.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a9420cb1-d0d3-45eb-a903-fcaa28c239f9

📥 Commits

Reviewing files that changed from the base of the PR and between d39bfbc and 150a8d9.

⛔ Files ignored due to path filters (27)
  • skills/morph-ppt/reference/styles/bw--brutalist-raw/bw__brutalist_raw.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/bw--mono-line/bw__mono_line.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/bw--swiss-bauhaus/bw__swiss_bauhaus.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--architectural-plan/dark__architectural_plan.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--blueprint-grid/dark__blueprint_grid.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--circle-digital/dark__circle_digital.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--cosmic-neon/dark__cosmic_neon.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--cyber-future/dark__cyber_future.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--diagonal-cut/dark__diagonal_cut.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--editorial-story/dark__editorial_story.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--investor-pitch/template.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--liquid-flow/dark__liquid_flow.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--luxury-minimal/dark__luxury_minimal.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--neon-productivity/dark__neon_productivity.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--premium-navy/dark__premium_navy.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--space-odyssey/dark__space_odyssey.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/dark--spotlight-stage/dark__spotlight_stage.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/light--bold-type/light__bold_type.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/light--isometric-clean/light__isometric_clean.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/light--minimal-product/light__minimal_product.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/light--watercolor-wash/light__watercolor_wash.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/mixed--duotone-split/mixed__duotone_split.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/vivid--candy-stripe/vivid__candy_stripe.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/vivid--playful-marketing/vivid__playful_marketing.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/warm--brand-refresh/warm__brand_refresh.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/warm--earth-organic/warm__earth_organic.pptx is excluded by !**/*.pptx
  • skills/morph-ppt/reference/styles/warm--playful-organic/Cat-Secret-Life.pptx is excluded by !**/*.pptx
📒 Files selected for processing (141)
  • .github/workflows/build.yml
  • .github/workflows/desktop-smoke.yml
  • .github/workflows/officecli-bump.yml
  • README.md
  • README_CN.md
  • THIRD_PARTY_NOTICES.md
  • packages/app/e2e/snap/skills-surface.snap.ts
  • packages/app/src/pages/skills/skill-presentation.ts
  • packages/desktop-electron/bundled-tools.json
  • packages/desktop-electron/package.json
  • packages/desktop-electron/scripts/prepare-officecli.test.ts
  • packages/desktop-electron/scripts/prepare-officecli.ts
  • packages/desktop-electron/scripts/prepare-uv.ts
  • packages/desktop-electron/scripts/release-workflow-contract.test.ts
  • packages/desktop-electron/scripts/sync-officecli-skills.test.ts
  • packages/desktop-electron/scripts/sync-officecli-skills.ts
  • packages/desktop-electron/tsconfig.release.json
  • packages/opencode/script/office-route-eval/eval.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/src/session/prompt/pawwork.txt
  • packages/opencode/src/tool/shell-artifact-orchestrator.ts
  • packages/opencode/src/tool/shell-office-artifacts.ts
  • packages/opencode/src/tool/shell-output-capture.ts
  • packages/opencode/src/tool/shell-write-heuristic.ts
  • packages/opencode/src/tool/shell.ts
  • packages/opencode/src/tool/shell/prompt.ts
  • packages/opencode/src/util/env.ts
  • packages/opencode/src/util/uv-mirror.ts
  • packages/opencode/test/github/bun-version-workflow.test.ts
  • packages/opencode/test/github/desktop-smoke-workflow.test.ts
  • packages/opencode/test/github/officecli-bump-workflow.test.ts
  • packages/opencode/test/github/pr-triage-workflow.test.ts
  • packages/opencode/test/session/session-skill.test.ts
  • packages/opencode/test/skill/skill.test.ts
  • packages/opencode/test/tool/shell-artifact-orchestrator.test.ts
  • packages/opencode/test/tool/shell-env-source.test.ts
  • packages/opencode/test/tool/shell-office-artifacts.test.ts
  • packages/opencode/test/tool/shell-prompt.test.ts
  • packages/opencode/test/tool/shell-write-heuristic.test.ts
  • packages/opencode/test/tool/shell.test.ts
  • packages/opencode/test/util/env.test.ts
  • site/public/llms.txt
  • site/src/components/Home.astro
  • site/src/i18n.ts
  • skills/morph-ppt-3d/SKILL.md
  • skills/morph-ppt/SKILL.md
  • skills/morph-ppt/reference/decision-rules.md
  • skills/morph-ppt/reference/morph-helpers.py
  • skills/morph-ppt/reference/morph-helpers.sh
  • skills/morph-ppt/reference/pptx-design.md
  • skills/morph-ppt/reference/styles/INDEX.md
  • skills/morph-ppt/reference/styles/bw--brutalist-raw/build.sh
  • skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md
  • skills/morph-ppt/reference/styles/bw--mono-line/build.sh
  • skills/morph-ppt/reference/styles/bw--mono-line/style.md
  • skills/morph-ppt/reference/styles/bw--swiss-bauhaus/build.sh
  • skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md
  • skills/morph-ppt/reference/styles/bw--swiss-system/style.md
  • skills/morph-ppt/reference/styles/dark--architectural-plan/build.sh
  • skills/morph-ppt/reference/styles/dark--architectural-plan/style.md
  • skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md
  • skills/morph-ppt/reference/styles/dark--blueprint-grid/build.sh
  • skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md
  • skills/morph-ppt/reference/styles/dark--circle-digital/build.sh
  • skills/morph-ppt/reference/styles/dark--circle-digital/style.md
  • skills/morph-ppt/reference/styles/dark--cosmic-neon/build.sh
  • skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md
  • skills/morph-ppt/reference/styles/dark--cyber-future/build.sh
  • skills/morph-ppt/reference/styles/dark--cyber-future/style.md
  • skills/morph-ppt/reference/styles/dark--diagonal-cut/build.sh
  • skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md
  • skills/morph-ppt/reference/styles/dark--editorial-story/build.sh
  • skills/morph-ppt/reference/styles/dark--editorial-story/style.md
  • skills/morph-ppt/reference/styles/dark--investor-pitch/build.sh
  • skills/morph-ppt/reference/styles/dark--investor-pitch/style.md
  • skills/morph-ppt/reference/styles/dark--liquid-flow/build.sh
  • skills/morph-ppt/reference/styles/dark--liquid-flow/style.md
  • skills/morph-ppt/reference/styles/dark--luxury-minimal/build.sh
  • skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md
  • skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md
  • skills/morph-ppt/reference/styles/dark--neon-productivity/build.sh
  • skills/morph-ppt/reference/styles/dark--neon-productivity/style.md
  • skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md
  • skills/morph-ppt/reference/styles/dark--premium-navy/build.sh
  • skills/morph-ppt/reference/styles/dark--premium-navy/style.md
  • skills/morph-ppt/reference/styles/dark--sage-grain/style.md
  • skills/morph-ppt/reference/styles/dark--space-odyssey/build.sh
  • skills/morph-ppt/reference/styles/dark--space-odyssey/style.md
  • skills/morph-ppt/reference/styles/dark--spotlight-stage/build.sh
  • skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md
  • skills/morph-ppt/reference/styles/dark--velvet-rose/style.md
  • skills/morph-ppt/reference/styles/light--bold-type/build.sh
  • skills/morph-ppt/reference/styles/light--bold-type/style.md
  • skills/morph-ppt/reference/styles/light--firmwise-saas/style.md
  • skills/morph-ppt/reference/styles/light--fluid-gradient/style.md
  • skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md
  • skills/morph-ppt/reference/styles/light--isometric-clean/build.sh
  • skills/morph-ppt/reference/styles/light--isometric-clean/style.md
  • skills/morph-ppt/reference/styles/light--minimal-corporate/style.md
  • skills/morph-ppt/reference/styles/light--minimal-product/build.sh
  • skills/morph-ppt/reference/styles/light--minimal-product/style.md
  • skills/morph-ppt/reference/styles/light--project-proposal/style.md
  • skills/morph-ppt/reference/styles/light--spring-launch/style.md
  • skills/morph-ppt/reference/styles/light--training-interactive/style.md
  • skills/morph-ppt/reference/styles/light--watercolor-wash/build.sh
  • skills/morph-ppt/reference/styles/light--watercolor-wash/style.md
  • skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md
  • skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md
  • skills/morph-ppt/reference/styles/mixed--duotone-split/build.sh
  • skills/morph-ppt/reference/styles/mixed--duotone-split/style.md
  • skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md
  • skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md
  • skills/morph-ppt/reference/styles/vivid--candy-stripe/build.sh
  • skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md
  • skills/morph-ppt/reference/styles/vivid--energy-neon/style.md
  • skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md
  • skills/morph-ppt/reference/styles/vivid--playful-marketing/build.sh
  • skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md
  • skills/morph-ppt/reference/styles/warm--bloom-academy/style.md
  • skills/morph-ppt/reference/styles/warm--brand-refresh/build.sh
  • skills/morph-ppt/reference/styles/warm--brand-refresh/style.md
  • skills/morph-ppt/reference/styles/warm--coral-culture/style.md
  • skills/morph-ppt/reference/styles/warm--earth-organic/build.sh
  • skills/morph-ppt/reference/styles/warm--earth-organic/style.md
  • skills/morph-ppt/reference/styles/warm--monument-editorial/style.md
  • skills/morph-ppt/reference/styles/warm--playful-organic/build.sh
  • skills/morph-ppt/reference/styles/warm--playful-organic/style.md
  • skills/morph-ppt/reference/styles/warm--sunset-mosaic/style.md
  • skills/morph-ppt/reference/styles/warm--vital-bloom/style.md
  • skills/office-docx/SKILL.md
  • skills/office-pdf/SKILL.md
  • skills/office-pptx/SKILL.md
  • skills/office-xlsx/SKILL.md
  • skills/officecli-academic-paper/SKILL.md
  • skills/officecli-data-dashboard/SKILL.md
  • skills/officecli-docx/SKILL.md
  • skills/officecli-financial-model/SKILL.md
  • skills/officecli-pitch-deck/SKILL.md
  • skills/officecli-pptx/SKILL.md
  • skills/officecli-word-form/SKILL.md
  • skills/officecli-xlsx/SKILL.md
💤 Files with no reviewable changes (98)
  • skills/morph-ppt/reference/styles/light--firmwise-saas/style.md
  • skills/morph-ppt/reference/styles/light--isometric-clean/style.md
  • skills/morph-ppt/reference/styles/bw--swiss-system/style.md
  • skills/morph-ppt/reference/styles/dark--spotlight-stage/style.md
  • skills/morph-ppt/reference/styles/light--bold-type/style.md
  • skills/morph-ppt/reference/styles/bw--mono-line/style.md
  • skills/morph-ppt/reference/styles/light--minimal-corporate/style.md
  • skills/morph-ppt/reference/styles/dark--premium-navy/style.md
  • skills/morph-ppt/reference/styles/dark--luxury-minimal/style.md
  • packages/desktop-electron/scripts/prepare-officecli.test.ts
  • skills/morph-ppt/reference/styles/light--training-interactive/style.md
  • skills/morph-ppt/reference/styles/warm--brand-refresh/style.md
  • skills/morph-ppt/reference/styles/warm--monument-editorial/style.md
  • skills/morph-ppt/reference/styles/dark--velvet-rose/style.md
  • skills/morph-ppt/reference/styles/vivid--bauhaus-electric/style.md
  • skills/morph-ppt/reference/styles/dark--neon-productivity/style.md
  • skills/morph-ppt/reference/styles/dark--cosmic-neon/build.sh
  • skills/morph-ppt/reference/styles/mixed--duotone-split/build.sh
  • skills/morph-ppt/reference/styles/light--glassmorphism-vc/style.md
  • skills/morph-ppt/reference/styles/dark--aurora-softedge/style.md
  • skills/morph-ppt/reference/styles/dark--editorial-story/style.md
  • skills/morph-ppt/reference/styles/dark--cosmic-neon/style.md
  • skills/morph-ppt/reference/styles/vivid--energy-neon/style.md
  • skills/morph-ppt/reference/styles/dark--liquid-flow/style.md
  • skills/morph-ppt/reference/styles/mixed--chromatic-aberration/style.md
  • skills/morph-ppt/reference/styles/warm--coral-culture/style.md
  • skills/morph-ppt/reference/styles/light--isometric-clean/build.sh
  • skills/morph-ppt/reference/styles/dark--neon-productivity/build.sh
  • skills/morph-ppt/reference/styles/vivid--playful-marketing/style.md
  • packages/opencode/test/tool/shell-env-source.test.ts
  • skills/morph-ppt/reference/styles/warm--playful-organic/build.sh
  • skills/morph-ppt/reference/styles/light--watercolor-wash/style.md
  • skills/morph-ppt/reference/decision-rules.md
  • skills/morph-ppt/reference/styles/mixed--spectral-grid/style.md
  • skills/morph-ppt/reference/styles/vivid--pink-editorial/style.md
  • skills/morph-ppt/reference/styles/dark--cyber-future/style.md
  • skills/morph-ppt/reference/styles/dark--spotlight-stage/build.sh
  • skills/morph-ppt/reference/styles/INDEX.md
  • packages/opencode/test/github/officecli-bump-workflow.test.ts
  • skills/morph-ppt/reference/styles/dark--space-odyssey/style.md
  • skills/morph-ppt/reference/styles/bw--swiss-bauhaus/build.sh
  • skills/morph-ppt/reference/styles/light--minimal-product/build.sh
  • skills/morph-ppt/reference/styles/dark--obsidian-amber/style.md
  • skills/morph-ppt/reference/styles/warm--earth-organic/style.md
  • skills/morph-ppt/reference/styles/bw--brutalist-raw/build.sh
  • skills/morph-ppt/reference/styles/light--watercolor-wash/build.sh
  • skills/morph-ppt/reference/styles/dark--diagonal-cut/style.md
  • skills/morph-ppt/reference/styles/bw--swiss-bauhaus/style.md
  • skills/morph-ppt/reference/styles/light--minimal-product/style.md
  • skills/morph-ppt/reference/pptx-design.md
  • skills/morph-ppt/reference/styles/mixed--bauhaus-blocks/style.md
  • skills/morph-ppt/reference/styles/mixed--duotone-split/style.md
  • skills/morph-ppt/reference/styles/dark--blueprint-grid/style.md
  • packages/desktop-electron/bundled-tools.json
  • skills/morph-ppt/reference/styles/light--fluid-gradient/style.md
  • skills/morph-ppt/reference/styles/dark--architectural-plan/style.md
  • .github/workflows/officecli-bump.yml
  • skills/morph-ppt/reference/styles/warm--bloom-academy/style.md
  • skills/morph-ppt/reference/styles/dark--circle-digital/style.md
  • skills/morph-ppt-3d/SKILL.md
  • skills/morph-ppt/reference/styles/vivid--candy-stripe/style.md
  • skills/morph-ppt/reference/styles/dark--premium-navy/build.sh
  • skills/morph-ppt/SKILL.md
  • packages/desktop-electron/scripts/sync-officecli-skills.test.ts
  • skills/morph-ppt/reference/styles/vivid--playful-marketing/build.sh
  • skills/morph-ppt/reference/styles/dark--space-odyssey/build.sh
  • skills/morph-ppt/reference/styles/bw--mono-line/build.sh
  • skills/morph-ppt/reference/styles/light--project-proposal/style.md
  • skills/morph-ppt/reference/styles/dark--blueprint-grid/build.sh
  • skills/morph-ppt/reference/styles/dark--midnight-blueprint/style.md
  • skills/morph-ppt/reference/styles/bw--brutalist-raw/style.md
  • skills/morph-ppt/reference/styles/light--bold-type/build.sh
  • skills/morph-ppt/reference/styles/light--spring-launch/style.md
  • skills/morph-ppt/reference/styles/dark--circle-digital/build.sh
  • skills/morph-ppt/reference/styles/dark--liquid-flow/build.sh
  • packages/opencode/test/tool/shell-office-artifacts.test.ts
  • skills/morph-ppt/reference/styles/dark--luxury-minimal/build.sh
  • skills/morph-ppt/reference/styles/dark--investor-pitch/build.sh
  • packages/desktop-electron/scripts/sync-officecli-skills.ts
  • skills/morph-ppt/reference/styles/dark--sage-grain/style.md
  • skills/morph-ppt/reference/styles/dark--cyber-future/build.sh
  • packages/desktop-electron/package.json
  • skills/morph-ppt/reference/styles/vivid--candy-stripe/build.sh
  • skills/morph-ppt/reference/styles/dark--diagonal-cut/build.sh
  • skills/morph-ppt/reference/styles/dark--architectural-plan/build.sh
  • skills/morph-ppt/reference/styles/dark--editorial-story/build.sh
  • skills/morph-ppt/reference/styles/warm--brand-refresh/build.sh
  • skills/morph-ppt/reference/styles/dark--investor-pitch/style.md
  • packages/desktop-electron/scripts/prepare-officecli.ts
  • .github/workflows/build.yml
  • skills/morph-ppt/reference/styles/warm--earth-organic/build.sh
  • skills/morph-ppt/reference/morph-helpers.py
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/src/tool/shell.ts
  • .github/workflows/desktop-smoke.yml
  • packages/opencode/test/github/bun-version-workflow.test.ts
  • packages/opencode/test/tool/shell.test.ts
  • skills/morph-ppt/reference/morph-helpers.sh

Astro-Han added 11 commits July 9, 2026 00:09
The prior heuristic flagged any python/uv command containing an office
extension as a write, so parse commands like `uv run python read_docx.py
input.docx` recorded false uncaptured-change markers, and quoted outputs like
`-o "report.docx"` were missed (words came from quote-stripped text). Instead
match the raw command for an -o/--out/--output/--outfile flag naming an office
output path, so read/parse commands stay read-only and quoted outputs are caught.

Surfaced by Codex round-2 review.
office-route-eval and ppt-quality-eval were one-off harnesses comparing the
officecli route against the native python routes. With officecli fully retired
they can no longer run — office:eval schedules the removed officecli binary and
ppt:eval copies a deleted morph-ppt template fixture — and the routing decision
they informed lives in the decision doc. Remove both harnesses and their package
scripts. officecli is now gone repo-wide except the skill.test.ts removal guard.

Surfaced by Codex round-2 review.
The raw-command regex matched -o/--out inside quoted literals, so read-only
commands like `echo "usage: -o report.docx"` and `grep "-o report.docx" file`
were flagged as writes and triggered office auto-discovery (recording a false
uncaptured-change signal). Detect the flag as a real shell token via tokenWords,
which respects quoting: quoted output paths with spaces are still captured, but
office-looking text inside an outer quote is not.
Drive the real isLikelyWriteCommand through the orchestrator so a real
`-o <office file>` generator with no declared expected_outputs flows
heuristic -> auto-discovery -> captured binary artifact, replacing the
coverage the removed OfficeCLI E2E gave the office output path.
office-pptx only documented SVG-authored generation, so routing an existing-deck
read/edit request there left no instructions, while pawwork.txt and the sibling
docx/xlsx skills promise read+parse. Add a python-pptx read/edit section and
broaden the description, matching office-docx/xlsx.
The description both claimed "any PDF request" and gated generation on a print
entry point that no tool currently exposes. Scope parsing as the unconditional
path and mark generation best-effort with graceful degradation, so the promoted
default route does not overclaim generation.
Round-4 review of the -o output-flag heuristic surfaced three capture bugs:
- The parsed -o path was discarded and capture fell back to a cwd scan, so a
  nested / `../` target (or one lost to discovery overflow) went unrecorded even
  though the command named it. Carry parsed output paths into artifact tracking.
- The -o check fired on any tool, so `grep -o report.docx file` (grep's
  only-matching flag) was read as a write. Gate detection to python / uv generators.
- .pdf counted toward the discovery capture budget, so a folder of ambient PDFs
  overflowed the scan and dropped real .docx/.xlsx/.pptx captures. Exclude .pdf
  from the cwd scan (PDF generation is unwired; an explicit -o out.pdf is still
  tracked and .pdf still reads as a binary artifact).
…ve()

Round-5 review found two capture bugs in the -o backstop:
- Parsed -o outputs were fed through the auto-discovery path, which always appends
  an uncaptured marker even after the exact file is recorded, so a normal
  `... -o deck.pptx` turn was reported mixed/uncaptured despite full capture. Treat
  parsed outputs like declared expected_outputs: exact, shown regardless of change,
  never flagged uncaptured, and captured without a cwd scan.
- docx/xlsx generation saves via `doc.save("out.docx")` with no -o flag, so the
  deliverable was neither captured nor marked uncaptured when expected_outputs was
  omitted. officeOutputPaths now also recognizes the python `.save(...)` call.

Also drops the now-dead Segment.delimiter field left over from the removed OfficeCLI
batch parsing.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/opencode/test/tool/shell-artifact-orchestrator.test.ts (1)

385-397: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the repo’s Effect test runner instead of manual Effect.runPromise.

These tests exercise an Effect-based workflow, so wrap them with the file’s testEffect(...).live / it.live pattern rather than manually running the Effect in each test. As per coding guidelines, tests that exercise Effect services or Effect-based workflows should use testEffect(...); based on learnings, tool tests under packages/opencode/test/tool/ should use testEffect(...).live / it.live for tool initialization/execution scenarios.

Also applies to: 427-439, 473-485, 515-527

🤖 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/opencode/test/tool/shell-artifact-orchestrator.test.ts` around lines
385 - 397, The tests in orchestrateArtifacts are manually invoking Effect
workflows with Effect.runPromise, but they should use the repo’s Effect test
harness instead. Update the affected test cases in
shell-artifact-orchestrator.test.ts to follow the existing testEffect(...).live
/ it.live pattern used for Effect-based tool initialization and execution, and
keep the assertions inside that Effect-aware test wrapper.

Sources: Coding guidelines, Learnings

🤖 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/opencode/src/tool/shell-artifact-orchestrator.ts`:
- Around line 89-116: The parsed Office output path is being treated as exact
even when the command changes directories, which can cause artifacts to be
missed. In shell-artifact-orchestrator, update the flow around
parseOfficeOutputs, resolveTrackedInput, and shouldAutoDiscover so relative
parsed outputs are resolved against the command’s active shell cwd at the point
of generation, not the original cwd. Detect cwd-changing segments like cd before
the generator, and if a relative parsed output depends on that changed
directory, either adjust the resolution or allow cwd discovery instead of
suppressing it.

In `@packages/opencode/src/tool/shell-office-artifacts.ts`:
- Around line 142-147: The `.save()` extraction in `shell-office-artifacts.ts`
is too broad because `isGeneratorCommand` is command-wide, so non-generator
segments can contribute phantom artifacts. Update the parsing logic around the
`matchAll` loop so `.save()` is only extracted from the actual generator
segment(s), not from arbitrary later segments like `node -e` or `ruby -e`, and
keep the existing `isOfficeOutputPath`/`isStaticOutputValue` checks intact. Use
the `isGeneratorCommand`, `saveScanSkipHeads`, and `commandHead(words)` gate as
the place to narrow scope before pushing into `paths`.
- Around line 87-89: The isStaticOutputValue helper is too permissive because it
only rejects $, backticks, *, and ?, but still treats shell-expanded literals
like brace expansion, character classes, and leading tilde as exact output.
Update isStaticOutputValue in shell-office-artifacts.ts to reject any value that
could be expanded by the shell before exact-output handling, including brace
patterns, bracket patterns, and ~-prefixed paths, so downstream discovery
doesn’t miss the real file.

---

Nitpick comments:
In `@packages/opencode/test/tool/shell-artifact-orchestrator.test.ts`:
- Around line 385-397: The tests in orchestrateArtifacts are manually invoking
Effect workflows with Effect.runPromise, but they should use the repo’s Effect
test harness instead. Update the affected test cases in
shell-artifact-orchestrator.test.ts to follow the existing testEffect(...).live
/ it.live pattern used for Effect-based tool initialization and execution, and
keep the assertions inside that Effect-aware test wrapper.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c34cd863-f72c-491a-ac79-1006936510f2

📥 Commits

Reviewing files that changed from the base of the PR and between a62b06f and a511e3a.

📒 Files selected for processing (5)
  • packages/opencode/src/tool/shell-artifact-orchestrator.ts
  • packages/opencode/src/tool/shell-office-artifacts.ts
  • packages/opencode/src/tool/shell.ts
  • packages/opencode/test/tool/shell-artifact-orchestrator.test.ts
  • packages/opencode/test/tool/shell-office-artifacts.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/opencode/test/tool/shell-office-artifacts.test.ts
  • packages/opencode/src/tool/shell.ts

Comment thread packages/opencode/src/tool/shell-artifact-orchestrator.ts Outdated
Comment thread packages/opencode/src/tool/shell-office-artifacts.ts
Comment thread packages/opencode/src/tool/shell-office-artifacts.ts Outdated
Astro-Han added 11 commits July 9, 2026 02:14
Codex R8 flagged two capture gaps in the native office route:

1. isStaticOutputValue missed Windows cmd variables — `-o "%OUT%.docx"`
   was tracked as the literal `%OUT%.docx` (never expanded), so a parsed
   output existed, discovery was skipped, and the real file was lost. Reject
   `%` alongside the POSIX/glob metacharacters.

2. The no-exact-output fallback keyed on "is a python/uv generator", so a
   read-only `uv run pytest` or `uv run python read_docx.py input.docx` in an
   office-heavy workspace triggered a cwd scan and, on discovery overflow, a
   false uncaptured marker. Replace hasOfficeGenerator with hasOfficeOutputIntent:
   the fallback fires only when the command NAMED an office output it couldn't
   resolve to an exact path (a dynamic `-o "$OUT.docx"` / `%OUT%.docx`), which
   still captures the real dynamic file while leaving read-only invocations
   untouched. A bare generator whose only write is an internal doc.save() is
   captured via the model-declared expected_outputs, the instructed primary path.
.save(r'out.docx') / .save(f'report.docx') were missed because the regex
expected a quote immediately after an optional backslash. Allow the r/b/u/f
python string prefixes so raw and f-string save calls are captured too.
Codex R9 / Pi R9 flagged two more backstop gaps:

1. An output flag holding the whole filename in a variable — `-o "$OUT"` —
   has no visible office extension, so intent detection returned false and the
   cwd scan was skipped. Trigger the scan when the value is a discoverable office
   extension OR a dynamic value whose extension is hidden in a variable. Align
   intent with what discovery can find (drop .pdf, which discovery excludes) so a
   dynamic .pdf no longer triggers a futile scan.

2. nonOfficeGeneratorText dropped the ENTIRE generator segment, discarding a
   redirect attached to it — `uv run python analyze.py > results.txt` or
   `... -o report.docx > log.txt` lost the redirected write, so it was neither
   scanned nor flagged uncaptured. Preserve write-redirections (quote-aware, so a
   `>` inside a python string is not mistaken for one) while still dropping the
   generator command whose -o/.save is captured exactly.

Also match `.save (` with whitespace before the paren.
Two edge cases in the undeclared-office-write backstop, found in review:

- A dynamic office output the command clearly intended (`-o "$OUT.docx"`)
  that the cwd scan cannot find — it expanded outside cwd or deeper than
  the scan reaches — silently vanished from the audit. Now flag the turn
  uncaptured when `dynamicOfficeOutput` intent fired but no office artifact
  actually changed, so an attempted write is never lost.

- `nonOfficeGeneratorText` split a command on newlines and stripped every
  generator segment independently. A heredoc body line `prs.save('deck.pptx')`
  was kept as its own segment and re-read as a write, double-flagging a
  purely-captured office deck as uncaptured. It also stripped `python setup.py
  build`, hiding a real non-office write. Now only segments whose write IS a
  captured office output (static -o/.save, dynamic office intent, or a
  generator's heredoc .save body) are dropped; a python segment that named no
  office output is kept so the write heuristic still judges it.
…options

Two more office-capture gaps found in review:

- A command mixing an exact office output with a dynamic one
  (`... -o a.docx && ... -o "$OUT.docx"`) suppressed the cwd scan for the
  dynamic sibling: the `parsed.length === 0` gate fired only when NO exact
  output existed. hasOfficeOutputIntent now reports only UNRESOLVED outputs
  (it excludes the ones officeOutputPaths captures exactly, via a shared
  isExactlyCapturableOutput helper), so the mixed command still scans for the
  dynamic one. The uncaptured decision now clears only on a DISCOVERED
  (non-parsed) office change, so an exact sibling changing cannot mask a lost
  dynamic output.

- uv permits global options before the `run` subcommand
  (`uv --directory work run python build.py -o report.docx`), which pushed
  `run` past the immediate next token and made isOfficeGeneratorSegment read
  the command as non-python / read-only. It now also detects a `run python`
  pair anywhere in a uv invocation.
Follow-ups to the uv-global-options handling:

- `uv --directory <dir>` changes the working directory before running, so a
  relative `-o report.docx` names `<dir>/report.docx`, not the shell cwd. The
  exact parser was still tracking the phantom cwd path (and, being an exact
  parse, skipping discovery). A new uvChangesDirectory() marks a relative
  output unresolved when the segment carries `--directory`, deferring to the
  discovery backstop; an absolute output and `--project` (which does not chdir)
  stay exactly captured.

- Unify the two uv branches in isOfficeGeneratorSegment: the immediate-next
  check accepted `uv run <anything>` while the global-options path required
  `run python`, so `uv run pytest` and `uv --offline run pytest` classified
  differently. Both now key on the presence of the `run` subcommand token,
  which also keeps `uv run script.py` (bare script, no explicit python)
  recognized. The office-output value gates still prevent a non-office
  `uv run <tool>` from producing a phantom capture.
Two more uv --directory edge cases from review:

- `--directory` is valid both as a global option (`uv --directory x run ...`)
  and as a `uv run` option (`uv run --directory x python ...`); both chdir.
  uvChangesDirectory() broke at the `run` token and missed the latter. It now
  scans up to the executed python command, so `--directory` in either position
  is detected while a `--directory` among the script's own args (after python)
  is correctly ignored.

- A heredoc `.save('out.docx')` sits in a later newline-split segment with no
  uv prefix, so its relativeUnresolved was computed without the generator's
  --directory. A `uv --directory` now propagates to the following segments (it
  sets the persistent cwd-changed flag, like `cd`), so a heredoc body's save
  correctly defers to discovery under work/ instead of tracking a phantom cwd
  path.
…dows .save

Three office-capture gaps from review:

- uv --directory over-propagated: the persistent cwd flag leaked the chdir
  across `&&`/`;` into the next command (`uv --directory work run ... &&
  uv run python b.py -o b.docx` wrongly deferred b.docx to discovery). Track
  two separate scopes: a persistent `cd` (crosses separators) and a
  command-scoped `uv --directory` that carries into a heredoc body (newline
  continuation) but resets at the next command separator. commandSegments now
  records each segment's trailing delimiter to distinguish the two.

- Shell line continuations (`\<newline>`) split a single generator into two
  segments, orphaning the `-o` flag. stripLineContinuations() collapses them
  (quote-aware: literal inside single quotes) before segmenting.

- A `.save(r"C:\dir\out.docx")` Windows path was truncated at `C:` by the
  capture regex's `[^"'\\]` class, silently losing the artifact. The shared
  saveOutputValues() helper now allows backslash path separators while still
  ending at an escaped closing quote, and de-duplicates the three previously
  copy-pasted .save regexes.
uvChangesDirectory() stopped at the first python-ish token, but in
`uv run --python python3 --directory work python build.py -o report.docx` the
`python3` is the value of uv's `--python`/`-p` interpreter selector, not the
executed command — so the scan broke before reaching `--directory` and tracked
a phantom cwd path. A python-ish token preceded by `--python`/`-p` no longer
stops the scan, so the later `--directory` is detected and the relative output
correctly defers to discovery.
Two more office-capture gaps from review:

- A newline separates independent commands too, not just a heredoc body, so
  scoping `uv --directory` to "newline = continuation" leaked the chdir across
  `uv --directory ... run <newline> uv run python b.py -o b.docx`. commandSegments
  is now heredoc-aware: a `<<DELIM ... DELIM` body (including `<<-` and quoted
  delimiters) stays in one segment, so a newline OUTSIDE a heredoc is a normal
  command separator. This lets `--directory` be judged per segment with no
  cross-command propagation — dropping the fragile delimiter/scope tracking.

- A cwd-relative static `.pdf` (`cd reports && ... -o report.pdf`) was neither
  exactly capturable (cwd changed) nor discoverable (.pdf excluded from the cwd
  scan), so it vanished with no uncaptured mark. A new unresolvedOfficeIntent()
  treats any unresolved STATIC office path (known filename, incl. .pdf) as
  intent, so the turn is flagged uncaptured instead of losing the deliverable;
  a dynamic visible .pdf (`-o "$OUT.pdf"`, no concrete name) still stays out.

@coderabbitai coderabbitai Bot 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.

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/opencode/src/tool/shell-office-artifacts.ts`:
- Around line 22-55: The continuation stripping in stripLineContinuations is
removing backslash-newline sequences inside heredoc bodies, which can break
commandSegments by swallowing the heredoc terminator and merging following
commands. Update the parsing flow in shell-office-artifacts so heredoc spans are
preserved before calling stripLineContinuations, or make stripLineContinuations
skip content inside heredocs while keeping its existing behavior elsewhere.
- Around line 253-265: Update isOfficeGeneratorSegment so that `uv` is only
treated as an office generator when `run` is the actual subcommand immediately
after any global options, not just any token in `rest`; the current
`rest.some(...)` check is too broad and can misclassify commands like `uv pip
install run ...`. Adjust the `commandHead`/`rest` handling in
`isOfficeGeneratorSegment` to validate the first non-option subcommand token is
`run`, keeping the existing `python` and `python3` cases unchanged.
- Around line 88-94: The heredoc delimiter parser in shell-office-artifacts.ts
only scans alphanumeric and underscore characters, so it truncates valid
delimiter words like PY-END and breaks matching of the closing marker. Update
the delimiter parsing logic in the heredoc handling code (the block that builds
delimiter and checks delimiterQuote) to consume the full delimiter word using
the same terminator rules that apply to heredoc tokens, including hyphens and
other valid delimiter characters, so the parsed delimiter matches the closing
marker correctly.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a6f60cb0-6455-4230-93fa-101fff045df4

📥 Commits

Reviewing files that changed from the base of the PR and between 494c1ab and b991b39.

📒 Files selected for processing (3)
  • packages/opencode/src/tool/shell-office-artifacts.ts
  • packages/opencode/test/tool/shell-artifact-orchestrator.test.ts
  • packages/opencode/test/tool/shell-office-artifacts.test.ts

Comment thread packages/opencode/src/tool/shell-office-artifacts.ts
Comment thread packages/opencode/src/tool/shell-office-artifacts.ts
Comment thread packages/opencode/src/tool/shell-office-artifacts.ts
Astro-Han added 13 commits July 9, 2026 04:43
…y redirects

isOfficeGeneratorSegment now matches a versioned python interpreter head
(python3.12 / python2 via /^python\d/, consistent with isPythonCommandToken)
so a direct `python3.12 build.py -o report.docx` is captured instead of
silently lost. segmentWriteRedirects skips `<<` heredoc bodies so ordinary
python code (`if total > 0:`, SVG/HTML `>` literals) inside a captured
generator's heredoc is not misread as a shell redirect and falsely marked as
an uncaptured side-effect write.
The R17 heredoc-body skip jumped from `<<` straight to the closing delimiter,
swallowing a real redirect on the opener line (`uv run python <<'PY' > log.txt`)
so that side-effect write vanished from turn-change auditing. Advance only past
the `<<` operator and skip the body at the opener line's newline, so opener-line
redirects are still captured while the stdin body is not misread as one.
…etection

`... -o report.docx >/dev/null` (or `2>/dev/null`, Windows `>NUL`) discards
output — it is not a file write. segmentWriteRedirects now drops null-device
targets so silencing a cleanly-captured office generator no longer leaves a
phantom redirect that makes isLikelyWriteCommand flag the turn as an uncaptured
non-office side effect.
…ubcommand

- skipHeredocBody now reads the whole delimiter word (quote-aware) so a valid
  hyphenated/dotted delimiter (`<<PY-END`, `<<'EOF.1'`) is matched instead of
  being truncated at the first hyphen, which left the closing marker unmatched
  and swallowed the rest of the command (hiding trailing side-effect writes).
- isOfficeGeneratorSegment treats `uv` as an office generator only when `run` is
  the actual subcommand, bailing when a different uv subcommand (pip/tool/...)
  precedes it, so `uv pip install run -o report.docx` is not misclassified.

Both from CodeRabbit review of PR #1494.
…iteral %

- uvRunsSubcommand now skips value-taking option values (`--directory build`,
  `--python python3`), so a directory/project name that collides with a uv
  subcommand name no longer hides the real `run` and drops the office output.
- isStaticOutputValue only treats a paired `%NAME%` Windows variable as dynamic,
  not any lone `%`, so a literal business filename (`Growth 20%.pptx`,
  `100%.docx`) stays static and is captured exactly.

Both from ChatGPT review of PR #1494.
Codex R20 + ChatGPT R20 each surfaced two real gaps in the office
artifact-capture path. All four are fixed with regression tests.

- Multiple dynamic outputs (`... -o "$A.docx" && ... -o "$B.docx"`):
  discovering one changed file no longer clears the uncaptured flag for
  its siblings. `unresolvedOfficeOutputCount` counts the intended dynamic
  outputs and the audit now requires one discovered file per output before
  treating them all as captured; `hasOfficeOutputIntent` delegates to it.

- Backslash-quoted heredoc delimiter (`<<\PY`): the closing line is `PY`,
  so the parser strips the quoting backslash instead of reading `\PY` and
  swallowing the rest of the command as heredoc body.

- Unquoted POSIX escaped space in an output path (`-o Quarterly\ Report.docx`):
  `shellWords` now binds `\<space>`/`\<tab>` into one token so the real
  filename is captured. Only whitespace is unescaped, so a Windows
  `C:\out\report.docx` keeps its separators.

- Trailing backslash inside a heredoc body (`# \` before the closing
  delimiter): `stripLineContinuations` is now heredoc-aware (reusing
  `skipHeredocBody`), so a body-internal `\`-newline is not collapsed into
  a line continuation that fuses the closing delimiter into the body and
  hides the following command from the side-effect audit.
One inline ArtifactDeps object in the orchestrator test still set the old
`hasOfficeOutputIntent` key after it was renamed to
`unresolvedOfficeOutputCount`, breaking `tsgo` typecheck. Update the key so
the deps object matches the type.
- uvChangesDirectory now skips every uv value-option's value (not just
  --python/-p), so a python-like --project value no longer hides a later
  --directory chdir and mistracks a relative office output as exact.
- shellWords unescapes unquoted backslash-escaped shell metacharacters
  (& ; | ( ) ' ") into literal filename chars; commandSegments no longer
  splits on an escaped delimiter, so R\&D.docx / report\;q.docx capture the
  real name instead of a phantom literal-backslash path or a torn segment.
  Windows-style C:\out\report.docx separators stay literal.
shellWords dropped a backslash before ANY char inside double quotes, so a
quoted Windows path `-o "C:\\Users\\me\\deck.pptx"` tokenized to
`C:Usersmedeck.pptx` and the office artifact was tracked at the wrong path
and lost. POSIX keeps a backslash special inside double quotes only before
" \\ $ `; before other chars it is literal. Restrict the unescape to that
set so Windows separators survive while an escaped quote in
`...save(\"x.docx\")` still resolves. Adds regression tests.
Two parser-completeness fixes surfaced by review:

- commandSegments skipped the whole heredoc span at `<<`, burying a command
  chained on the OPENER line (`uv run python <<'PY'; touch x` / `&& rm y`)
  inside the captured office segment, so a non-office side-effect write vanished
  from the audit. Now only the body is skipped: the opener line keeps splitting
  on ;/&&/|| and the body is re-attached to the generator segment, so the
  trailing write becomes its own auditable segment while office capture is kept.

- uvValueOptions missed several value-taking `uv run` options (--package,
  --extra, --group, --no-group, --only-group, and more), so a python-shaped
  value there (`uv run --package python --directory work ...`) stopped the
  uvChangesDirectory/uvRunsSubcommand scan early and hid a later --directory
  chdir, mistracking a relative office output as an exact shell-cwd path.
  Completed and centralized the table as the single maintenance point.

Adds regression tests for both.
Windows PowerShell 5.1 has no `&&`, so the shell-tool prompt instructs agents
to chain dependent commands as `cmd1; if ($?) { cmd2 }`. commandSegments never
descended into the `{ }` block, so a generator wrapped in it
(`... ; if ($?) { uv run python build.py -o report.docx }`) had segment head
`if`, was not recognized as an office generator, and its deliverable was neither
captured nor flagged uncaptured unless expected_outputs was set.

Treat a STANDALONE `{`/`}` (whitespace/`)`/`;`-bounded) as a command separator so
the wrapped generator becomes its own segment with the real command as its head.
A brace glued to `$` (${VAR}), `@` (hashtable), or a word (brace-expansion
`report{1,2}.docx`) is left intact. Also unwraps a bash `{ cmd; }` group. Adds
regression tests.
…kens

uvChangesDirectory only stopped at python/pythonN/*.py tokens, so a `uv run`
that executes a console script (`uv run render-pdf --directory inputs -o report.pdf`)
scanned past the command and read the script's own `--directory` arg as uv's
chdir. The relative output was then demoted from exact capture to unresolved;
since discovery excludes .pdf, a real cwd `report.pdf` produced only an
uncaptured mark with no downloadable artifact (and .docx/.xlsx/.pptx lost
exact-capture overflow protection).

Rewrite uvChangesDirectory as a uv-run option parser: skip uv global options to
`run`, then skip run options and their values until the first positional (the
executed command) or `--`; a `--directory` beyond that point is the command's own
argument. Handles console-script commands, not just python. Drops the now-unused
isPythonCommandToken. Adds regression tests.
…i/-f aliases

The artifact-capture cwd backstop parses uv run options to find where the
executed COMMAND starts, so a value-option value (e.g. `-w python-docx`) is
not mistaken for the command and does not hide a later `--directory` chdir.
The table was missing the short aliases -w/-i/-f and several long value
options, so `uv run -w pkg --directory work python make.py -o report.docx`
mis-captured the relative output as an exact shell-cwd file.

Sync uvValueOptions to `uv run --help` (uv 0.11.28) as the source of truth:
add -w/-i/-f plus --allow-insecure-host, --no-extra, --upgrade-group,
--exclude-newer-package, --config-settings-package, --no-editable-package,
--no-sources-package. Future uv versions adding a value option is a known
limitation, documented at the table; resync on uv bump.

Add -w/-i/-f regression tests (discovery when hiding --directory, exact when not).
@Astro-Han
Astro-Han merged commit b4105fa into dev Jul 9, 2026
44 checks passed
@Astro-Han
Astro-Han deleted the feat/1273-office-route-switchover branch July 9, 2026 09:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows ci Continuous integration / GitHub Actions harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions task Narrow execution, audit, spike, migration, tracking, or upstream follow-up work ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant