Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
288af8c
feat(oss-sync): direct-merge community PR lane with Orbit backflow (#…
caio-pizzol Jul 7, 2026
276e0a9
fix(super-editor): declare full namespace set on freshly-created numb…
MIt9 Jul 7, 2026
1e1559c
docs: add MIt9 to community contributors (#3810)
superdoc-bot[bot] Jul 8, 2026
cfa9f9d
fix(oss-ci): install bun in React workflow
caio-pizzol Jul 8, 2026
1b51f77
🔄 Sync stable → main (#3809)
superdoc-bot[bot] Jul 8, 2026
8d6c32a
docs: security - update privacy policy url (#172)
edoversb Jul 8, 2026
4ee24ac
fix(super-editor): reject overlapping metadata anchors and restore se…
caio-pizzol Jul 9, 2026
cefea74
fix(super-editor): sync custom xml writes through part-sync (IT-1229)…
caio-pizzol Jul 9, 2026
de102c9
fix(super-editor): drop superseded deferred comment SELECTED events (…
caio-pizzol Jul 13, 2026
53fe16d
fix(ci): preserve stable ancestry in main sync
caio-pizzol Jul 13, 2026
c3ff74e
fix(ci): recognize Python SDK release pins (#482)
caio-pizzol Jul 14, 2026
e2b6597
test(super-editor): sync comment feedback regression coverage (#496)
caio-pizzol Jul 14, 2026
7f2d1f9
chore: merge stable into main (release conflicts auto-resolved)
github-actions[bot] Jul 14, 2026
027f5fb
fix(release): publish GitHub releases for stable versions only
caio-pizzol Jul 13, 2026
e3d1eee
feat(sdk): custom-actions authoring kit — canonical ActionSpec with s…
andrii-harbour Jul 14, 2026
721f1cd
docs(readme): list supported frameworks
caio-pizzol Jul 14, 2026
d270b67
feat: add package-scoped AI release notes to all six public packages …
caio-pizzol Jul 14, 2026
ed5be40
Merge pull request #447 from superdoc/andrii/sd-3723-llm-tools-custom…
caio-pizzol Jul 14, 2026
a8ecb19
Merge remote-tracking branch 'origin/main' into merge/main-into-stabl…
github-actions[bot] Jul 14, 2026
c9a2231
fix(sdk): drop empty perform-action catalog rows
caio-pizzol Jul 14, 2026
bd66e3b
fix(sdk-python): preserve empty toolkit base values
caio-pizzol Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/promote-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ jobs:
git show ":3:${f}" | normalize_json_without_keys version > "${tmpdir}/theirs" || { rm -rf "${tmpdir}"; return 1; }
;;
pyproject.toml|*/pyproject.toml)
git show ":2:${f}" | sed -E '/^[[:space:]]*version[[:space:]]*=/d' > "${tmpdir}/ours" || { rm -rf "${tmpdir}"; return 1; }
git show ":3:${f}" | sed -E '/^[[:space:]]*version[[:space:]]*=/d' > "${tmpdir}/theirs" || { rm -rf "${tmpdir}"; return 1; }
git show ":2:${f}" | python3 scripts/normalize-pyproject-release-artifact.py > "${tmpdir}/ours" || { rm -rf "${tmpdir}"; return 1; }
git show ":3:${f}" | python3 scripts/normalize-pyproject-release-artifact.py > "${tmpdir}/theirs" || { rm -rf "${tmpdir}"; return 1; }
;;
version.json|*/version.json)
git show ":2:${f}" | normalize_json_without_keys version sdkVersion > "${tmpdir}/ours" || { rm -rf "${tmpdir}"; return 1; }
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-vscode-ext.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Stable releases are orchestrated centrally by release-stable.yml so that
# every stable release shares one concurrency slot and one git push lane.
# Note: VS Code Marketplace doesn't support semver prerelease versions, so
# main pushes only build a .vsix and attach it to the GitHub release.
# main pushes create prerelease tags without publishing a .vsix.
name: 📦 Release vscode-ext

on:
Expand Down
134 changes: 66 additions & 68 deletions .github/workflows/sync-patches.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
name: 🔄 Sync stable → main

# Stable pushes first run semantic-release, which can append version/tag
# commits back to stable. Sync only after that release lane finishes so main
# never gets a PR from an intermediate stable head.
# Stable releases can append version and tag commits. Wait for that lane to
# drain, then pin its settled head before merging it into main.
on:
workflow_run:
workflows:
- "📦 Release stable tooling (CLI/SDK/MCP)"
- '📦 Release stable tooling (CLI/SDK/MCP)'
types:
- completed
workflow_dispatch:

permissions:
actions: read
contents: write
pull-requests: write

concurrency:
group: sync-stable-to-main
Expand All @@ -27,15 +25,13 @@ jobs:
github.event_name == 'workflow_dispatch' ||
(
github.event.workflow_run.head_branch == 'stable' &&
(
github.event.workflow_run.conclusion == 'success' ||
github.event.workflow_run.conclusion == 'failure'
)
github.event.workflow_run.conclusion == 'success'
)

steps:
# Manual dispatch is the recovery path after an investigated failure,
# but it still waits for active stable releases before pinning stable.
- name: Wait for stable release lane to drain
if: github.event_name == 'workflow_run'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
Expand All @@ -52,7 +48,7 @@ jobs:
--jq '[.[] | select((.name == "📦 Release stable tooling (CLI/SDK/MCP)" or .name == "📦 Release esign" or .name == "📦 Release template-builder") and .status != "completed")] | length')

if [ "$active_runs" -eq 0 ]; then
echo "Stable release lane is idle."
echo "Stable release lane is drained."
break
fi

Expand Down Expand Up @@ -85,26 +81,68 @@ jobs:

- name: Sync stable into main
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
EVENT_NAME: ${{ github.event_name }}
TRIGGER_STABLE_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

git fetch origin main stable --tags --prune
stable_sha=$(git rev-parse origin/stable)

SYNC_BRANCH="sync/stable-to-main-$(date +%Y%m%d-%H%M%S)"
git checkout -b "$SYNC_BRANCH" origin/main
if [ "$EVENT_NAME" = "workflow_run" ]; then
if ! git merge-base --is-ancestor "$TRIGGER_STABLE_SHA" "$stable_sha"; then
echo "Pinned stable SHA does not descend from the successful tooling release."
exit 1
fi

release_subject_pattern='^chore(\([^)]+\))?: [^[:space:]]+ \[skip ci\]$'
unexpected_commit=false
while read -r sha subject; do
[ -z "$sha" ] && continue

author_email=$(git show -s --format='%ae' "$sha")
committer_email=$(git show -s --format='%ce' "$sha")
if [ "$author_email" != "semantic-release-bot@martynus.net" ] ||
[ "$committer_email" != "semantic-release-bot@martynus.net" ] ||
[[ ! "$subject" =~ $release_subject_pattern ]]; then
echo "Pinned stable includes a non-release commit: $sha $subject"
unexpected_commit=true
continue
fi

changed_files=$(git diff-tree --no-commit-id --name-only -r "$sha")
if [ -z "$changed_files" ]; then
echo "Release writeback has no version files: $sha"
unexpected_commit=true
continue
fi

while read -r f; do
case "$f" in
package.json|*/package.json|pyproject.toml|*/pyproject.toml|version.json|*/version.json) ;;
*)
echo "Release writeback changes an unexpected file: $sha $f"
unexpected_commit=true
;;
esac
done <<< "$changed_files"
done < <(git log --format='%H %s' "$TRIGGER_STABLE_SHA..$stable_sha")

if [ "$unexpected_commit" = true ]; then
exit 1
fi
fi

if git merge-base --is-ancestor origin/stable origin/main; then
git checkout --detach origin/main

if git merge-base --is-ancestor "$stable_sha" origin/main; then
echo "No changes to sync - stable is already in main's ancestry."
exit 0
fi

PR_TITLE="🔄 Sync stable → main"
MERGE_STATUS="clean"

normalize_json_without_keys() {
python3 -c 'import json, sys; data = json.load(sys.stdin); [data.pop(key, None) for key in sys.argv[1:]]; sys.stdout.write(json.dumps(data, sort_keys=True, separators=(",", ":")))' "$@"
}
Expand All @@ -121,8 +159,8 @@ jobs:
git show ":3:${f}" | normalize_json_without_keys version > "${tmpdir}/theirs" || { rm -rf "${tmpdir}"; return 1; }
;;
pyproject.toml|*/pyproject.toml)
git show ":2:${f}" | sed -E '/^[[:space:]]*version[[:space:]]*=/d' > "${tmpdir}/ours" || { rm -rf "${tmpdir}"; return 1; }
git show ":3:${f}" | sed -E '/^[[:space:]]*version[[:space:]]*=/d' > "${tmpdir}/theirs" || { rm -rf "${tmpdir}"; return 1; }
git show ":2:${f}" | python3 scripts/normalize-pyproject-release-artifact.py > "${tmpdir}/ours" || { rm -rf "${tmpdir}"; return 1; }
git show ":3:${f}" | python3 scripts/normalize-pyproject-release-artifact.py > "${tmpdir}/theirs" || { rm -rf "${tmpdir}"; return 1; }
;;
version.json|*/version.json)
git show ":2:${f}" | normalize_json_without_keys version sdkVersion > "${tmpdir}/ours" || { rm -rf "${tmpdir}"; return 1; }
Expand All @@ -140,17 +178,13 @@ jobs:
return "${status}"
}

