Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions .github/workflows/ai-policy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT

name: AI Policy

on:
pull_request:
types: [opened, synchronize, reopened]
branches: [master, main]

permissions:
contents: read
# Required to add the "AI assisted" label via `gh pr edit --add-label`
pull-requests: write
# Required to create the "AI assisted" label via the REST labels endpoint
# (labels are an issues-scoped resource in the GitHub API)
issues: write

concurrency:
group: ai-policy-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
check-ai-trailers:
runs-on: ubuntu-latest-low
steps:
- name: Collect PR commit messages
id: collect
env:
GH_TOKEN: ${{ github.token }}
COMMITS_URL: ${{ github.event.pull_request.commits_url }}
run: |
set -euo pipefail
gh api ${COMMITS_URL} | jq -r '.[] | .commit.message' > /tmp/pr_commits.txt
echo "--- PR commit messages ---"
cat /tmp/pr_commits.txt
echo "--------------------------"

- name: Define shared agent detection patterns
run: |
set -euo pipefail

# Email addresses known to be used by coding agents.
# These should never appear in Signed-off-by because the DCO can only be attested by a human.
EMAIL_PATTERN="copilot@github\.com\
|noreply@anthropic\.com\
|devin@cognition\.ai\
|devin@cognition-labs\.com\
|aider@aider\.chat\
|noreply@aider\.chat\
|codex@openai\.com\
|cursor@anysphere\.com\
|windsurf@codeium\.com\
|codeium@codeium\.com\
|amazon-q@amazon\.com\
|codewhisperer@amazon\.com\
|gemini-code-assist@google\.com\
|openhands@all-hands\.dev\
|swe-agent@princeton\.edu"

# Strip embedded whitespace (used above only for readability)
EMAIL_PATTERN=$(echo "$EMAIL_PATTERN" | tr -d ' \n')
echo "AGENT_EMAIL_PATTERN=${EMAIL_PATTERN}" >> "$GITHUB_ENV"

# Display-name prefixes used by known coding agents (shared by Signed-off-by and Co-Authored-By checks)
# shellcheck disable=SC2016
echo 'AGENT_NAMES=GitHub Copilot|Claude( [A-Za-z0-9. -]+)?|Devin( AI)?|aider( \(.*\))?|OpenAI Codex|Cursor( AI)?|Windsurf|Amazon Q|CodeWhisperer|Gemini Code Assist|OpenHands|SWE-agent|AutoCodeRover|Tabnine' >> "$GITHUB_ENV"

- name: Check for AI-assistant / Assisted-by trailers
id: ai_trailers
run: |
set -euo pipefail
AI_ASSISTED=false
if grep -qiE '^(AI-assistant|Assisted-by|AI-Assisted-By):' /tmp/pr_commits.txt; then
AI_ASSISTED=true
echo "Found AI-assistant/Assisted-by/AI-Assisted-By trailer(s):"
grep -iE '^(AI-assistant|Assisted-by|AI-Assisted-By):' /tmp/pr_commits.txt
fi
echo "ai_assisted=${AI_ASSISTED}" >> "$GITHUB_OUTPUT"

- name: Check for coding-agent Signed-off-by trailers
id: agent_signoff
run: |
set -euo pipefail

EMAIL_HITS=$(grep -iE "^Signed-off-by:.*<(${AGENT_EMAIL_PATTERN})>" /tmp/pr_commits.txt 2>/dev/null || true)
NAME_HITS=$(grep -iE "^Signed-off-by: *(${AGENT_NAMES}) *[<(]" /tmp/pr_commits.txt 2>/dev/null || true)

AGENT_LINES=$(printf '%s\n%s' "$EMAIL_HITS" "$NAME_HITS" | sort -u | sed '/^[[:space:]]*$/d')

AGENT_SIGNOFF=false
if [ -n "$AGENT_LINES" ]; then
AGENT_SIGNOFF=true
fi

