diff --git a/.github/ISSUE_TEMPLATE/improve-existing-docs.yaml b/.github/ISSUE_TEMPLATE/improve-existing-docs.yaml
index 0937f6d78c1c..fe4701b1d146 100644
--- a/.github/ISSUE_TEMPLATE/improve-existing-docs.yaml
+++ b/.github/ISSUE_TEMPLATE/improve-existing-docs.yaml
@@ -6,10 +6,9 @@ body:
- type: markdown
attributes:
value: |
- **HUBBERS!!** This is the github/docs open source repo. You may want to open an issue in the internal-only github/docs-content repo instead.
-
* Before you file an issue read the [Contributing guide](https://docs.github.com/en/contributing).
* Check to make sure someone hasn't already opened a similar [issue](https://github.com/github/docs/issues).
+ * If your issue relates to API endpoint reference content under https://docs.github.com/rest, please raise it against https://github.com/github/rest-api-description.
- type: checkboxes
id: terms
diff --git a/.github/workflows/check-for-spammy-issues.yml b/.github/workflows/check-for-spammy-issues.yml
index ceeb08346232..6bd4e68dab42 100644
--- a/.github/workflows/check-for-spammy-issues.yml
+++ b/.github/workflows/check-for-spammy-issues.yml
@@ -70,7 +70,7 @@ jobs:
owner: owner,
repo: repo,
issue_number: issue.number,
- body: `This issue may have been opened accidentally. I'm going to close it now, but feel free to open a new issue with a more descriptive title!`
+ body: `This issue may have been opened accidentally. I'm going to close it now, but feel free to open a new issue with a more descriptive title! Make sure not to include full URLs in your issue titles, and use paths instead.`
});
// Add the issue to the Done column on the triage board
diff --git a/.github/workflows/check-for-spammy-prs.yml b/.github/workflows/check-for-spammy-prs.yml
index 6188eaee1d7f..ac862691065a 100644
--- a/.github/workflows/check-for-spammy-prs.yml
+++ b/.github/workflows/check-for-spammy-prs.yml
@@ -49,7 +49,9 @@ jobs:
return changedLines.length > 0 &&
changedLines.every(line => line.slice(1).trim() === '')
})
- const onlyRenames = files.length > 0 && files.every(f => f.status === 'renamed')
+ const onlyRenames = files.length > 0 && files.every(
+ f => f.status === 'renamed' && f.additions === 0 && f.deletions === 0
+ )
if (
targetsNonMain ||
diff --git a/content/copilot/concepts/agents/cloud-agent/agent-management.md b/content/copilot/concepts/agents/cloud-agent/agent-management.md
index ea08bdb554b3..f27aff08a793 100644
--- a/content/copilot/concepts/agents/cloud-agent/agent-management.md
+++ b/content/copilot/concepts/agents/cloud-agent/agent-management.md
@@ -44,7 +44,7 @@ From the Agents tab, you can:
{% data reusables.copilot.cloud-agent.use-latest-vscode %}
* **Review and merge agent code**: Once the agent completes a session, you can jump to the pull request to review the changes, request further improvements, or approve and merge. See [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/review-copilot-output).
* **Set up {% data variables.copilot.copilot_automations %}**: Run {% data variables.copilot.copilot_cloud_agent %} automatically, on a schedule or in response to events such as an issue being opened. See [AUTOTITLE](/copilot/concepts/agents/cloud-agent/about-automations).
-* **Query your past sessions**: You can search and reference your past agent sessions using natural language from {% data variables.copilot.copilot_cli_short %} or {% data variables.product.prodname_vscode_shortname %}. See [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle).
+* **Query your past sessions**: You can search and reference your past agent sessions using natural language from {% data variables.copilot.copilot_cli_short %} or {% data variables.product.prodname_vscode_shortname %}. See [AUTOTITLE](/copilot/concepts/security-governance-and-network-settings/session-data).
## Next steps
diff --git a/content/copilot/concepts/agents/cloud-agent/index.md b/content/copilot/concepts/agents/cloud-agent/index.md
index 8878e39bbda5..fc3c2d1aff64 100644
--- a/content/copilot/concepts/agents/cloud-agent/index.md
+++ b/content/copilot/concepts/agents/cloud-agent/index.md
@@ -12,7 +12,6 @@ children:
- /about-automations
- /about-automation-rationale-and-approvals
- /mcp-and-cloud-agent
- - /risks-and-mitigations
contentType: concepts
redirect_from:
- /copilot/concepts/agents/coding-agent
diff --git a/content/copilot/concepts/agents/copilot-cli/chronicle.md b/content/copilot/concepts/agents/copilot-cli/chronicle.md
deleted file mode 100644
index ba1f7eee6889..000000000000
--- a/content/copilot/concepts/agents/copilot-cli/chronicle.md
+++ /dev/null
@@ -1,118 +0,0 @@
----
-title: 'About {% data variables.copilot.copilot_cli %} session data'
-shortTitle: Session data
-allowTitleToDifferFromFilename: true
-intro: 'Your {% data variables.copilot.copilot_cli_short %} sessions build a searchable history of everything you have worked on. Query past sessions with natural language, generate standup reports, get personalized tips, and resume previous work.'
-versions:
- feature: copilot
-contentType: concepts
-category:
- - Learn about Copilot # Copilot discovery page
- - Learn about Copilot CLI # Copilot CLI bespoke page
-docsTeamMetrics:
- - copilot-cli
----
-
-## Introduction
-
-Every time you use {% data variables.copilot.copilot_cli_short %}, your session data is recorded on your local machine. This includes your prompts, {% data variables.product.prodname_copilot_short %}'s responses, the tools that were used, and details of files that were modified. By default, session data is also synced to your {% data variables.product.github %} account. This lets you query all of your past sessions, including sessions from {% data variables.copilot.copilot_cli_short %}, {% data variables.copilot.copilot_cloud_agent %}, {% data variables.copilot.copilot_code-review_short %}, {% data variables.product.prodname_vscode_shortname %}, JetBrains, and the {% data variables.copilot.github_copilot_app %}.
-
-This session data powers several features, available from {% data variables.copilot.copilot_cli_short %}, {% data variables.product.prodname_vscode_shortname %}, JetBrains, the {% data variables.copilot.github_copilot_app %}, and {% data variables.product.prodname_dotcom_the_website %}:
-
-* **Querying your session history**: Ask natural language questions about your past work, and {% data variables.product.prodname_copilot_short %} will search your session data for answers.
-* **Resuming sessions**: Pick up where you left off in any previous session.
-* **The `/chronicle` slash command**: A set of purpose-built subcommands that generate standup reports, personalized tips, cost analysis, and suggestions for improving your custom instructions, all derived from your session history.
-
-With user-scoped queries, you can only query your own sessions—no one else can access your session data.
-
-In {% data variables.jetbrains.prodname_jetbrains_ides %}, `/chronicle` is available in interactive {% data variables.copilot.copilot_cli_short %} sessions, so you can review session history and generate insights without leaving the IDE.
-
-This conceptual article explains how session data is stored and synced, and how you can use it to enhance your workflow. For a practical guide to resuming a session, asking {% data variables.product.prodname_copilot_short %} about your CLI sessions, and using the `/chronicle` slash command, see [AUTOTITLE](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle). For querying sessions from {% data variables.product.prodname_vscode_shortname %}, see [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents).
-
-## How session data is stored
-
-Every {% data variables.copilot.copilot_cli_short %} session is persisted as a set of files in the `~/.copilot/session-state/` directory on your machine. The data for each session contains a complete record of the session. These files allow you to resume an interactive CLI session.
-
-In addition to the session files, {% data variables.copilot.copilot_cli_short %} stores structured session data in a local SQLite database, referred to as the session store. This data is a subset of the full data stored in the session files. The session store is what powers the `/chronicle` slash command and it also allows {% data variables.product.prodname_copilot_short %} to answer questions you ask about your past work.
-
-## Session syncing
-
-By default, {% data variables.copilot.copilot_cli_short %} syncs your session data to your {% data variables.product.github %} account. This lets you query your past sessions from {% data variables.copilot.copilot_cli_short %}, {% data variables.product.prodname_vscode_shortname %}, JetBrains, the {% data variables.copilot.github_copilot_app %}, or {% data variables.product.prodname_dotcom_the_website %}, and includes sessions from {% data variables.copilot.copilot_cloud_agent %}, {% data variables.copilot.copilot_code-review_short %}, {% data variables.product.prodname_vscode_shortname %}, and the {% data variables.copilot.github_copilot_app %}.
-
-You can opt out of session syncing by setting `"remoteExport": false` in your CLI settings JSON. If you opt out, session data stays on your machine and you can only query it from {% data variables.copilot.copilot_cli_short %}.
-
-For {% data variables.copilot.copilot_enterprise_short %} and {% data variables.copilot.copilot_business_short %} users, an organization administrator must set the "Store local sessions in the Cloud" policy to at least "View from cloud." If the policy is disabled or unconfigured, sessions are stored locally only.
-
-### Privacy and data locality
-
-Local session data is stored in `~/.copilot/session-state/` and is only accessible to your user account on that machine.
-
-Synced session data is stored on {% data variables.product.github %} and is tied to your personal account. It is accessible only to you by default. Organization and enterprise administrators control whether syncing is available through the "Store local sessions in the Cloud" policy, but enabling the policy does not give administrators access to your session data.
-
-You can choose to share individual sessions, giving view-only access to others who have access to the repository. Shared sessions are not indexed for other users' session queries. For more information, see [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents#share-a-session).
-
-{% data variables.product.prodname_copilot_short %} reads session data when you ask questions about past interactions or use the `/chronicle` slash command. Session data such as your previous prompts, context, and responses may be sent to the AI model, just as in any normal {% data variables.copilot.copilot_cli_short %} interaction.
-
-### Managing your session data
-
-You can manage your session data both locally and on {% data variables.product.prodname_dotcom_the_website %}.
-
-#### Deleting sessions with the `/session` slash command
-
-The simplest way to delete sessions is to use the `/session` slash command in an interactive CLI session.
-
-* `/session delete` deletes the current session and starts a new one in its place.
-* `/session delete SESSION-ID` deletes a specific session. This shows a preview first; add `--yes` to confirm, for example `/session delete SESSION-ID --yes`.
-* `/session delete-all` deletes all of your local sessions except the current one. Add `--yes` to confirm: `/session delete-all --yes`. Sessions that are in use by another process are skipped.
-* `/session prune --older-than DAYS` deletes sessions older than the specified number of days. Add `--dry-run` to preview what would be deleted.
-
-When you delete a session that has been synced to your account, `/session delete` asks whether you also want to delete the synced (remote) copy. Deleting the synced copy also removes the session from your `/chronicle` insights and query results. The `/session delete-all` and `/session prune` subcommands only affect local sessions and do not delete synced data. To remove synced data for those sessions, manually delete it from {% data variables.product.prodname_dotcom_the_website %}.
-
-For the full list of `/session` subcommands, see [AUTOTITLE](/copilot/reference/copilot-cli-reference/cli-command-reference).
-
-#### Deleting session data manually
-
-* **Local data**: To remove data for a particular CLI session locally, delete the relevant session directory from `~/.copilot/session-state/`. To clear all local session data, delete everything under `~/.copilot/session-state/`. After doing this you must manually reindex the session store. See [Reindexing the session store](#reindexing-the-session-store) later in this article. Deleting local files does not affect session data that has been synced to your account. You cannot delete synced data locally.
-* **Synced data**: You can delete or hide synced CLI sessions from {% data variables.product.prodname_dotcom_the_website %}. Hiding a session removes it from your session index so it no longer appears in query results. Deleting a session removes it from your session list on {% data variables.product.prodname_dotcom_the_website %}. Deletion applies to CLI, {% data variables.product.prodname_vscode_shortname %}, and {% data variables.copilot.github_copilot_app %} sessions.
-
-## The `/chronicle` slash command
-
-The `/chronicle` slash command provides purpose-built subcommands for generating insights from your session history, including standup reports, personalized tips, cost analysis, and suggestions for improving your custom instructions. For the full list of subcommands and usage examples, see [AUTOTITLE](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle#using-the-chronicle-slash-command).
-
-If you use {% data variables.copilot.copilot_cli_short %} in {% data variables.jetbrains.prodname_jetbrains_ides %}, these `/chronicle` subcommands are available from the interactive CLI session you open in the IDE.
-
-## When to use session data
-
-* **At the start of your day**: Run `/chronicle standup last 3 days` to generate a summary of recent work instead of reconstructing it manually.
-* **Periodically, to level up**: Run `/chronicle tips` every week or two to discover features and workflow improvements you might be missing.
-* **When {% data variables.product.prodname_copilot_short %} keeps making the same mistake**: Run `/chronicle improve` to identify the pattern and generate custom instructions that make the agent more effective for your project.
-* **To understand your token usage**: Run `/chronicle cost-tips` to see where tokens are going and get suggestions for reducing costs.
-* **To search for a specific topic**: Run `/chronicle search KEYWORD` to find sessions containing a specific term or topic. Unlike free-form questions, this searches session content directly for keywords rather than interpreting your query semantically.
-* **To recall past work**: Ask a free-form question like "Have I worked on anything related to the payments API?" and {% data variables.product.prodname_copilot_short %} will search your history.
-* **To continue previous work**: Use `copilot --continue` or `copilot --resume` to pick up where you left off.
-
-## Reindexing the session store
-
-The session store is populated incrementally during a CLI session. Data for a session is written to disk in a session-specific subdirectory of `~/.copilot/session-state/`. This also happens periodically during a session, and also when the session ends.
-
-You can reindex the session store from the session files on disk. Reindexing also syncs your session data to your account.
-
-Situations where you might need to reindex include:
-
-* **Indexing old sessions**: If you have old session files on disk that were created before the session store existed, reindexing will populate the session store with data from those sessions.
-* **Session deletion**: To delete a session from your history, use the `/session delete`, `/session delete-all`, or `/session prune` slash commands. For a synced session, `/session delete` can also remove the synced copy, which removes the session from your `/chronicle` insights.
-* **Migrating/recovering sessions**: If you moved your session files to another machine, or restored them from a backup, without also moving/restoring the session store file (`~/.copilot/session-store.db`), you can use the reindex command to recreate the session store.
-* **File corruption**: If the session store file (`~/.copilot/session-store.db`) becomes corrupted, or is accidentally deleted, you can recover the session store from the session files.
-* **Unexpected termination**: If a session terminates unexpectedly (for example, due to a crash or power loss) before data held in memory has been flushed to the session store you may be able to populate the session store with the missing data if it was written to disk, in the session files, prior to the termination.
-
-To reindex the session store, use the following slash command in an interactive CLI session:
-
-```copilot copy
-/chronicle reindex
-```
-
-## Further reading
-
-* [AUTOTITLE](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle)
-* [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents)
-* [AUTOTITLE](/copilot/reference/copilot-cli-reference/cli-command-reference)
diff --git a/content/copilot/concepts/agents/copilot-cli/index.md b/content/copilot/concepts/agents/copilot-cli/index.md
index cff6e182d98e..8a5a181db4b1 100644
--- a/content/copilot/concepts/agents/copilot-cli/index.md
+++ b/content/copilot/concepts/agents/copilot-cli/index.md
@@ -16,7 +16,6 @@ children:
- /autopilot
- /fleet
- /research
- - /chronicle
- /rubber-duck
- /lsp-servers
- /about-cli-extensions
diff --git a/content/copilot/concepts/context/index.md b/content/copilot/concepts/context/index.md
index 4d2f71eda769..6c6069d2a6d1 100644
--- a/content/copilot/concepts/context/index.md
+++ b/content/copilot/concepts/context/index.md
@@ -9,6 +9,6 @@ children:
- /mcp
- /spaces
- /repository-indexing
- - /content-exclusion
contentType: concepts
---
+
diff --git a/content/copilot/concepts/index.md b/content/copilot/concepts/index.md
index 8110be9edcc2..b31b4cfe830b 100644
--- a/content/copilot/concepts/index.md
+++ b/content/copilot/concepts/index.md
@@ -8,14 +8,12 @@ children:
- /completions
- /chat
- /agents
- - /about-cloud-and-local-sandboxes
- /prompting
- /context
- /tools
- /models
- /billing-and-usage
- - /network-settings
+ - /security-governance-and-network-settings
- /enterprise
contentType: concepts
---
-
diff --git a/content/copilot/concepts/about-cloud-and-local-sandboxes.md b/content/copilot/concepts/security-governance-and-network-settings/about-cloud-and-local-sandboxes.md
similarity index 99%
rename from content/copilot/concepts/about-cloud-and-local-sandboxes.md
rename to content/copilot/concepts/security-governance-and-network-settings/about-cloud-and-local-sandboxes.md
index 951e7f931b66..66105f92f883 100644
--- a/content/copilot/concepts/about-cloud-and-local-sandboxes.md
+++ b/content/copilot/concepts/security-governance-and-network-settings/about-cloud-and-local-sandboxes.md
@@ -7,6 +7,7 @@ versions:
feature: copilot
redirect_from:
- /copilot/concepts/about-github-sandbox
+ - /copilot/concepts/about-cloud-and-local-sandboxes
contentType: concepts
category:
- Learn about Copilot
diff --git a/content/copilot/concepts/context/content-exclusion.md b/content/copilot/concepts/security-governance-and-network-settings/content-exclusion.md
similarity index 98%
rename from content/copilot/concepts/context/content-exclusion.md
rename to content/copilot/concepts/security-governance-and-network-settings/content-exclusion.md
index d253a53981d8..5b92997ba3a1 100644
--- a/content/copilot/concepts/context/content-exclusion.md
+++ b/content/copilot/concepts/security-governance-and-network-settings/content-exclusion.md
@@ -8,8 +8,9 @@ versions:
redirect_from:
- /copilot/concepts/content-exclusion-for-github-copilot
- /copilot/concepts/content-exclusion
+ - /copilot/concepts/context/content-exclusion
contentType: concepts
-category:
+category:
- Manage Copilot for a team
---
diff --git a/content/copilot/concepts/security-governance-and-network-settings/index.md b/content/copilot/concepts/security-governance-and-network-settings/index.md
new file mode 100644
index 000000000000..335346fd6f02
--- /dev/null
+++ b/content/copilot/concepts/security-governance-and-network-settings/index.md
@@ -0,0 +1,17 @@
+---
+title: Security, governance, and network settings for GitHub Copilot
+shortTitle: Security, governance, and network settings
+intro: Understand how to control access to {% data variables.product.prodname_copilot %}, protect data, isolate agent execution, and configure network access.
+versions:
+ feature: copilot
+children:
+ - /content-exclusion
+ - /session-data
+ - /about-cloud-and-local-sandboxes
+ - /risks-and-mitigations
+ - /network-settings
+contentType: concepts
+category:
+ - Manage Copilot for a team
+ - Roll Copilot out at scale
+---
diff --git a/content/copilot/concepts/network-settings.md b/content/copilot/concepts/security-governance-and-network-settings/network-settings.md
similarity index 99%
rename from content/copilot/concepts/network-settings.md
rename to content/copilot/concepts/security-governance-and-network-settings/network-settings.md
index 16f5119bf68d..f654fcb7fef9 100644
--- a/content/copilot/concepts/network-settings.md
+++ b/content/copilot/concepts/security-governance-and-network-settings/network-settings.md
@@ -7,6 +7,7 @@ versions:
ghec: '*'
redirect_from:
- /copilot/concepts/network-settings-for-github-copilot
+ - /copilot/concepts/network-settings
contentType: concepts
category:
- Manage Copilot for a team
diff --git a/content/copilot/concepts/agents/cloud-agent/risks-and-mitigations.md b/content/copilot/concepts/security-governance-and-network-settings/risks-and-mitigations.md
similarity index 98%
rename from content/copilot/concepts/agents/cloud-agent/risks-and-mitigations.md
rename to content/copilot/concepts/security-governance-and-network-settings/risks-and-mitigations.md
index 4265328acd53..df3b5a925e81 100644
--- a/content/copilot/concepts/agents/cloud-agent/risks-and-mitigations.md
+++ b/content/copilot/concepts/security-governance-and-network-settings/risks-and-mitigations.md
@@ -1,7 +1,7 @@
---
title: Risks and mitigations for GitHub Copilot cloud agent
shortTitle: Risks and mitigations
-intro: 'How do {% data variables.copilot.copilot_cloud_agent %}''s built-in security protections mitigate known risks?'
+intro: How do {% data variables.copilot.copilot_cloud_agent %}'s built-in security protections mitigate known risks?
versions:
feature: copilot
contentType: concepts
@@ -11,6 +11,7 @@ category:
- Roll Copilot out at scale
redirect_from:
- /copilot/concepts/agents/coding-agent/risks-and-mitigations
+ - /copilot/concepts/agents/cloud-agent/risks-and-mitigations
---
{% data variables.copilot.copilot_cloud_agent %} is an autonomous agent that has access to your code and can push changes to your repository. This entails certain risks.
diff --git a/content/copilot/concepts/security-governance-and-network-settings/session-data.md b/content/copilot/concepts/security-governance-and-network-settings/session-data.md
new file mode 100644
index 000000000000..bf96aed79b81
--- /dev/null
+++ b/content/copilot/concepts/security-governance-and-network-settings/session-data.md
@@ -0,0 +1,126 @@
+---
+title: About GitHub Copilot session data
+shortTitle: Session data
+allowTitleToDifferFromFilename: true
+intro: 'Understand what session data is, where it is stored, who can access it, and how it is managed.'
+versions:
+ feature: copilot
+contentType: concepts
+category:
+ - Learn about Copilot # Copilot discovery page
+docsTeamMetrics:
+ - copilot-cli
+redirect_from:
+ - /copilot/concepts/agents/copilot-cli/chronicle
+---
+
+A session is a period of interaction with {% data variables.product.prodname_copilot_short %}, such as a conversation in an IDE or work performed by an agent. **Session data** is the information recorded about that interaction. This can include prompts and responses, tools used, and changes made to files.
+
+Your **session history** is the collection of sessions that you can query.
+
+Session data helps you understand and return to work performed with {% data variables.product.prodname_copilot_short %}. You can use session data to:
+
+* **Query your session history**: Ask natural-language questions about work from your previous sessions.
+* **Resume sessions**: Pick up where you left off in any previous session.
+* **Review or share** a session.
+* **Generate insights** such as standup reports, workflow tips, and cost analysis.
+
+The available capabilities depend on the surface where the session is running.
+
+## Understanding session location and access
+
+Where a session runs, where its data is stored, whether it is synced to your {% data variables.product.github %} account, and who can access it are separate considerations.
+
+A locally run session can have data stored both on your machine and in your {% data variables.product.github %} account. Syncing a session does not share it with other people.
+
+### Locally run sessions
+
+{% data variables.copilot.copilot_cli_short %} and the {% data variables.copilot.github_copilot_app %} store the complete record of each session under `~/.copilot/session-state/`. They also store a subset of the data in a local SQLite database, referred to as the session store. The session store supports session-history queries and the `/chronicle` command.
+
+Session storage for sessions started in an IDE is IDE-specific. For information about what session data is stored and where, see the documentation for your IDE.
+
+### Sessions run on {% data variables.product.github %}
+
+{% data variables.copilot.copilot_cloud_agent %} sessions run in an ephemeral environment hosted by {% data variables.product.github %}. The environment is destroyed when the session ends, but the session log remains available on {% data variables.product.prodname_dotcom_the_website %}. These sessions are shared by default and visible to people with access to the repository.
+
+## Session syncing
+
+By default, locally-run sessions created with {% data variables.copilot.copilot_cli_short %} or the {% data variables.copilot.github_copilot_app %} are synced to your {% data variables.product.github %} account.
+
+You can control syncing for {% data variables.copilot.copilot_cli_short %} and the {% data variables.copilot.github_copilot_app %}. See `remote` and `remoteExport` in [AUTOTITLE](/copilot/reference/copilot-cli-reference/cli-config-dir-reference).
+
+### Policy governing session syncing
+
+For {% data variables.copilot.copilot_enterprise_short %} and {% data variables.copilot.copilot_business_short %} users, the applicable "**Store local sessions in the Cloud**" policy must be set to at least "View from cloud" for session data to be synced. If the policy is disabled or unconfigured, sessions are stored locally only.
+
+## Privacy and sharing
+
+Local sessions are unshared by default. You can share an individual local session with people who have access to the repository. Recipients have view-only access, and shared sessions are not included in queries of the recipient's own session history. See [Sharing a session](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle#sharing-a-session).
+
+Synced session data is tied to your personal account and is accessible only to you by default. Administrators can control whether syncing is available, but enabling syncing does not give them access to your session data.
+
+{% data variables.copilot.copilot_cloud_agent %} sessions are shared by default. They appear in the "All sessions" view on the "Agents" tab of your repository, visible to anyone with access to the repository.
+
+When you query previous interactions or use `/chronicle`,
+{% data variables.product.prodname_copilot_short %} may send relevant session data, such as prompts, context, and responses, to the AI model.
+
+## Access and retention of session data
+
+The controls available for managing session data depend on where the session data is stored and accessed.
+
+### Locally stored sessions
+
+The following controls are available:
+
+* **Share**: Create a shareable copy of the session as a link, gist, or file.
+* **Delete**: Permanently remove the session from local storage.
+* **Archive**: Move the session out of your active list without deleting it.
+
+| Surface | Share | Delete | Archive |
+| --- | --- | --- | --- |
+| {% data variables.copilot.copilot_cli_short %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} |
+| {% data variables.copilot.github_copilot_app %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} |
+
+For {% data variables.copilot.copilot_cli_short %}, deleting a local session that has been synced to your account prompts you to choose whether to also delete the synced copy. For the {% data variables.copilot.github_copilot_app %}, deleting a local session also deletes its synced copy immediately.
+
+### Sessions stored on {% data variables.product.prodname_dotcom_the_website %}
+
+The following controls are available:
+
+* **Share**: Make the session visible to people with access to the repository.
+* **Delete**: Permanently remove the synced session record from {% data variables.product.prodname_dotcom_the_website %}.
+* **Archive**: Move the session out of the active list on {% data variables.product.prodname_dotcom_the_website %} without deleting it.
+
+| Session source | Share | Delete | Archive |
+| --- | --- | --- | --- |
+| Synced {% data variables.copilot.copilot_cli_short %} session | {% octicon "check" aria-label="Supported" %} (Unshared by default) | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} |
+| Synced {% data variables.copilot.github_copilot_app %} session | {% octicon "check" aria-label="Supported" %} (Unshared by default) | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} |
+| {% data variables.copilot.copilot_cloud_agent %} session | {% octicon "check" aria-label="Supported" %} (Shared by default) | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} |
+
+For more information, see:
+
+* **{% data variables.copilot.copilot_cli_short %}**: [AUTOTITLE](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle)
+* **{% data variables.copilot.github_copilot_app %}**: [AUTOTITLE](/copilot/how-tos/github-copilot-app/agent-sessions)
+* **{% data variables.copilot.copilot_cloud_agent %}**: [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents)
+
+For information about the session data controls available for {% data variables.product.prodname_copilot_short %} in an IDE, see the documentation for that IDE.
+
+## Session management for {% data variables.copilot.copilot_cli_short %}
+
+For {% data variables.copilot.copilot_cli_short %} specifically, you can manage locally stored session data using slash commands or by manually interacting with the session store located at `~/.copilot/session-state/`.
+
+### Deleting session data
+
+You can delete local session data from your machine and from your synced session history on {% data variables.product.prodname_dotcom_the_website %}. Deleting a session removes it from your local session list, your session-history index, and your synced session list.
+
+Use the `/session` commands to delete CLI sessions or delete session data manually from `~/.copilot/session-state/`. See [Deleting sessions](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle#deleting-sessions).
+
+### Re-indexing the session store
+
+If local session files are moved, restored from a backup, or no longer represented in the session store, `/chronicle reindex` rebuilds the session store from the files under `~/.copilot/session-state/`. See [Reindexing the session store](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle#reindexing-the-session-store).
+
+## Further reading
+
+* [AUTOTITLE](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle)
+* [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents)
+* [AUTOTITLE](/copilot/how-tos/github-copilot-app/agent-sessions)
\ No newline at end of file
diff --git a/content/copilot/how-tos/copilot-cli/index.md b/content/copilot/how-tos/copilot-cli/index.md
index b553d3ac0776..474b9f849981 100644
--- a/content/copilot/how-tos/copilot-cli/index.md
+++ b/content/copilot/how-tos/copilot-cli/index.md
@@ -32,7 +32,6 @@ children:
- /content/copilot/concepts/agents/copilot-cli/about-remote-control
- /content/copilot/concepts/agents/copilot-cli/autopilot
- /content/copilot/concepts/agents/copilot-cli/cancel-and-roll-back
- - /content/copilot/concepts/agents/copilot-cli/chronicle
- /content/copilot/concepts/agents/copilot-cli/comparing-cli-features
- /content/copilot/concepts/agents/copilot-cli/context-management
- /content/copilot/concepts/agents/copilot-cli/tool-search
@@ -45,6 +44,7 @@ children:
- /content/copilot/reference/copilot-cli-reference/cli-command-reference
- /content/copilot/reference/copilot-cli-reference/cli-plugin-reference
- /content/copilot/reference/copilot-cli-reference/cli-programmatic-reference
+ - /content/copilot/concepts/security-governance-and-network-settings/session-data
- /content/copilot/reference/hooks-reference
- /content/copilot/responsible-use/agents
- /content/copilot/tutorials/copilot-cli-hooks
diff --git a/content/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle.md b/content/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle.md
index e8b583570d94..db6563ad404b 100644
--- a/content/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle.md
+++ b/content/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle.md
@@ -21,7 +21,7 @@ docsTeamMetrics:
* Use the `/chronicle` slash command to generate standup reports, get personalized tips, and receive suggestions for improving your `.github/copilot-instructions.md` file.
* Ask {% data variables.product.prodname_copilot_short %} questions about your past interactions.
-This article explains how to use these features from {% data variables.copilot.copilot_cli_short %}. For a deeper dive into how session data is stored and synced, see [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle). For querying your sessions from {% data variables.product.prodname_vscode_shortname %}, see [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents).
+This article explains how to use these features from {% data variables.copilot.copilot_cli_short %}. For a deeper dive into how session data is stored and synced, see [AUTOTITLE](/copilot/concepts/security-governance-and-network-settings/session-data). For querying your sessions from {% data variables.product.prodname_vscode_shortname %}, see [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents).
## Resuming a previous session
@@ -232,8 +232,46 @@ Have I worked on anything related to authentication in the last month?
{% data variables.product.prodname_copilot_short %} uses full-text search across your session history to find relevant sessions, then summarizes what you did.
+## Deleting sessions
+
+The simplest way to delete sessions is to use the `/session` slash command in an interactive CLI session.
+
+* `/session delete` deletes the current session and starts a new one in its place.
+* `/session delete SESSION-ID` deletes a specific session. This shows a preview first; add `--yes` to confirm, for example `/session delete SESSION-ID --yes`.
+* `/session delete-all` deletes all of your local sessions except the current one. Add `--yes` to confirm: `/session delete-all --yes`. Sessions that are in use by another process are skipped.
+* `/session prune --older-than DAYS` deletes sessions older than the specified number of days. Add `--dry-run` to preview what would be deleted.
+
+When you delete a session that has been synced to your account, `/session delete` asks whether you also want to delete the synced (remote) copy. Deleting the synced copy also removes the session from your `/chronicle` insights and query results. The `/session delete-all` and `/session prune` subcommands only affect local sessions and do not delete synced data. To remove synced data for those sessions, manually delete it from {% data variables.product.prodname_dotcom_the_website %}.
+
+For the full list of `/session` subcommands, see [AUTOTITLE](/copilot/reference/copilot-cli-reference/cli-command-reference).
+
+### Deleting session data manually
+
+* **Local data**: To remove data for a particular CLI session locally, delete the relevant session directory from `~/.copilot/session-state/`. To clear all local session data, delete everything under `~/.copilot/session-state/`. After doing this you must manually reindex the session store. See [Reindexing the session store](#reindexing-the-session-store) later in this article. Deleting local files does not affect session data that has been synced to your account. You cannot delete synced data locally.
+* **Synced data**: You can delete or hide synced CLI sessions from {% data variables.product.prodname_dotcom_the_website %}. Hiding a session removes it from your session index so it no longer appears in query results. Deleting a session removes it from your session list on {% data variables.product.prodname_dotcom_the_website %}. Deletion applies to CLI, {% data variables.product.prodname_vscode_shortname %}, and {% data variables.copilot.github_copilot_app %} sessions.
+
+## Reindexing the session store
+
+The session store is populated incrementally during a CLI session. Data for a session is written to disk in a session-specific subdirectory of `~/.copilot/session-state/`. This also happens periodically during a session, and also when the session ends.
+
+You can reindex the session store from the session files on disk. Reindexing also syncs your session data to your account.
+
+Situations where you might need to reindex include:
+
+* **Indexing old sessions**: If you have old session files on disk that were created before the session store existed, reindexing will populate the session store with data from those sessions.
+* **Session deletion**: To delete a session from your history, use the `/session delete`, `/session delete-all`, or `/session prune` slash commands. For a synced session, `/session delete` can also remove the synced copy, which removes the session from your `/chronicle` insights.
+* **Migrating/recovering sessions**: If you moved your session files to another machine, or restored them from a backup, without also moving/restoring the session store file (`~/.copilot/session-store.db`), you can use the reindex command to recreate the session store.
+* **File corruption**: If the session store file (`~/.copilot/session-store.db`) becomes corrupted, or is accidentally deleted, you can recover the session store from the session files.
+* **Unexpected termination**: If a session terminates unexpectedly (for example, due to a crash or power loss) before data held in memory has been flushed to the session store you may be able to populate the session store with the missing data if it was written to disk, in the session files, prior to the termination.
+
+To reindex the session store, use the following slash command in an interactive CLI session:
+
+```copilot copy
+/chronicle reindex
+```
+
## Further reading
-* [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle)
+* [AUTOTITLE](/copilot/concepts/security-governance-and-network-settings/session-data)
* [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents)
* [AUTOTITLE](/copilot/reference/copilot-cli-reference/cli-command-reference)
diff --git a/content/copilot/how-tos/copilot-cli/use-copilot-cli/steer-remotely.md b/content/copilot/how-tos/copilot-cli/use-copilot-cli/steer-remotely.md
index 133361dcbffd..0b499549e10a 100644
--- a/content/copilot/how-tos/copilot-cli/use-copilot-cli/steer-remotely.md
+++ b/content/copilot/how-tos/copilot-cli/use-copilot-cli/steer-remotely.md
@@ -128,7 +128,7 @@ A {% data variables.copilot.copilot_cli_short %} session is available in {% data
### Use a QR code to quickly open a session on your phone
1. In an interactive session, enter the `/remote` slash command to redisplay the remote session details.
-1. Press Ctrl+E to toggle on/off display of a QR code.
+1. Press Ctrl+O (letter O) to toggle on/off display of a QR code.
> [!NOTE]
> This keyboard shortcut expands/collapses all details in the session conversation, not just the QR code. It only works if the input field is currently empty.
diff --git a/content/copilot/how-tos/copilot-cli/use-copilot-cli/work-with-multiple-sessions.md b/content/copilot/how-tos/copilot-cli/use-copilot-cli/work-with-multiple-sessions.md
index 3a8ac339f1bd..63e40a55c383 100644
--- a/content/copilot/how-tos/copilot-cli/use-copilot-cli/work-with-multiple-sessions.md
+++ b/content/copilot/how-tos/copilot-cli/use-copilot-cli/work-with-multiple-sessions.md
@@ -196,5 +196,5 @@ The sidebar is available by default. You can turn off the ability to open it, or
For the full list of sidebar settings and their defaults, see [AUTOTITLE](/copilot/reference/copilot-cli-reference/cli-command-reference#sessions-sidebar-settings).
Further reading:
-* [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle)
+* [AUTOTITLE](/copilot/concepts/security-governance-and-network-settings/session-data)
* [AUTOTITLE](/copilot/reference/copilot-cli-reference/cli-command-reference#session-status-indicators)
diff --git a/content/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents.md b/content/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents.md
index 7cc5d246835f..ba71f5bb1396 100644
--- a/content/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents.md
+++ b/content/copilot/how-tos/copilot-on-github/use-copilot-agents/manage-and-track-agents.md
@@ -63,7 +63,7 @@ Archive stopped sessions to remove them from your sessions list.
1. Click **{% octicon "kebab-horizontal" aria-label="More actions" %}**, then click **{% octicon "inbox" aria-hidden="true" aria-label="inbox" %} Archive session**.
1. In the dialog, click **Yes, archive**.
-{% data variables.copilot.copilot_cloud_agent %} sessions can be archived but not deleted. Only local sessions (from {% data variables.copilot.copilot_cli %}, {% data variables.product.prodname_vscode_shortname %}, JetBrains, or the {% data variables.copilot.github_copilot_app %}) can be deleted. For more information about deleting sessions, see [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle#managing-your-session-data).
+{% data variables.copilot.copilot_cloud_agent %} sessions can be archived but not deleted. Only local sessions (from {% data variables.copilot.copilot_cli %}, {% data variables.product.prodname_vscode_shortname %}, JetBrains, or the {% data variables.copilot.github_copilot_app %}) can be deleted. For more information about deleting sessions, see [AUTOTITLE](/copilot/concepts/security-governance-and-network-settings/session-data#deleting-session-data).
## Share a session
@@ -73,7 +73,7 @@ Local sessions (from {% data variables.copilot.copilot_cli %}, {% data variables
Recipients can view the session's prompts, responses, and file changes, but cannot steer or modify the session. Shared local sessions appear in the "All sessions" view but are not indexed for other users' session queries (they will not appear in another user's `/chronicle` results).
-For more information about session syncing, see [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle#session-syncing).
+For more information about session syncing, see [AUTOTITLE](/copilot/concepts/security-governance-and-network-settings/session-data#session-syncing).
## Query past sessions
@@ -83,7 +83,7 @@ You can only query sessions that you started. Your session data is private by de
### Prerequisites
-Your sessions must be synced to your {% data variables.product.github %} account. By default, session data is synced automatically. If you have opted out of syncing (by setting `"remoteExport": false` in your CLI settings JSON), or if your organization has not enabled the "Store local sessions in the Cloud" policy, session querying is not available. For more information, see [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle#session-syncing).
+Your sessions must be synced to your {% data variables.product.github %} account. By default, session data is synced automatically. If you have opted out of syncing (by setting `"remoteExport": false` in your CLI settings JSON), or if your organization has not enabled the "Store local sessions in the Cloud" policy, session querying is not available. For more information, see [AUTOTITLE](/copilot/concepts/security-governance-and-network-settings/session-data#session-syncing).
### Example queries
@@ -105,7 +105,7 @@ Show me sessions where the agent struggled or I had to correct it multiple times
{% data variables.product.prodname_copilot_short %} can search across your prompts, responses, file changes, and other context from your synced sessions.
-For CLI-specific session querying and `/chronicle` commands, see [AUTOTITLE](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle). For conceptual information about how session data is stored and synced, see [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle).
+For CLI-specific session querying and `/chronicle` commands, see [AUTOTITLE](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle). For conceptual information about how session data is stored and synced, see [AUTOTITLE](/copilot/concepts/security-governance-and-network-settings/session-data).
## Continue a session's work in {% data variables.copilot.copilot_chat %}
diff --git a/content/copilot/reference/copilot-cli-reference/cli-command-reference.md b/content/copilot/reference/copilot-cli-reference/cli-command-reference.md
index 3667be6847dd..2769496fdb21 100644
--- a/content/copilot/reference/copilot-cli-reference/cli-command-reference.md
+++ b/content/copilot/reference/copilot-cli-reference/cli-command-reference.md
@@ -432,7 +432,7 @@ These are the slash commands you can use from within an interactive CLI session.
| `/allow-all [off\|auto\|show]`, `/yolo [off\|auto\|show]` | Enable all permissions (tools, paths, and URLs). This is an alias for `/permissions allow-all`; see the `/permissions` row for the canonical command and its subcommands. |
| `/autopilot [OBJECTIVE]`, `/goal [OBJECTIVE]` | Start or refocus autopilot mode, optionally with an explicit objective (for example, `/goal Refactor the auth module`). Without an objective, autopilot infers intent from context, and the status panel shows your last prompt as the inferred objective. You can cap AI-credit spend for the objective by using `--max-ai-credits N` (for example, `/goal Refactor the auth module --max-ai-credits 5`). When the cap is reached, autopilot pauses and opens a panel reporting credits used against the cap. Enter a new amount to resume with a fresh credit window, or dismiss the panel to stay paused. You can also resume a paused objective yourself, without the panel, by running the option on its own with no objective text—for example, `/goal --max-ai-credits 5`. This is the same action the panel performs: it opens a fresh window of the credits you specify (the full new cap, not an increment) and continues the objective. `/goal on` and `/goal off` toggle autopilot mode without setting an objective and don't accept `--max-ai-credits`. An active goal renders as a pinned panel above the prompt box, showing the objective, credits used, and todo progress. The panel auto-collapses to a single identity row on short terminals (below 30 rows) and expands above that threshold; press Ctrl+X then `g` to override the automatic sizing by hand. |
| `/changelog [summarize] [VERSION\|last N\|since VERSION]`, `/release-notes [summarize] [VERSION\|last N\|since VERSION]` | Display the CLI changelog. Optionally specify a version, a count of recent releases, or a starting version. Add the keyword `summarize` for an AI-generated summary. |
-| `/chronicle ` | Session history tools and insights. The `skills` subcommands draft, review, and track the status of repository skill proposals generated from observed usage. See [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle). |
+| `/chronicle ` | Session history tools and insights. The `skills` subcommands draft, review, and track the status of repository skill proposals generated from observed usage. See [AUTOTITLE](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle#using-the-chronicle-slash-command). |
| `/clear [PROMPT]`, `/new [PROMPT]`, `/reset [PROMPT]` | Start a new conversation. |
| `/clikit [COMPONENT]` | Preview CLI business components (for example, quota info). |
| `/compact [FOCUS-INSTRUCTIONS]` | Summarize the conversation history to reduce context window usage. Optionally provide focus instructions to steer the summary—for example, `/compact focus on the auth module`. See [AUTOTITLE](/copilot/concepts/agents/copilot-cli/context-management#compaction). |
diff --git a/content/copilot/reference/copilot-cli-reference/cli-config-dir-reference.md b/content/copilot/reference/copilot-cli-reference/cli-config-dir-reference.md
index 6ea1a3d26a4d..52693c3cfb61 100644
--- a/content/copilot/reference/copilot-cli-reference/cli-config-dir-reference.md
+++ b/content/copilot/reference/copilot-cli-reference/cli-config-dir-reference.md
@@ -328,7 +328,7 @@ Approve one MCP tool, memory writes, and extension permission access:
Contains session history data, organized by session ID in subdirectories. Each session directory stores an event log (`events.jsonl`) and workspace artifacts (plans, checkpoints, tracked files). This data enables session resume (`--resume` or `--continue`).
-Deleting files from this directory only removes local copies. If you have synced sessions to your {% data variables.product.github %} account, the synced data is stored separately and is not affected by local file deletion. You can delete or hide synced sessions from {% data variables.product.prodname_dotcom_the_website %}. For more information, see [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle#managing-your-session-data).
+Deleting files from this directory only removes local copies. If you have synced sessions to your {% data variables.product.github %} account, the synced data is stored separately and is not affected by local file deletion. You can delete or hide synced sessions from {% data variables.product.prodname_dotcom_the_website %}. For more information, see [AUTOTITLE](/copilot/concepts/security-governance-and-network-settings/session-data#deleting-session-data).
### `command-history-state/`
diff --git a/content/copilot/tutorials/optimize-ai-usage.md b/content/copilot/tutorials/optimize-ai-usage.md
index afd08196ce39..f1348f34861e 100644
--- a/content/copilot/tutorials/optimize-ai-usage.md
+++ b/content/copilot/tutorials/optimize-ai-usage.md
@@ -148,7 +148,7 @@ In {% data variables.copilot.copilot_cli_short %}, `/chronicle` can generate use
* Use `/chronicle tips` to analyze your recent session history and surface opportunities to use {% data variables.product.prodname_copilot_short %} more efficiently.
* Use `/chronicle cost-tips` to understand your token usage patterns and get insights into how to reduce cost.
-See [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle#the-chronicle-slash-command).
+See [AUTOTITLE](/copilot/how-tos/copilot-cli/use-copilot-cli/chronicle#using-the-chronicle-slash-command).
### Feed insights into a `copilot-instructions.md` file
diff --git a/content/pull-requests/reference/forks.md b/content/pull-requests/reference/forks.md
index f0858468edf5..56a22814ed9d 100644
--- a/content/pull-requests/reference/forks.md
+++ b/content/pull-requests/reference/forks.md
@@ -107,7 +107,7 @@ Forks are powerful collaboration tools, but they can expose code and history in
* Organization owners may have administrative access to forks created in personal namespaces.
* Organization owners of a repository that has been forked have read permission to forks created in organizations, but do not have the ability to delete the fork or its branches.
* Forks created in another organization will not be deleted when individual access is removed from the upstream repository.
-* Commits to any repository in a network can be accessed from any repository in the same network, including the upstream repository, even after a fork is deleted.
+* Git data from any repository in a network may be accessed from any repository in the same network, including the upstream repository, even after a fork is deleted.
Before allowing forks for sensitive work, review the permissions and visibility model for your repository or organization.
diff --git a/data/reusables/copilot/copilot-cli/synced-sessions-view-only.md b/data/reusables/copilot/copilot-cli/synced-sessions-view-only.md
index 3408427a6bc4..1eaa70d8efe6 100644
--- a/data/reusables/copilot/copilot-cli/synced-sessions-view-only.md
+++ b/data/reusables/copilot/copilot-cli/synced-sessions-view-only.md
@@ -1 +1 @@
-Remote access is different from session syncing. Your {% data variables.copilot.copilot_cli_short %} sessions are synced to your {% data variables.product.github %} account by default, and synced sessions appear as view-only on {% data variables.product.prodname_dotcom_the_website %} and {% data variables.product.prodname_mobile %}. These sessions are not steerable. To steer a session remotely, you must enable remote control. For more information about session syncing, see [AUTOTITLE](/copilot/concepts/agents/copilot-cli/chronicle#session-syncing).
+Remote access is different from session syncing. Your {% data variables.copilot.copilot_cli_short %} sessions are synced to your {% data variables.product.github %} account by default, and synced sessions appear as view-only on {% data variables.product.prodname_dotcom_the_website %} and {% data variables.product.prodname_mobile %}. These sessions are not steerable. To steer a session remotely, you must enable remote control. For more information about session syncing, see [AUTOTITLE](/copilot/concepts/security-governance-and-network-settings/session-data#session-syncing).
diff --git a/package-lock.json b/package-lock.json
index 00a884910dd5..d7e61b307e2b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -44,7 +44,6 @@
"cookie-parser": "^1.4.7",
"cuss": "2.2.0",
"dayjs": "^1.11.19",
- "dereference-json-schema": "^0.2.1",
"dotenv": "^17.3.1",
"escape-string-regexp": "5.0.0",
"express": "^5.2.1",
@@ -87,7 +86,6 @@
"quick-lru": "7.0.1",
"react": "^19.2.5",
"react-dom": "^19.2.5",
- "react-is": "^19.2.4",
"react-markdown": "^10.1.0",
"rehype-highlight": "^7.0.2",
"rehype-raw": "^7.0.0",
@@ -186,9 +184,6 @@
},
"engines": {
"node": "^24 || ^26"
- },
- "optionalDependencies": {
- "esm": "^3.2.25"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -7220,12 +7215,6 @@
"node": ">=6"
}
},
- "node_modules/dereference-json-schema": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/dereference-json-schema/-/dereference-json-schema-0.2.1.tgz",
- "integrity": "sha512-uzJsrg225owJyRQ8FNTPHIuBOdSzIZlHhss9u6W8mp7jJldHqGuLv9cULagP/E26QVJDnjtG8U7Dw139mM1ydA==",
- "license": "MIT"
- },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -8351,15 +8340,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/esm": {
- "version": "3.2.25",
- "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
- "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
- "optional": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/espree": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
@@ -13518,7 +13498,8 @@
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
"integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/react-markdown": {
"version": "10.1.0",
diff --git a/package.json b/package.json
index 5baa4f7ab728..995fc6ee7dec 100644
--- a/package.json
+++ b/package.json
@@ -206,7 +206,6 @@
"cookie-parser": "^1.4.7",
"cuss": "2.2.0",
"dayjs": "^1.11.19",
- "dereference-json-schema": "^0.2.1",
"dotenv": "^17.3.1",
"escape-string-regexp": "5.0.0",
"express": "^5.2.1",
@@ -249,7 +248,6 @@
"quick-lru": "7.0.1",
"react": "^19.2.5",
"react-dom": "^19.2.5",
- "react-is": "^19.2.4",
"react-markdown": "^10.1.0",
"rehype-highlight": "^7.0.2",
"rehype-raw": "^7.0.0",
@@ -370,9 +368,6 @@
"sharp": "$sharp",
"fast-uri": "^3.1.7"
},
- "optionalDependencies": {
- "esm": "^3.2.25"
- },
"engines": {
"node": "^24 || ^26"
},
diff --git a/src/workflows/sync-sdk-docs/convert-mermaid.ts b/src/workflows/sync-sdk-docs/convert-mermaid.ts
index ebfeea654ce8..dbcd214ae81d 100644
--- a/src/workflows/sync-sdk-docs/convert-mermaid.ts
+++ b/src/workflows/sync-sdk-docs/convert-mermaid.ts
@@ -1,20 +1,16 @@
#!/usr/bin/env node
-/**
- * Converts Mermaid code blocks in SDK docs to PNG images.
- *
- * For each ```mermaid block found in markdown files:
- * - Extracts the Mermaid source
- * - Renders it to PNG using @mermaid-js/mermaid-cli (mmdc)
- * - Saves the PNG to the assets directory
- * - Replaces the code block with an image reference
- *
- * Filenames are deterministic based on source file path and block index,
- * so re-running produces stable results.
- *
- * Usage:
- * node convert-mermaid.mjs --sdk-docs-dir --assets-dir
- */
+// Renders each ```mermaid block in the SDK docs to a PNG with
+// @mermaid-js/mermaid-cli (mmdc), saves it under the assets directory, and
+// replaces the code block with an image reference. A block whose render fails
+// is left as it is.
+//
+// Filenames come from the source file path and the block index, so re-running
+// produces stable results.
+//
+// Usage:
+// npx tsx src/workflows/sync-sdk-docs/convert-mermaid.ts --sdk-docs-dir \
+// --assets-dir [--repo-root ] [--puppeteer-config ]
import fs from 'node:fs'
import path from 'node:path'
@@ -40,7 +36,7 @@ if (!fs.existsSync(SDK_DOCS_DIR)) {
process.exit(1)
}
-// Find mmdc binary — check global PATH first, then local node_modules
+// Find the mmdc binary: global PATH first, then local node_modules.
let MMDC_BIN: string
try {
MMDC_BIN = execSync('which mmdc', { encoding: 'utf8' }).trim()
@@ -54,7 +50,7 @@ try {
}
}
-/** Recursively collect all .md files. */
+// Recursively collect all .md files.
function getAllMarkdownFiles(dir: string): string[] {
const results: string[] = []
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
@@ -68,24 +64,19 @@ function getAllMarkdownFiles(dir: string): string[] {
return results
}
-/**
- * Generate a deterministic filename for a mermaid diagram.
- * Based on the source file's relative path and the block index.
- */
+// Generates a filename from the source file's relative path and the block
+// index, so it is stable across runs.
function generateImageName(filePath: string, blockIndex: number): string {
const rel = path.relative(SDK_DOCS_DIR, filePath).replace(/\.md$/, '').replace(/\//g, '-')
return `${rel}-diagram-${blockIndex}.png`
}
-/**
- * Generate alt text from a mermaid diagram source.
- * Uses the diagram type and first meaningful line.
- */
+// Builds generic alt text from the diagram type named on the first line. The
+// contents of the diagram are not used.
function generateAltText(mermaidSource: string): string {
const lines = mermaidSource.trim().split('\n')
const firstLine = lines[0].trim()
- // Extract the diagram type
const typeMatch = firstLine.match(
/^(flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|graph|gitGraph|journey|mindmap|timeline|quadrantChart|sankey|xychart)/i,
)
@@ -115,14 +106,10 @@ function generateAltText(mermaidSource: string): string {
return 'Diagram illustrating the described process.'
}
-/**
- * Process a single markdown file, converting all mermaid blocks to PNG.
- * Returns the number of conversions performed.
- */
+// Converts the mermaid blocks in one file and returns how many succeeded.
function processFile(filePath: string, assetsUrlPath: string): number {
const raw = fs.readFileSync(filePath, 'utf8')
- // Match ```mermaid ... ``` blocks
const mermaidRegex = /```mermaid\n([\s\S]*?)```/g
const matches = [...raw.matchAll(mermaidRegex)]
@@ -139,7 +126,6 @@ function processFile(filePath: string, assetsUrlPath: string): number {
const altText = generateAltText(mermaidSource)
const imagePath = path.join(ASSETS_DIR, imageName)
- // Write mermaid source to temp file
const tmpFile = path.join(ASSETS_DIR, `_tmp_${imageName}.mmd`)
fs.writeFileSync(tmpFile, mermaidSource, 'utf8')
@@ -168,7 +154,6 @@ function processFile(filePath: string, assetsUrlPath: string): number {
` WARN (render failed): ${path.relative(SDK_DOCS_DIR, filePath)} block ${i}: ${(err as Error).message}`,
)
} finally {
- // Clean up temp file
if (fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile)
}
}
@@ -180,11 +165,8 @@ function processFile(filePath: string, assetsUrlPath: string): number {
return converted
}
-// --- Main ---
-
console.log('--- Converting Mermaid diagrams to PNG ---\n')
-// Ensure assets directory exists
fs.mkdirSync(ASSETS_DIR, { recursive: true })
// Compute the URL path for image references
diff --git a/src/workflows/sync-sdk-docs/normalize-sdk-docs.ts b/src/workflows/sync-sdk-docs/normalize-sdk-docs.ts
index 57901202dc25..d7cd5bf4b26c 100644
--- a/src/workflows/sync-sdk-docs/normalize-sdk-docs.ts
+++ b/src/workflows/sync-sdk-docs/normalize-sdk-docs.ts
@@ -1,29 +1,15 @@
#!/usr/bin/env node
-/**
- * Normalizes Copilot SDK docs for publishing on docs.github.com.
- *
- * For every .md file in the SDK docs directory, this script:
- * - Removes pages that have been relocated out of the synced tree
- * (see RELOCATED_PAGES) and repoints inbound links at their new URLs
- * - Renames README.md files to index.md (the SDK repo uses README.md as the
- * landing page for each docs directory; docs-internal requires index.md)
- * - Adds YAML frontmatter (title, intro, shortTitle, versions, contentType)
- * - Adds `children` arrays to index.md files
- * - Removes `docs-validate: hidden` ranges (validation-only code samples that
- * must not reach readers)
- * - Converts consecutive language blocks to {% codetabs %} syntax
- * - Rewrites internal relative .md links to [AUTOTITLE](/path) format
- * - Rewrites absolute docs.github.com links to [AUTOTITLE](/path) format
- * - Creates missing index.md files for subdirectories
- * - Fixes code fence language aliases (go → golang, ts → typescript)
- * - Normalizes ordered list prefixes to 1.
- *
- * Adapted from the spike normalization script in docs-internal#60525.
- *
- * Usage:
- * node normalize-sdk-docs.mjs --content-dir --sdk-docs-dir
- */
+// Normalizes Copilot SDK docs for publishing on docs.github.com. The steps are
+// called at the bottom of this file, roughly but not exactly in numeric order:
+// Step 0a runs before Step 0, and Step 1b after Step 1. Where the ordering
+// matters, the step's own comment says why.
+//
+// Adapted from the spike normalization script in docs-internal#60525.
+//
+// Usage:
+// npx tsx src/workflows/sync-sdk-docs/normalize-sdk-docs.ts --content-dir \
+// --sdk-docs-dir
import fs from 'node:fs'
import path from 'node:path'
@@ -32,7 +18,6 @@ import matter from '@gr2m/gray-matter'
import { stripHiddenBlocks, nextFenceState, type OpenFence } from './strip-hidden-blocks'
-// Parse CLI arguments
const { values: args } = parseArgs({
options: {
'content-dir': { type: 'string' },
@@ -53,7 +38,7 @@ const SDK_DOCS_DIR = path.resolve(args['sdk-docs-dir'] as string)
*
* Each entry does two inseparable things on every sync:
* 1. Deletes the upstream copy after it is rsynced in (Step 0a), so the page
- * is not republished at its old URL — that URL is now a `redirect_from` on
+ * is not republished at its old URL. That URL is now a `redirect_from` on
* the hand-authored page and must stay vacant.
* 2. Teaches the internal-link rewriter (Step 3) to point inbound relative
* links at the new URL, instead of logging "target missing" and leaving a
@@ -69,7 +54,7 @@ const RELOCATED_PAGES: Record = {
'getting-started.md': '/copilot/get-started/sdk-quickstart',
}
-/** Relocated pages whose upstream source file was not found during this sync. */
+// Relocated pages whose upstream source file was not found during this sync.
const missingRelocatedSources: string[] = []
if (!fs.existsSync(CONTENT_DIR)) {
@@ -81,9 +66,7 @@ if (!fs.existsSync(SDK_DOCS_DIR)) {
process.exit(1)
}
-// --- Helpers ---
-
-/** Recursively collect all .md files in a directory. */
+// Recursively collect all .md files in a directory.
function getAllMarkdownFiles(dir: string): string[] {
const results: string[] = []
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
@@ -173,10 +156,8 @@ function convertReadmesToIndex(): void {
}
}
-/**
- * Return the new URL for a relocated page, given an absolute path inside the
- * SDK docs tree. Returns undefined for pages that have not been relocated.
- */
+// Returns the new URL for a relocated page, or undefined for a page that has
+// not been relocated.
function relocatedUrlFor(absPath: string): string | undefined {
return RELOCATED_PAGES[path.relative(SDK_DOCS_DIR, absPath)]
}
@@ -255,7 +236,7 @@ function reportMissingRelocatedSources(): void {
'These pages are listed in `RELOCATED_PAGES` but no longer exist in',
'[copilot-sdk docs](https://github.com/github/copilot-sdk/tree/main/docs).',
'If upstream **renamed** the file, it is now republishing under a new URL and may have',
- 'reclaimed the URL this move vacated — update `RELOCATED_PAGES`. If upstream',
+ 'reclaimed the URL this move vacated. Update `RELOCATED_PAGES`. If upstream',
'**deleted** it deliberately, remove the entry instead.',
'',
...missingRelocatedSources.map((source) => `* \`${source}\``),
@@ -265,7 +246,7 @@ function reportMissingRelocatedSources(): void {
fs.appendFileSync(summaryPath, lines.join('\n'))
}
-/** Convert a filename slug to a title-case short title. */
+// Convert a filename slug to a title-case short title.
function slugToTitle(slug: string): string {
const ACRONYMS: Record = {
cli: 'CLI',
@@ -284,7 +265,7 @@ function slugToTitle(slug: string): string {
.join(' ')
}
-/** Return the children entries for an index.md file. */
+// Return the children entries for an index.md file.
function getChildren(indexPath: string): string[] {
const dir = path.dirname(indexPath)
const entries = fs.readdirSync(dir, { withFileTypes: true })
@@ -307,10 +288,9 @@ function getChildren(indexPath: string): string[] {
return children.sort()
}
-/**
- * Convert a resolved absolute file path to a docs URL path.
- * e.g. /…/content/copilot/sdk-docs/setup/local-cli.md → /copilot/sdk-docs/setup/local-cli
- */
+// Converts an absolute file path to a docs URL path, so
+// /content/copilot/sdk-docs/setup/local-cli.md becomes
+// /copilot/sdk-docs/setup/local-cli.
function filePathToUrlPath(absPath: string): string {
let rel = path.relative(CONTENT_DIR, absPath)
rel = rel.replace(/\.md$/, '')
@@ -318,16 +298,11 @@ function filePathToUrlPath(absPath: string): string {
return `/${rel}`
}
-// --- Processing steps ---
-
-/**
- * Step 1: Add frontmatter to a markdown file.
- * Extracts title from the first H1, intro from the first paragraph.
- */
+// Step 1: Add frontmatter, taking the title from the first H1 and the intro
+// from the first paragraph.
function addFrontmatter(filePath: string): void {
const raw = fs.readFileSync(filePath, 'utf8')
- // Skip files that already have frontmatter
if (raw.startsWith('---')) {
console.log(` SKIP (has frontmatter): ${path.relative(SDK_DOCS_DIR, filePath)}`)
return
@@ -335,7 +310,6 @@ function addFrontmatter(filePath: string): void {
const lines = raw.split('\n')
- // Extract title from first H1
let title = ''
let titleLineIndex = -1
for (let i = 0; i < lines.length; i++) {
@@ -352,7 +326,6 @@ function addFrontmatter(filePath: string): void {
title = path.basename(filePath, '.md')
}
- // Extract intro: first non-empty paragraph after the title
let intro = ''
let introEndIndex = titleLineIndex
if (titleLineIndex >= 0) {
@@ -368,11 +341,10 @@ function addFrontmatter(filePath: string): void {
intro = paraLines.join(' ')
}
- // Compute shortTitle from filename for slugified-title test compatibility
+ // shortTitle comes from the filename so the slugified-title test passes.
const basename = path.basename(filePath, '.md')
const shortTitle = basename === 'index' ? undefined : slugToTitle(basename)
- // Build frontmatter
const frontmatterData: Record = {
title,
...(shortTitle && { shortTitle }),
@@ -386,7 +358,6 @@ function addFrontmatter(filePath: string): void {
frontmatterData.children = getChildren(filePath)
}
- // Remove the title line and intro paragraph from the body
const bodyLines = [...lines]
if (titleLineIndex >= 0) {
bodyLines.splice(titleLineIndex, introEndIndex - titleLineIndex)
@@ -404,9 +375,7 @@ function addFrontmatter(filePath: string): void {
console.log(` OK: ${path.relative(SDK_DOCS_DIR, filePath)}`)
}
-/**
- * Step 3: Rewrite internal relative .md links to [AUTOTITLE](/url-path) format.
- */
+// Step 3: Rewrite internal relative .md links to [AUTOTITLE](/url-path).
function rewriteInternalLinks(filePath: string): void {
const raw = fs.readFileSync(filePath, 'utf8')
const dir = path.dirname(filePath)
@@ -453,17 +422,14 @@ function rewriteInternalLinks(filePath: string): void {
}
}
-/**
- * Step 3b: Rewrite repo-relative links that point outside the docs tree.
- * These are links like ../nodejs/README.md that should point to the SDK repo on GitHub.
- * Catches any remaining relative .md links that Step 3 didn't convert to AUTOTITLE.
- */
+// Step 3b: Rewrite the ./ and ../ .md links Step 3 could not resolve into
+// links to the SDK repo on GitHub. Mostly these point outside the docs tree,
+// such as ../nodejs/README.md, but a missing in-tree target lands here too.
function rewriteRepoRelativeLinks(filePath: string): void {
const raw = fs.readFileSync(filePath, 'utf8')
const dir = path.dirname(filePath)
const SDK_REPO_URL = 'https://github.com/github/copilot-sdk/tree/main'
- // Match relative .md links that were NOT already rewritten to AUTOTITLE
const linkRegex = /\[([^\]]+)\]\((\.{1,2}\/[^)]*\.md(?:#[^)]*)?)\)/g
let changed = false
@@ -471,26 +437,24 @@ function rewriteRepoRelativeLinks(filePath: string): void {
const [rawPath, anchor] = href.split('#', 2)
const resolved = path.resolve(dir, rawPath)
- // Skip if the file actually exists in the content tree (should have been handled by Step 3)
if (fs.existsSync(resolved)) return _match
- // Compute where this file would be in the SDK repo.
- // SDK docs are at content/copilot/sdk-docs/ which maps to copilot-sdk/docs/
- // So a link from content/copilot/sdk-docs/getting-started.md to ../nodejs/README.md
- // resolves to content/copilot/nodejs/README.md → which in the SDK repo is nodejs/README.md
+ // content/copilot/sdk-docs/ maps to copilot-sdk/docs/, so a link from
+ // content/copilot/sdk-docs/getting-started.md to ../nodejs/README.md
+ // resolves to content/copilot/nodejs/README.md, which in the SDK repo is
+ // nodejs/README.md.
const relFromSdkDocs = path.relative(SDK_DOCS_DIR, resolved)
- // Links starting with ../ from SDK_DOCS_DIR go up to the repo root
- // e.g. ../nodejs/README.md from sdk-docs/ → ../../nodejs/README.md from content/copilot/sdk-docs/
- // relFromSdkDocs would be like "../nodejs/README.md"
- // We strip leading ../ segments to get the repo-root-relative path
+ // One leading ../ reaches the repo root, so relFromSdkDocs looks like
+ // "../nodejs/README.md". Strip the leading ../ segments. A target more than
+ // one level above SDK_DOCS_DIR is outside the repo entirely and still gets
+ // a plausible-looking repo URL.
const parts = relFromSdkDocs.split(path.sep)
let upCount = 0
for (const part of parts) {
if (part === '..') upCount++
else break
}
- // The repo path is everything after the ".." segments
const repoPath = parts.slice(upCount).join('/')
const anchorSuffix = anchor ? `#${anchor}` : ''
@@ -504,9 +468,8 @@ function rewriteRepoRelativeLinks(filePath: string): void {
}
}
-/**
- * Step 4: Rewrite absolute docs.github.com links to [AUTOTITLE](/url-path).
- */
+// Step 4: Strip the docs.github.com domain from markdown links. A target found
+// in CONTENT_DIR also gets its link text replaced with AUTOTITLE.
function rewriteDocsGitHubLinks(filePath: string): void {
const raw = fs.readFileSync(filePath, 'utf8')
@@ -544,9 +507,7 @@ function rewriteDocsGitHubLinks(filePath: string): void {
}
}
-/**
- * Step 5: Create missing index.md files for subdirectories.
- */
+// Step 5: Create missing index.md files for subdirectories.
function createMissingIndexFiles(): string[] {
const created: string[] = []
@@ -557,12 +518,10 @@ function createMissingIndexFiles(): string[] {
const dirPath = path.join(dir, entry.name)
const indexPath = path.join(dirPath, 'index.md')
- // Recurse into subdirectories
walk(dirPath)
if (fs.existsSync(indexPath)) continue
- // Check that the directory has at least one .md file
const dirFiles = fs.readdirSync(dirPath)
if (!dirFiles.some((f) => f.endsWith('.md'))) continue
@@ -587,10 +546,7 @@ function createMissingIndexFiles(): string[] {
return created
}
-/**
- * Step 6: Fix code fence language aliases.
- * Replaces ```go with ```golang and ```ts with ```typescript.
- */
+// Step 6: Replace ```go with ```golang and ```ts with ```typescript.
function fixCodeFenceLanguages(filePath: string): void {
const raw = fs.readFileSync(filePath, 'utf8')
@@ -617,10 +573,7 @@ function fixCodeFenceLanguages(filePath: string): void {
}
}
-/**
- * Step 7: Normalize ordered list prefixes to all use 1.
- * Changes "2. foo", "3. bar" etc. to "1. foo", "1. bar".
- */
+// Step 7: Renumber ordered lists so every item uses "1.".
function normalizeOrderedLists(filePath: string): void {
const raw = fs.readFileSync(filePath, 'utf8')
const lines = raw.split('\n')
@@ -629,7 +582,6 @@ function normalizeOrderedLists(filePath: string): void {
let inCodeBlock = false
for (let i = 0; i < lines.length; i++) {
- // Track code blocks to avoid modifying code
if (lines[i].trimStart().startsWith('```')) {
inCodeBlock = !inCodeBlock
continue
@@ -649,10 +601,7 @@ function normalizeOrderedLists(filePath: string): void {
}
}
-/**
- * Step 8: Add language to bare code fences.
- * Fences without a language (```) get labeled as ```text.
- */
+// Step 8: MD040 wants a language on every fence, so label a bare one ```text.
function fixBareCodeFences(filePath: string): void {
const raw = fs.readFileSync(filePath, 'utf8')
const lines = raw.split('\n')
@@ -664,10 +613,10 @@ function fixBareCodeFences(filePath: string): void {
const isBare = /^\s*```\s*$/.test(lines[i])
if (isBare) {
if (inCodeBlock) {
- // Closing fence — leave as-is
+ // Closing fence, leave as-is.
inCodeBlock = false
} else {
- // Opening fence with no language — add 'text'
+ // Opening fence with no language, so add 'text'.
lines[i] = lines[i].replace(/```/, '```text')
inCodeBlock = true
changed = true
@@ -683,10 +632,7 @@ function fixBareCodeFences(filePath: string): void {
}
}
-/**
- * Step 9: Ensure blank lines around code fences.
- * MD031 requires a blank line before and after fenced code blocks.
- */
+// Step 9: MD031 wants a blank line before and after every fenced code block.
function fixBlanksAroundFences(filePath: string): void {
const raw = fs.readFileSync(filePath, 'utf8')
const lines = raw.split('\n')
@@ -700,7 +646,8 @@ function fixBlanksAroundFences(filePath: string): void {
if (isFence) {
if (!inCodeBlock) {
- // Opening fence — ensure blank line before (unless start of file or already blank)
+ // Opening fence, so add a blank line before it unless this is the start
+ // of the file or the previous line is already blank.
if (result.length > 0 && result[result.length - 1].trim() !== '') {
result.push('')
changed = true
@@ -708,7 +655,7 @@ function fixBlanksAroundFences(filePath: string): void {
result.push(line)
inCodeBlock = true
} else {
- // Closing fence — push as-is, then ensure blank line after
+ // Closing fence, so push it and then add a blank line after.
result.push(line)
inCodeBlock = false
if (i + 1 < lines.length && lines[i + 1].trim() !== '') {
@@ -782,12 +729,10 @@ function reportUnbalancedMarkers(): void {
fs.appendFileSync(summaryPath, lines.join('\n'), 'utf8')
}
-/**
- * Step 2: Convert consecutive language blocks to codetabs.
- * SDK source docs use Language
- * blocks for multi-language examples. This converts groups of 2+ consecutive
- * details blocks into {% codetabs %}/{% codetab %} Liquid syntax.
- */
+// Step 2: SDK source docs use Language
+//
blocks for multi-language examples. Convert a group of two or
+// more consecutive ones to {% codetabs %}/{% codetab %} Liquid syntax. A block
+// whose label has no codetab key is warned about and dropped from the output.
// Maps label text to codetab language keys
const LABEL_TO_CODETAB_KEY: Record = {
@@ -837,7 +782,7 @@ function convertDetailsToCodetabs(filePath: string): void {
continue
}
- // Found a tag outside a code block — try to collect a group
+ // A tag outside a code block, so try to collect a group.
const group: DetailsBlock[] = []
const groupStartLine = i
@@ -861,7 +806,6 @@ function convertDetailsToCodetabs(filePath: string): void {
}
}
- // Only convert groups of 2+ blocks
if (group.length < 2) {
// When the first block fails to parse, `i` never moved — which happens
// for an inline `` mention in prose, since fence tracking does
@@ -871,14 +815,12 @@ function convertDetailsToCodetabs(filePath: string): void {
i++
continue
}
- // Emit original lines unchanged
for (let j = groupStartLine; j < i; j++) {
result.push(lines[j])
}
continue
}
- // Check if all blocks have valid codetab keys
const unsupported = group.filter((b) => !b.codetabKey)
if (unsupported.length > 0) {
for (const b of unsupported) {
@@ -888,17 +830,16 @@ function convertDetailsToCodetabs(filePath: string): void {
}
}
- // Filter to only supported tabs
+ // Unsupported blocks are dropped, not passed through.
const convertible = group.filter((b) => b.codetabKey)
if (convertible.length < 2) {
- // Not enough convertible tabs — emit original lines
+ // Not enough convertible tabs, so emit the original lines.
for (let j = groupStartLine; j < i; j++) {
result.push(lines[j])
}
continue
}
- // Emit codetabs
changed = true
result.push('{% codetabs %}')
for (const block of convertible) {
@@ -919,17 +860,14 @@ function convertDetailsToCodetabs(filePath: string): void {
}
}
-/**
- * Parse a single block starting at line index `start`.
- * Returns the block info or null if the block doesn't match expected structure.
- */
+// Parses one block starting at line index `start`, returning null
+// when the block does not match the expected structure.
function parseDetailsBlock(lines: string[], start: number): DetailsBlock | null {
if (!/]/.test(lines[start])) return null
let i = start + 1
let label = ''
- // Find the line
while (i < lines.length) {
const summaryMatch = lines[i].match(/(.*?)<\/strong><\/summary>/)
if (summaryMatch) {
@@ -946,7 +884,6 @@ function parseDetailsBlock(lines: string[], start: number): DetailsBlock | null
if (!label) return null
- // Collect inner content until
const innerLines: string[] = []
while (i < lines.length) {
if (/<\/details>/.test(lines[i])) {
@@ -960,11 +897,10 @@ function parseDetailsBlock(lines: string[], start: number): DetailsBlock | null
const endLine = i // The line
- // Hidden ranges are already gone (Step 1b), including any unbalanced marker
- // left deliberately in place, so only blank-line trimming is needed here.
+ // Step 1b already removed the balanced hidden ranges. An unbalanced one is
+ // left in place deliberately, so only blank-line trimming is needed here.
const cleaned = [...innerLines]
- // Trim leading and trailing blank lines
while (cleaned.length > 0 && cleaned[0].trim() === '') cleaned.shift()
while (cleaned.length > 0 && cleaned[cleaned.length - 1].trim() === '') cleaned.pop()
@@ -979,15 +915,11 @@ function parseDetailsBlock(lines: string[], start: number): DetailsBlock | null
}
}
-/**
- * Step 10: Rewrite remaining raw docs.github.com URLs (not in markdown links).
- * Catches bare URLs and URLs in other contexts that the link rewriter missed.
- */
+// Step 10: Rewrite the raw docs.github.com URLs left over from Step 4, which
+// are the ones not inside markdown link syntax.
function rewriteBareDocsUrls(filePath: string): void {
const raw = fs.readFileSync(filePath, 'utf8')
- // Match bare docs.github.com URLs NOT already inside markdown link syntax
- // Skip URLs that are already in [text](url) format (handled by Step 4)
let changed = false
const updated = raw.replace(
/(?\]]+)/g,
@@ -1008,21 +940,16 @@ function rewriteBareDocsUrls(filePath: string): void {
}
}
-/**
- * Step 11: Suppress SDK-specific lint rules.
- * Adds a markdownlint-disable comment after frontmatter for rules that
- * don't apply to SDK docs (per docs pipeline proposal tradeoffs).
- */
+// Step 11: Add a markdownlint-disable comment after the frontmatter for the
+// rules that don't apply to SDK docs, per the docs pipeline proposal.
function suppressSdkLintRules(filePath: string): void {
const raw = fs.readFileSync(filePath, 'utf8')
const SUPPRESS_COMMENT =
'\n' +
'\n'
- // Skip if already has the suppression
if (raw.includes('markdownlint-disable GHD046')) return
- // Insert after the closing frontmatter ---
const fmEnd = raw.indexOf('---', raw.indexOf('---') + 3)
if (fmEnd === -1) return
@@ -1033,8 +960,6 @@ function suppressSdkLintRules(filePath: string): void {
console.log(` SUPPRESS: ${path.relative(SDK_DOCS_DIR, filePath)}`)
}
-// --- Main ---
-
console.log(`Normalizing SDK docs in: ${SDK_DOCS_DIR}`)
console.log(`Content directory: ${CONTENT_DIR}\n`)
@@ -1051,7 +976,6 @@ reportMissingRelocatedSources()
console.log('\n--- Renaming README.md files to index.md ---\n')
convertReadmesToIndex()
-// Step 1: Add frontmatter
console.log('\n--- Adding frontmatter ---\n')
const files = getAllMarkdownFiles(SDK_DOCS_DIR)
console.log(`Found ${files.length} markdown files.\n`)
@@ -1059,75 +983,64 @@ for (const file of files) {
addFrontmatter(file)
}
-// Step 1b: Remove docs-validate: hidden ranges before anything rewrites the
-// blocks that contain them.
+// Step 1b: Remove docs-validate: hidden ranges before the codetabs conversion
+// rewrites the groups that contain them.
console.log('\n--- Removing docs-validate: hidden blocks ---\n')
for (const file of files) {
stripHiddenValidationBlocks(file)
}
reportUnbalancedMarkers()
-// Step 2: Convert language blocks to codetabs
console.log('\n--- Converting details blocks to codetabs ---\n')
for (const file of files) {
convertDetailsToCodetabs(file)
}
-// Step 3: Rewrite internal links
console.log('\n--- Rewriting internal links ---\n')
const allFiles = getAllMarkdownFiles(SDK_DOCS_DIR)
for (const file of allFiles) {
rewriteInternalLinks(file)
}
-// Step 3b: Rewrite repo-relative links (outside content tree)
console.log('\n--- Rewriting repo-relative links ---\n')
for (const file of allFiles) {
rewriteRepoRelativeLinks(file)
}
-// Step 4: Rewrite docs.github.com links
console.log('\n--- Rewriting docs.github.com links ---\n')
for (const file of allFiles) {
rewriteDocsGitHubLinks(file)
}
-// Step 5: Create missing index files
console.log('\n--- Creating missing index.md files ---\n')
createMissingIndexFiles()
-// Step 6: Fix code fence languages
console.log('\n--- Fixing code fence languages ---\n')
const updatedFiles = getAllMarkdownFiles(SDK_DOCS_DIR)
for (const file of updatedFiles) {
fixCodeFenceLanguages(file)
}
-// Step 7: Normalize ordered lists
console.log('\n--- Normalizing ordered lists ---\n')
for (const file of updatedFiles) {
normalizeOrderedLists(file)
}
-// Step 8: Fix bare code fences (MD040)
console.log('\n--- Fixing bare code fences ---\n')
for (const file of updatedFiles) {
fixBareCodeFences(file)
}
-// Step 9: Ensure blank lines around fences (MD031)
console.log('\n--- Fixing blank lines around fences ---\n')
for (const file of updatedFiles) {
fixBlanksAroundFences(file)
}
-// Step 10: Rewrite remaining bare docs.github.com URLs
console.log('\n--- Rewriting bare docs.github.com URLs ---\n')
for (const file of updatedFiles) {
rewriteBareDocsUrls(file)
}
-// Step 11: Suppress SDK-specific lint rules (GHD046, GHD005)
console.log('\n--- Suppressing SDK-specific lint rules ---\n')
const finalFiles = getAllMarkdownFiles(SDK_DOCS_DIR)
for (const file of finalFiles) {
diff --git a/src/workflows/sync-sdk-docs/strip-hidden-blocks.ts b/src/workflows/sync-sdk-docs/strip-hidden-blocks.ts
index 261cf9b5383c..b467fc2393fe 100644
--- a/src/workflows/sync-sdk-docs/strip-hidden-blocks.ts
+++ b/src/workflows/sync-sdk-docs/strip-hidden-blocks.ts
@@ -23,7 +23,7 @@
*
* Nothing enforced the publishing half of that contract. The markers are plain
* HTML comments, and a Markdown parser treats each as a self-contained
- * single-line HTML block — the fence between them is a sibling node, not a
+ * single-line HTML block. The fence between them is a sibling node, not a
* child, so it renders like any other code block. Without this step both
* samples ship and readers see the same example twice.
*/
@@ -142,7 +142,7 @@ export function stripHiddenBlocks(content: string): StripHiddenBlocksResult {
const nextIsBlank = next === undefined || next.trim() === ''
if (previousIsBlank && nextIsBlank) {
- // Both sides were blank and are now adjacent — keep only one.
+ // Both sides were blank and are now adjacent, so keep only one.
i++
} else if (!previousIsBlank && !nextIsBlank) {
// The range was the only thing separating two blocks. Without a blank