# Use a real merge, not a squash, so stable release tags become
# reachable from main. semantic-release uses reachable tags as the
# version floor for @next prereleases.
if git merge --no-ff --no-edit origin/stable; then
MERGE_STATUS="clean"
else
# A real merge makes stable release tags reachable from main, which
# semantic-release needs as the version floor for @next.
if ! git merge --no-ff --no-edit "$stable_sha"; then
echo "Merge conflict — attempting auto-resolution of release artifacts..."

# Auto-resolve release artifact conflicts only when the conflict is
# version-only: keep stable's already-published version, but never
# drop dependency, script, export, or package metadata changes.
# Only published-version fields may take stable's side. Other
# package metadata conflicts remain manual.
while read -r f; do
case "$f" in
package.json|*/package.json|pyproject.toml|*/pyproject.toml|version.json|*/version.json)
Expand All @@ -166,7 +200,6 @@ jobs:
done < <(git diff --name-only --diff-filter=U)

if [ -z "$(git diff --name-only --diff-filter=U)" ]; then
MERGE_STATUS="auto_resolved"
git commit -m "chore: merge stable into main (release conflicts auto-resolved)"
else
echo "Unresolved conflicts remain after version-only release artifact auto-resolution."
Expand All @@ -176,45 +209,10 @@ jobs:
fi
fi