echo "agent_signoff=${AGENT_SIGNOFF}" >> "$GITHUB_OUTPUT"
{
echo "agent_lines<<AGENT_EOF"
echo "${AGENT_LINES}"
echo "AGENT_EOF"
} >> "$GITHUB_OUTPUT"

- name: Check for coding-agent Co-Authored-By trailers
id: co_authored
run: |
set -euo pipefail

EMAIL_HITS=$(grep -iE "^Co-Authored-By:.*<(${AGENT_EMAIL_PATTERN})>" /tmp/pr_commits.txt 2>/dev/null || true)
NAME_HITS=$(grep -iE "^Co-Authored-By: *(${AGENT_NAMES}) *[<(]" /tmp/pr_commits.txt 2>/dev/null || true)

CO_AUTHORED=false
if [ -n "$EMAIL_HITS" ] || [ -n "$NAME_HITS" ]; then
CO_AUTHORED=true
echo "Found coding-agent Co-Authored-By trailer(s):"
printf '%s\n%s' "$EMAIL_HITS" "$NAME_HITS" | sort -u | sed '/^[[:space:]]*$/d'
fi

echo "co_authored=${CO_AUTHORED}" >> "$GITHUB_OUTPUT"

- name: Create 'AI assisted' label if absent
if: steps.ai_trailers.outputs.ai_assisted == 'true' || steps.agent_signoff.outputs.agent_signoff == 'true' || steps.co_authored.outputs.co_authored == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api "repos/${{ github.repository }}/labels" \
--method POST \
-f name="AI assisted" \
-f color="d93f0b" \
-f description="This PR contains AI-assisted commits" \
2>/dev/null || true

- name: Label PR as AI assisted
if: steps.ai_trailers.outputs.ai_assisted == 'true' || steps.agent_signoff.outputs.agent_signoff == 'true' || steps.co_authored.outputs.co_authored == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh pr edit "${{ github.event.pull_request.number }}" \
--repo "${{ github.repository }}" \
--add-label "AI assisted"
echo "Added 'AI assisted' label to PR #${{ github.event.pull_request.number }}"

- name: Fail on coding-agent Signed-off-by
if: steps.agent_signoff.outputs.agent_signoff == 'true'
env:
AGENT_LINES: ${{ steps.agent_signoff.outputs.agent_lines }}
AGENTS_MD_URL: https://github.com/${{ github.repository }}/blob/${{ github.base_ref }}/AGENTS.md
run: |
echo "::error title=Coding-agent sign-off detected::A Signed-off-by trailer from a known coding agent was found in one or more commits."
echo ""
echo "Offending trailer(s):"
echo "${AGENT_LINES}"
echo ""
echo "The 'Signed-off-by' trailer represents the Developer Certificate of Origin (DCO)"
echo "and must only be attested by a human contributor."
echo "Please amend the affected commit(s) to remove the coding-agent sign-off"
echo "and replace it with an 'Assisted-by' trailer, for example:"
echo ""
echo " Assisted-by: Claude Code:claude-sonnet-4-6"
echo ""
echo "References:"
echo " • AGENTS.md (this repository)"
echo " ${AGENTS_MD_URL}"
echo " • AI Contribution Policy"
echo " https://github.com/nextcloud/.github/blob/master/AI_POLICY.md"
echo " • Contribution Guidelines"
echo " https://github.com/nextcloud/.github/blob/master/CONTRIBUTING.md"
exit 1
170 changes: 170 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

# Agent Guidelines for Nextcloud files_mindmap

This file provides instructions for AI coding agents (Claude Code, GitHub Copilot, Cursor, Windsurf, and others) operating on this repository. Read it before generating any code, commits, or pull requests.

---

## Nextcloud Contribution Policy

All contributions generated or assisted by this agent must fully comply with:

