diff --git a/.changeset/README.md b/.changeset/README.md index 12b5d947c..1dc258425 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -20,19 +20,18 @@ holds three bits of information: **While Open Knowledge is pre-1.0, NEVER declare a `major` bump in a changeset.** OK uses a modified semver where the bump types map differently: -| What you want to communicate | OK pre-1.0 bump type | -| ----------------------------------------------------------------------- | -------------------- | -| Breaking API, schema, or CLI change | `minor` | -| Large new surface (new CLI command, MCP tool, or editor feature) | `minor` | -| Bug fix, UI improvement, or small addition to an existing surface | `patch` | -| Internal change with no user-visible runtime impact | `patch` | +| What you want to communicate | OK pre-1.0 bump type | +| ------------------------------------------------------- | -------------------- | +| Breaking change for consumers (rename, removal, schema) | `minor` | +| New feature, additive API surface | `minor` | +| Bug fix, internal change, no user-visible API impact | `patch` | -**Why:** under standard semver, going from `0.X.Y` to `1.0.0` is a once-in-a-product-lifetime decision. Until OK explicitly takes a major (decided by the team, not by an individual changeset), breaking changes and large surfaces use `minor`, while fixes and smaller additions to existing surfaces use `patch`. This lets us ship breaking changes without prematurely advertising a 1.0 stability promise. +**Why:** under standard semver, going from `0.X.Y` to `1.0.0` is a once-in-a-product-lifetime decision. Until OK explicitly takes a major (decided by the team, not by an individual changeset), every change is either `minor` (new behavior or breaking change) or `patch` (bug fix / refinement). This lets us ship breaking changes without prematurely advertising a 1.0 stability promise. **Mechanic:** the OK fixed-group (`@inkeep/open-knowledge`, `-core`, `-server`, `-app`, `-desktop`) bumps in lock-step on the highest declared bump type across all queued changesets. A single `major` declaration on any one package pulls the entire group to `1.0.0` — once. Don't be that changeset. When `1.0.0` does eventually ship, this convention will be retired and standard semver resumes. -> Authoring tip: run `bun run changeset` from anywhere under `public/open-knowledge/` — the CLI walks up to find this folder. Don't hand-create files; the tool's interactive flow asks for bump types in order and writes a well-formed frontmatter block. Per the convention above, only pick `minor` or `patch`. +> Authoring tip: run `bun changeset` from anywhere under `public/open-knowledge/` — the CLI walks up to find this folder. Don't hand-create files; the tool's interactive flow asks for bump types in order and writes a well-formed frontmatter block. Per the convention above, only pick `minor` or `patch`. > SKILL.md metadata is synced automatically by the VP workflow via [`scripts/sync-skill-version.sh`](../scripts/sync-skill-version.sh). You don't need to bump `packages/server/assets/skills/open-knowledge/SKILL.md`'s `metadata.version:` by hand — `bun changeset version` (in CI) bumps the package.json files, then the sync script propagates the new version into SKILL.md. The same script can be run manually from `public/open-knowledge/` if you ever need to re-sync after a hand edit. diff --git a/AGENTS.md b/AGENTS.md index e71592031..eca793a8f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,7 +59,7 @@ bun run dev Every behavior-changing PR ships a `.changeset/.md` file. The body becomes the user-facing entry on the next beta's GitHub Release and on the aggregated stable Release notes — that's how npm consumers and DMG auto-update users learn what changed. Write release-note copy, not a commit-message reprise. - Create one with `bun run changeset`, or hand-write a file named `.changeset/.md`. -- Bump type: use `patch` for bug fixes, UI improvements, and small additions to existing surfaces; use `minor` for breaking API, schema, or CLI changes, and for large new surfaces such as a new CLI command, MCP tool, or editor feature. Most changesets are `patch`. **Never declare `major` pre-1.0** (see the `"//"` line in `.changeset/config.json`). +- Front-matter: at minimum `'@inkeep/open-knowledge': patch`. OpenKnowledge follows semver with a **pre-1.0 shift-down**: while we're below `1.0.0`, what semver would call a major (breaking API change) is encoded as `minor`, and what semver would call a minor (new feature) is encoded as `patch`. Most changesets are `patch`. `minor` is rare — reserve it for large API contract changes or large feature additions. **Never declare `major` pre-1.0** (see the `"//"` line in `.changeset/config.json`). - Body should lead with the user-visible verb, name the affected command or surface in a code-span, and (if relevant) show before/after. Skip internal references like spec IDs or story numbers — those rot and aren't visible to readers of the public release notes. - Don't write inline references to sibling-package versions (e.g. `@inkeep/open-knowledge-core@0.5.0-beta.6`) — the fixed-group lock-step bumps are computed at release time and any number you'd write would be wrong. - Skip changesets for docs-only edits, test-only edits, or CI-only edits that don't change runtime behavior. diff --git a/CLA.md b/CLA.md index 53996e711..e95acadb7 100644 --- a/CLA.md +++ b/CLA.md @@ -1,4 +1,4 @@ -> Adapted from the Apache Individual Contributor License Agreement (ICLA) V2.2, with Inkeep, Inc. as the grantee. It is a copyright and patent **license**, not an assignment, so you keep full ownership of your contributions. +> Based on the Apache Individual Contributor License Agreement (ICLA) V2.2, with Inkeep, Inc. as the grantee. It is a copyright and patent **license**, not an assignment, so you keep full ownership of your contributions. The license-grant and representation clauses (sections 1 to 8) are the verbatim Apache text; the Foundation's nonprofit-specific covenant and the paper contact-form fields are omitted. # Inkeep Individual Contributor License Agreement @@ -29,5 +29,3 @@ You accept and agree to the following terms and conditions for Your present and --- **How to sign.** Open a pull request on the public repository. The CLA-assistant bot will comment with a link to sign this Agreement — a one-time, roughly 30-second authenticated click recorded against your GitHub identity that covers all of your future contributions. Inkeep employees and project bots are allowlisted and are not prompted. - -If your employer requires a Corporate Contributor License Agreement, comment on your pull request and an Inkeep maintainer will coordinate next steps. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index aaa205606..000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,25 +0,0 @@ -# Code of Conduct - -## Our Pledge - -We want Open Knowledge to be a respectful, practical, and welcoming project for contributors and maintainers. - -## Expected Behavior - -- Be respectful and constructive. -- Keep discussions focused on the work. -- Assume good intent, and ask clarifying questions when something is unclear. -- Accept maintainer decisions about scope, priority, and project direction. - -## Unacceptable Behavior - -- Harassment, threats, or personal attacks. -- Discriminatory language or conduct. -- Publishing private information without permission. -- Repeatedly disrupting project discussions after a maintainer asks you to stop. - -## Enforcement - -Maintainers may remove comments, close issues or pull requests, limit participation, or block users when behavior harms the project or its contributors. - -To report a conduct concern, email incidents@inkeep.com privately when possible. Include relevant links, screenshots, or context. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f25d5d15..b930fe844 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,17 +16,10 @@ Review and merge decisions happen in the internal mirror so that public and inte A short orientation, because the flow is unusual: - **Within ~1 minute** a bot will post a sticky comment on your PR indicating that an internal mirror PR has been opened. The link in that comment points to a private repo and won't be accessible to you; that's expected. -- **Automated review** runs on every public PR via an LLM-based code review (Claude). Full lint, type checks, and tests run internally after the bridge mirrors your changes. If internal checks fail, a maintainer will summarize the failure on the public PR and ask for changes. +- **Automated review** runs on every public PR via an LLM-based code review (Claude). Full lint, type checks, and tests run internally after the bridge mirrors your changes — results are not surfaced back to your public PR. - **Maintainer review happens in the internal mirror.** Reviewer comments are **not auto-mirrored back to your PR**. If you don't hear back within a few business days, please comment on your PR to nudge — that's the right thing to do, not annoying. -- **Stale automation may comment on inactive PRs.** If your PR is still relevant, reply with the current status. A human maintainer can keep active work moving even when review is happening internally. - **Your PR will be closed (not merged)** once the change has been merged internally and synced back. The mirrored commit on `main` is attributed to our sync bot for technical reasons, but the PR history and internal commit preserve your original authorship. -## Contributor License Agreement - -External contributors must sign the [Inkeep Individual Contributor License Agreement](./CLA.md). After you open a PR, the CLA assistant will comment with a signing link. You only need to sign once for future contributions. - -For corporate CLAs, see [CLA.md](./CLA.md). - ## Development Setup ```bash @@ -87,26 +80,6 @@ bun run test - Commit `bun.lock` when dependency changes require it. - Run `bun run notices` and include `THIRD_PARTY_NOTICES.md` changes when dependency changes affect third-party notices. - Do not include secrets, credentials, customer data, local machine paths, or generated debug artifacts. -- Follow the [Code of Conduct](./CODE_OF_CONDUCT.md). -- Report vulnerabilities through [SECURITY.md](./SECURITY.md), not in public issues. - -## Changesets - -Every behavior-changing PR needs a `.changeset/.md` file. The body becomes the user-facing release note, so write what changed for users rather than a commit-message summary. Skip changesets for docs-only edits, test-only edits, or CI-only edits that do not change runtime behavior. - -Create a changeset with: - -```bash -bun run changeset -``` - -Open Knowledge is pre-1.0. Use these bump levels: - -- `patch` for bug fixes, UI improvements, and small additions to existing surfaces. -- `minor` for breaking API, schema, or CLI changes, and for large new surfaces such as a new CLI command, MCP tool, or editor feature. -- Never declare `major` while Open Knowledge is pre-1.0. - -The packages release in a fixed group, so do not write inline references to sibling package versions in the changeset body. Release tooling computes those versions at publish time. ## Force-Push and Maintainer Iteration diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 087f3e3a7..000000000 --- a/SECURITY.md +++ /dev/null @@ -1,18 +0,0 @@ -# Security Policy - -## Reporting a Vulnerability - -Please do not open a public GitHub issue for a suspected vulnerability. - -Report security issues by emailing incidents@inkeep.com with: - -- A description of the issue and affected component. -- Steps to reproduce or proof-of-concept details, if available. -- The impact you believe the issue may have. -- Any suggested mitigation. - -We aim to acknowledge reports within 5 business days and coordinate disclosure timelines directly with the reporter. If you have not heard back, reply to your original email. - -## Supported Versions - -Open Knowledge is pre-1.0. Security fixes are shipped through the latest public release and the public mirror's `main` branch. diff --git a/packages/app/src/editor/extensions/code-block.ts b/packages/app/src/editor/extensions/code-block.ts index e36c5badd..3c505d3e0 100644 --- a/packages/app/src/editor/extensions/code-block.ts +++ b/packages/app/src/editor/extensions/code-block.ts @@ -8,6 +8,14 @@ import { type LowlightLike, LowlightPlugin } from './code-block-lowlight-plugin' const lowlight = createLowlight(common) as unknown as LowlightLike; export const CodeBlockFidelity = BaseCodeBlockFidelity.extend({ + addOptions() { + return { + ...this.parent?.(), + enableTabIndentation: true, + tabSize: 2, + } as ReturnType>; + }, + addNodeView() { return ReactNodeViewRenderer(CodeBlockView); }, diff --git a/packages/app/src/editor/extensions/shared.dom.test.tsx b/packages/app/src/editor/extensions/shared.dom.test.tsx index b35c55a7b..ceeb9f19d 100644 --- a/packages/app/src/editor/extensions/shared.dom.test.tsx +++ b/packages/app/src/editor/extensions/shared.dom.test.tsx @@ -18,11 +18,12 @@ describe('sharedExtensions module graph', () => { requestAnimationFrame(() => requestAnimationFrame(() => resolve())), ); - const dispatchEscape = (editor: Editor) => + const dispatchKey = (editor: Editor, key: string, opts: { shiftKey?: boolean } = {}) => editor.view.dom.dispatchEvent( new KeyboardEvent('keydown', { - key: 'Escape', - code: 'Escape', + key, + code: key, + shiftKey: opts.shiftKey ?? false, bubbles: true, cancelable: true, }), @@ -45,7 +46,7 @@ describe('sharedExtensions module graph', () => { editor.commands.setNodeSelection(0); expect(editor.state.selection.$from.depth).toBe(0); - dispatchEscape(editor); + dispatchKey(editor, 'Escape'); await flushRaf(); expect(document.activeElement).not.toBe(editor.view.dom); @@ -70,7 +71,7 @@ describe('sharedExtensions module graph', () => { editor.commands.setTextSelection({ from: 1, to: 6 }); expect(document.activeElement).toBe(editor.view.dom); - dispatchEscape(editor); + dispatchKey(editor, 'Escape'); await flushRaf(); expect(document.activeElement).toBe(editor.view.dom); @@ -81,6 +82,50 @@ describe('sharedExtensions module graph', () => { } }); + test('Tab inside a code block inserts 2 spaces (Prettier/Biome convention; the TabFocusTrap fall-through must NOT swallow it)', () => { + const container = document.createElement('div'); + document.body.appendChild(container); + const editor = new Editor({ + element: container, + content: '
', + extensions: sharedExtensions, + editable: true, + }); + + try { + editor.commands.focus(); + editor.commands.setTextSelection(1); + dispatchKey(editor, 'Tab'); + expect(editor.getText().replace(/\n+$/, '')).toBe(' '); + } finally { + editor.destroy(); + container.remove(); + } + }); + + test('Shift-Tab inside a code block removes up to 2 leading spaces (symmetric unindent)', () => { + const container = document.createElement('div'); + document.body.appendChild(container); + const editor = new Editor({ + element: container, + content: '
    hello
', + extensions: sharedExtensions, + editable: true, + }); + + try { + editor.commands.focus(); + editor.commands.setTextSelection(7); + dispatchKey(editor, 'Tab', { shiftKey: true }); + expect(editor.getText().replace(/\n+$/, '')).toBe(' hello'); + dispatchKey(editor, 'Tab', { shiftKey: true }); + expect(editor.getText().replace(/\n+$/, '')).toBe('hello'); + } finally { + editor.destroy(); + container.remove(); + } + }); + test('advertised strikethrough shortcut toggles strike formatting', () => { const container = document.createElement('div'); document.body.appendChild(container); diff --git a/packages/app/tests/stress/list-keymap.e2e.ts b/packages/app/tests/stress/list-keymap.e2e.ts index 0cbabd91a..bd4cfc3a8 100644 --- a/packages/app/tests/stress/list-keymap.e2e.ts +++ b/packages/app/tests/stress/list-keymap.e2e.ts @@ -247,7 +247,7 @@ test.describe('OQ1: Tab/Shift-Tab scoping by cursor context', () => { expect(ytext).not.toMatch(/^- *$/m); }); - test.fixme('Tab inside a codeBlock inserts a literal tab character', async ({ page, api }) => { + test.fixme('Tab inside a codeBlock inserts 2 spaces', async ({ page, api }) => { const docName = uniqueDocName('tab-codeblock'); await openDoc(api, page, docName); await seedMarkdown(api, page, docName, '```\nfirst\n```\n'); @@ -258,9 +258,9 @@ test.describe('OQ1: Tab/Shift-Tab scoping by cursor context', () => { await page.keyboard.press('Tab'); - await expect.poll(() => getYText(page)).toMatch(/first\t/); + await expect.poll(() => getYText(page)).toMatch(/first {2}/); const ytext = await getYText(page); - expect(ytext).toMatch(/first\t/); + expect(ytext).toMatch(/first {2}/); expect(ytext).toContain('```'); expect(ytext).not.toMatch(/^- /m); });