if ! git merge-base --is-ancestor origin/stable HEAD; then
if ! git merge-base --is-ancestor "$stable_sha" HEAD; then
echo "Stable is still not in the sync branch ancestry after merge."
exit 1
fi

git push origin "$SYNC_BRANCH"

# Check for existing open sync PR
EXISTING=$(gh pr list --base main --head "$SYNC_BRANCH" --state open --json number -q '.[0].number' 2>/dev/null || true)
if [ -n "$EXISTING" ]; then
echo "Sync PR #$EXISTING already exists."
exit 0
fi

gh pr create \
--base main \
--head "$SYNC_BRANCH" \
--title "$PR_TITLE" \
--body "$(cat <<EOF
## Summary

Merges latest stable patches into main with a real merge commit so stable release tags remain reachable from main.

This keeps \`@next\` prerelease version calculation ahead of the latest published stable release.

## Merge requirement

This PR must be merged with GitHub's merge-commit option. Squash or rebase merging will discard stable ancestry and break \`@next\` version calculation again.

## Conflict handling

Merge status: \`${MERGE_STATUS}\`.

Version-only release artifact conflicts are auto-resolved to keep stable's already-published versions. Non-version conflicts fail this workflow instead of committing conflict markers.

---
_Auto-created by sync-patches workflow._
EOF
)" \
--label "patch-sync"

echo "✅ Created sync PR"
# Push this merge unchanged. A normal push fails safely if main moved.
git push origin HEAD:main
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<strong>The document engine for DOCX files.</strong><br>
Renders, edits, and automates .docx files in the browser, headless on the server, and within AI agent workflows.<br>
Self-hosted. Open source. Works with React, Vue, and vanilla JS.
Self-hosted. Open source. Works with React, Vue, Angular, Svelte, and vanilla JS.
</p>

<div align="center">
Expand Down
49 changes: 32 additions & 17 deletions apps/cli/.releaserc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const {
*
* Keep in sync with .github/workflows/release-cli.yml paths: trigger.
*/
require('../../scripts/semantic-release/patch-commit-filter.cjs')([
const RELEASE_PATHS = [
'apps/cli',
'packages/document-api',
'packages/superdoc',
Expand All @@ -22,7 +22,9 @@ require('../../scripts/semantic-release/patch-commit-filter.cjs')([
'packages/preset-geometry',
'shared',
'pnpm-workspace.yaml',
]);
];

require('../../scripts/semantic-release/patch-commit-filter.cjs')(RELEASE_PATHS);

const branch = process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_BRANCH;

Expand All @@ -33,16 +35,28 @@ const branches = [

const isPrerelease = branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease);

// stable -> main syncs (real merges) re-attribute prereleases to PRs already shipped on @latest.
// Gate per-PR/issue success comments off on prereleases to avoid duplicate "shipped" comments.
const shouldCommentOnRelease = !isPrerelease;
// Linear release comments are the shipped-version breadcrumb inside Linear
// itself, so keep them on for prereleases even while GitHub PR comments stay
// gated separately.
// GitHub Releases are stable-only; prerelease tags and package publishing still proceed.
const shouldPublishGitHubRelease = Boolean(branch) && !isPrerelease;
// Linear release comments remain the shipped-version breadcrumb, so
// prereleases link to their Git tags when no GitHub Release exists.
const shouldCommentOnLinearRelease = true;

// Use AI-powered notes for stable releases, conventional generator for prereleases
const notesPlugin = isPrerelease ? createReleaseNotesGenerator() : ['semantic-release-ai-notes', { style: 'concise' }];
const notesPlugin = isPrerelease
? createReleaseNotesGenerator()
: [
'semantic-release-ai-notes',
{
style: 'concise',
scope: {
name: 'SuperDoc CLI',
paths: RELEASE_PATHS,
audience: 'Developers using the SuperDoc CLI to convert and process documents from the command line',
instructions:
'This CLI wraps the document engine. Only mention engine changes when they change CLI commands, output, or supported document operations.',
},
},
];

const config = {
branches,
Expand Down Expand Up @@ -89,13 +103,14 @@ config.plugins.push([
},
]);

config.plugins.push([
'@semantic-release/github',
{
successComment:
':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **superdoc-cli** v${nextRelease.version}\n\nThe release is available on [GitHub release](${releases.find(release => release.pluginName === "@semantic-release/github").url})',
successCommentCondition: shouldCommentOnRelease ? undefined : false,
},
]);
if (shouldPublishGitHubRelease) {
config.plugins.push([
'@semantic-release/github',
{
successComment:
':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **superdoc-cli** v${nextRelease.version}\n\nThe release is available on [GitHub release](${releases.find(release => release.pluginName === "@semantic-release/github").url})',
},
]);
}

module.exports = config;
27 changes: 13 additions & 14 deletions apps/create/.releaserc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@ const branches = [

const isPrerelease = branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease);

// stable -> main syncs (real merges) re-attribute prereleases to PRs already shipped on @latest.
// Gate per-PR/issue success comments off on prereleases to avoid duplicate "shipped" comments.
const shouldCommentOnRelease = !isPrerelease;
// Linear release comments are the shipped-version breadcrumb inside Linear
// itself, so keep them on for prereleases even while GitHub PR comments stay
// gated separately.
// GitHub Releases are stable-only; prerelease tags and package publishing still proceed.
const shouldPublishGitHubRelease = Boolean(branch) && !isPrerelease;
// Linear release comments remain the shipped-version breadcrumb, so
// prereleases link to their Git tags when no GitHub Release exists.
const shouldCommentOnLinearRelease = true;

const notesPlugin = isPrerelease ? createReleaseNotesGenerator() : ['semantic-release-ai-notes', { style: 'concise' }];
Expand Down Expand Up @@ -49,13 +47,14 @@ config.plugins.push([
},
]);

config.plugins.push([
'@semantic-release/github',
{
successComment:
':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **@superdoc-dev/create** v${nextRelease.version}\n\nThe release is available on [GitHub release](${releases.find(release => release.pluginName === "@semantic-release/github").url})',
successCommentCondition: shouldCommentOnRelease ? undefined : false,
},
]);
if (shouldPublishGitHubRelease) {
config.plugins.push([
'@semantic-release/github',
{
successComment:
':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **@superdoc-dev/create** v${nextRelease.version}\n\nThe release is available on [GitHub release](${releases.find(release => release.pluginName === "@semantic-release/github").url})',
},
]);
}

module.exports = config;
4 changes: 1 addition & 3 deletions apps/docs/ai/agents/core-preset.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,7 @@ Unknown action names in the list throw immediately (typo protection). The legacy

## Creating custom actions

<Note>
**Coming soon.** A first-class authoring kit (`defineAction`) is in the works: define your own action with a typed schema and handler, register it alongside the built-in forty, and it appears in the tool enum, the system prompt's action list, and the dispatch surface automatically — same receipts, same exclusion support. Until it ships, custom capabilities can be added as [custom tools](/ai/agents/legacy-preset#creating-custom-tools) registered next to the preset's own tools in your loop.
</Note>
Use `defineAction` to add a named action with built-in `steps` or a native `run` function. `createAgentToolkit` keeps the resulting tool schema, system prompt, and dispatcher aligned. See [Custom actions](/ai/agents/custom-actions) for the SDK workflow and the optional coding-agent skill.

## Over MCP

Expand Down
Loading
Loading