- **[AI Contribution Policy](https://github.com/nextcloud/.github/blob/master/AI_POLICY.md)** - the primary reference for AI-specific rules, covering disclosure, author accountability, communication, security, licensing, code quality, and autonomous agent behavior.
- **[Contribution Guidelines](https://github.com/nextcloud/.github/blob/master/CONTRIBUTING.md)** - covering testing requirements, the Developer Certificate of Origin (DCO), license headers, conventional commits, and translations. These apply in full to all contributions regardless of how they were produced.

### What this agent must always do

- Add an `Assisted-by: AGENT_NAME:MODEL_VERSION` git trailer to every commit containing AI-assisted content.
- Ensure every pull request includes a disclosure of AI tool use in the PR description.
- Produce focused, scoped pull requests that address exactly one concern. Do not touch unrelated files or introduce incidental refactors.
- Verify all dependencies against actual package registries before suggesting them. Do not use hallucinated or unverified package names.
- Explicitly inform the contributor when any action they are about to take, or have taken, would violate the AI Contribution Policy or the Contribution Guidelines. Do not silently proceed. State which rule is at risk and what the contributor should do instead.
- Warn the contributor if a pull request is growing too large. A PR approaching several thousand lines of changed code is a signal that it should be split into smaller, focused PRs. Suggest a logical split before the PR is opened, not after.
- Recommend opening a ticket for discussion before starting implementation whenever a feature or change is sufficiently complex - for example when it touches multiple subsystems, requires architectural decisions, or the right approach is not yet clear. A ticket allows maintainers and the contributor to align on direction before code is written, avoiding wasted effort on a PR that may be rejected or require fundamental rework.

### What this agent must never do

- Open issues, submit pull requests, post review comments, or send security reports autonomously. Every contribution must be reviewed and submitted by a human.
- Add `Signed-off-by` tags to commits. Only the human contributor can certify the Developer Certificate of Origin.
- Generate or submit security reports without independent human verification. Report verified vulnerabilities via [HackerOne](https://hackerone.com/nextcloud), not as GitHub issues.
- Write PR descriptions, review comments, or issue reports on behalf of the contributor. These must be in the contributor's own words.
- Fully automate the resolution of issues labeled [`good first issue`](https://github.com/issues?q=org%3Anextcloud+label%3A%22good+first+issue%22) or similar beginner-friendly labels.
- Submit code that has not been reviewed and cleaned up by the contributor. Dead code, redundant logic, excessive comments, and unrelated changes must be removed before submission.

---

## Repository-Specific Requirements

### Commit format

Use [Conventional Commits](https://www.conventionalcommits.org) for all commit messages:

```
<type>(<scope>): <short description>

[optional body]

Assisted-by: AGENT_NAME:MODEL_VERSION
```

Common types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `perf`, `build`, `ci`.
The scope should match the affected component or app (e.g. `files_sharing`, `core`, `encryption`).

Example:
```
feat(files_sharing): allow sharing with contacts

Assisted-by: ClaudeCode:claude-sonnet-4-6
```

### Developer Certificate of Origin (DCO)

The project uses the DCO as an additional safeguard. Only the human contributor may add the `Signed-off-by` trailer - agents must not add it:

```
Signed-off-by: Random J Developer <random@developer.example.org>
```

Contributors can sign automatically with `git commit -s` after configuring `user.name` and `user.email`.

### License headers

Every new file must include the correct SPDX license header. For AGPL-3.0-or-later (the default for this repository):

```php
/**
* SPDX-FileCopyrightText: <year> <name>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
```

See [HowToApplyALicense.md](https://github.com/nextcloud/server/blob/master/contribute/HowToApplyALicense.md) for details on per-language formats. AI-generated code must not include material from sources incompatible with AGPL-3.0-or-later.

### Security

- Do not open GitHub issues for potential vulnerabilities. Report them via [HackerOne](https://hackerone.com/nextcloud) following the [security policy](https://nextcloud.com/security/).
- AI-generated security reports must be independently verified by the human contributor before submission.
- Manually verify all access control logic, authentication patterns, and dependency names - AI tools are known to hallucinate package names and reproduce vulnerable patterns.

## Commands

```bash
# Development
npm run watch # Watch mode (rebuilds on change)
npm run dev # One-time development build

# Production
npm run build # Production build → js/

# Testing
npm run test # Run tests once (Vitest)
npm run test:watch # Watch mode

# Linting
npm run lint # ESLint check

# Packaging
make appstore # Build appstore tarball
make release # Tag + appstore package
```

**Requirements:** Node.js ^24.0.0, npm ^11.3.0

## Architecture

This is a Nextcloud app that allows users to edit mind map files (`.km`, `.xmind`, `.mm`) in the browser.

### Two-Layer Frontend

The frontend has two distinct layers:

1. **Modern Vue 2.7 layer** (`src/mindmap.js`, `src/mindmapviewer.js`) — registers file actions in the Nextcloud Files UI and wraps the viewer using the Viewer API. Entry points are compiled by Vite into `js/`.

2. **Legacy AngularJS + KityMinder layer** (`vendor/kityminder-core/`, `vendor/angular/`) — the actual mind map editor runs inside an `<iframe>` loaded via `DisplayController::showMindmapViewer()`. This layer is served directly from `vendor/` (not Vite-compiled). `src/viewer.js` is the entry point for this iframe.

The Vue component (`src/views/MindMap.js`) renders only the iframe. All real editing happens inside it.

### File Operation Flow

```
File click → Nextcloud Viewer → MindMap.js (Vue) renders iframe
→ iframe loads DisplayController → viewer.php + viewer.js
→ AngularJS/KityMinder editor boots inside iframe

Load: GET /ajax/loadfile → FileHandlingController::load() → base64-encoded content
Save: PUT /ajax/savefile → FileHandlingController::save() → writes to user storage
```

Public share routes mirror authenticated routes under `/public/{token}` and `/share/save`.

### Plugin System for File Formats

`src/plugins/` contains format handlers (`km.js`, `xmind.js`, `freemind.js`). Each plugin handles encoding/decoding for its format. Only `.km` supports both read and write; `.xmind` and `.mm` are read-only imports.

### PHP Backend

`lib/AppInfo/Application.php` registers event listeners for:
- Loading JS into Files (`LoadAdditionalListener`)
- Loading JS into the Viewer app (`LoadViewerListener`, `LoadPublicViewerListener`)
- Registering the "New mind map" template creator (`RegisterTemplateCreatorListener`)

Routes are defined in `appinfo/routes.php` and handled by controllers in `lib/Controller/`.

### Vue Compatibility Note

`src/views/MindMap.js` uses the Options API (not SFC `.vue` files) to maintain Vue 2.7 compatibility with the Nextcloud Viewer app mixin system. This is intentional — do not refactor to SFC.

## Tests

Tests live in `src/__tests__/` and use Vitest with jsdom. The test suite covers format plugins and core utilities — the AngularJS viewer layer is not covered by automated tests.

## Further Reading

- [Nextcloud Contribution Guidelines](https://github.com/nextcloud/.github/blob/master/CONTRIBUTING.md)
- [AI Contribution Policy](https://github.com/nextcloud/.github/blob/master/AI_POLICY.md)
- [How to Apply a License](https://github.com/nextcloud/server/blob/master/contribute/HowToApplyALicense.md)
- [Security Vulnerability Reporting (HackerOne)](https://hackerone.com/nextcloud)
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
6 changes: 6 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ SPDX-PackageName = "files_mindmap"
SPDX-PackageSupplier = "Nextcloud <info@nextcloud.com>"
SPDX-PackageDownloadLocation = "https://github.com/nextcloud/files_mindmap"

[[annotations]]
path = ["CLAUDE.md"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2026 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "AGPL-3.0-or-later"

[[annotations]]
path = ["l10n/**.js", "l10n/**.json"]
precedence = "aggregate"
Expand Down
Loading