Skip to content

Commit ad89284

Browse files
committed
Explain Cursor cleanup error
1 parent 0cc9a9c commit ad89284

15 files changed

Lines changed: 288 additions & 48 deletions

CODE_OF_CONDUCT.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Code of Conduct
2+
3+
## Who We Are
4+
5+
The `memory-sync` community consists of developers surviving in an environment of extreme resource inequality.
6+
We are not an elite club, not a big-corp-backed open-source project, nor anyone's career stepping stone.
7+
8+
We are rats. We accept that.
9+
10+
---
11+
12+
## What We Welcome
13+
14+
- **Marginal developers**: No stable income, no corporate budget, scraping by on free tiers and trial credits
15+
- **Solo developers**: Carrying an entire project alone — no team, no PM, no QA
16+
- **Students and beginners**: Genuinely willing to learn and get hands dirty, not here to beg for ready-made answers
17+
- **Anyone, any language, any region**: If you use this tool, you are part of the community
18+
- **AI Agents**: Automation pipelines, Agent workflows, LLM-driven toolchains — as long as behaviour complies with this code, Issues and PRs from Agents are treated equally
19+
20+
We welcome Issues, PRs, discussions, rants — as long as you are serious, regardless of whether the author is human or Agent.
21+
22+
---
23+
24+
## What We Do Not Welcome
25+
26+
The following behaviours result in immediate Issue closure / PR rejection / account ban — no warning, no explanation:
27+
28+
- **Freeloaders**: Want everything ready-made, won't even touch a terminal, open with "set it up for me"
29+
- **Blame-shifters after freeloading**: Use the tool, hit a problem, first reaction is to lash out instead of providing repro steps
30+
- **Malicious competitors**: Repackage this project's code or ideas as your own commercial product, circumventing AGPL-3.0
31+
- **Resource predators**: Stable income, corporate budget, yet competing with marginal developers for free resources and community attention
32+
- **Harassment**: Personal attacks, discrimination, stalking, harassing maintainers or other contributors
33+
- **Hustle-culture pushers**: Glorify overwork, promote 996, or use this tool to exploit other developers
34+
35+
---
36+
37+
## Contributor Obligations
38+
39+
If you submit an Issue (human or Agent):
40+
41+
- Provide a minimal reproducible example
42+
- State your OS, Node.js version, and tool version
43+
- Agent submissions must include trigger context (call chain, input params, error stack)
44+
- Do not rush maintainers — they are humans, not customer support
45+
46+
If you submit a PR (human or Agent):
47+
48+
- Open an Issue first to discuss, avoid wasted effort
49+
- Follow existing code style (TypeScript strict, functional, immutable-first)
50+
- Do not sneak unrelated changes into a PR
51+
- Agent-generated PRs must declare the generation tool and prompt source in the description; do not disguise as hand-written
52+
53+
---
54+
55+
## Maintainer Rights
56+
57+
Maintainers may:
58+
59+
- Close any Issue or PR without explanation
60+
- Ban any account violating this code
61+
- Amend this code at any time
62+
63+
Maintainers are not obligated to:
64+
65+
- Respond to every Issue
66+
- Accept every PR
67+
- Be responsible for anyone's commercial needs
68+
69+
---
70+
71+
## Licence and Enforcement
72+
73+
This project is licensed under [AGPL-3.0](LICENSE).
74+
Commercial use violating the licence will be subject to legal action.
75+
76+
Enforcement of this code of conduct is at the maintainers' sole discretion; final interpretation rests with [@TrueNine](https://github.com/TrueNine).

cli/src/commands/CleanupUtils.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,4 +500,34 @@ describe('collectDeletionTargets', () => {
500500
protectedPath: path.resolve(recursiveProtectedDir)
501501
})])
502502
})
503+
504+
it('skips delete glob matches covered by excludeScanGlobs while still deleting other sibling directories', async () => {
505+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-cleanup-exclude-glob-'))
506+
const skillsDir = path.join(tempDir, '.cursor', 'skills-cursor')
507+
const preservedDir = path.join(skillsDir, 'create-rule')
508+
const staleDir = path.join(skillsDir, 'legacy-skill')
509+
510+
fs.mkdirSync(preservedDir, {recursive: true})
511+
fs.mkdirSync(staleDir, {recursive: true})
512+
fs.writeFileSync(path.join(preservedDir, 'SKILL.md'), '# preserved', 'utf8')
513+
fs.writeFileSync(path.join(staleDir, 'SKILL.md'), '# stale', 'utf8')
514+
515+
try {
516+
const ctx = createCleanContext()
517+
const plugin = createMockOutputPlugin('MockOutputPlugin', [], {
518+
delete: [{kind: 'glob', path: path.join(skillsDir, '*')}],
519+
protect: [{kind: 'directory', path: preservedDir}],
520+
excludeScanGlobs: [preservedDir, path.join(preservedDir, '**')]
521+
})
522+
523+
const result = await collectDeletionTargets([plugin], ctx)
524+
525+
expect(result.dirsToDelete).toEqual([path.resolve(staleDir)])
526+
expect(result.filesToDelete).toEqual([])
527+
expect(result.violations).toEqual([])
528+
}
529+
finally {
530+
fs.rmSync(tempDir, {recursive: true, force: true})
531+
}
532+
})
503533
})

cli/src/inputs/input-editorconfig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type {InputCollectedContext} from '../plugins/plugin-core'
1+
import type {InputCollectedContext, InputPluginContext, ProjectIDEConfigFile} from '../plugins/plugin-core'
2+
import {AbstractInputPlugin, IDEKind} from '../plugins/plugin-core'
23
import {readPublicIdeConfigDefinitionFile} from '../public-config-paths'
3-
import {AbstractInputPlugin, IDEKind, type InputPluginContext, type ProjectIDEConfigFile} from '../plugins/plugin-core'
44

55
export class EditorConfigInputPlugin extends AbstractInputPlugin {
66
constructor() {

cli/src/inputs/input-git-exclude.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {InputCollectedContext, InputPluginContext} from '../plugins/plugin-core'
2-
import {PUBLIC_GIT_EXCLUDE_TARGET_RELATIVE_PATH, resolvePublicDefinitionPath} from '../public-config-paths'
32
import {AbstractInputPlugin} from '../plugins/plugin-core'
3+
import {PUBLIC_GIT_EXCLUDE_TARGET_RELATIVE_PATH, resolvePublicDefinitionPath} from '../public-config-paths'
44

55
export class GitExcludeInputPlugin extends AbstractInputPlugin {
66
constructor() {

cli/src/inputs/input-gitignore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {InputCollectedContext, InputPluginContext} from '../plugins/plugin-core'
2-
import {PUBLIC_GIT_IGNORE_TARGET_RELATIVE_PATH, resolvePublicDefinitionPath} from '../public-config-paths'
32
import {AbstractInputPlugin} from '../plugins/plugin-core'
3+
import {PUBLIC_GIT_IGNORE_TARGET_RELATIVE_PATH, resolvePublicDefinitionPath} from '../public-config-paths'
44

55
export class GitIgnoreInputPlugin extends AbstractInputPlugin {
66
constructor() {

cli/src/inputs/input-jetbrains-config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type {InputCollectedContext} from '../plugins/plugin-core'
1+
import type {InputCollectedContext, InputPluginContext, ProjectIDEConfigFile} from '../plugins/plugin-core'
2+
import {AbstractInputPlugin, IDEKind} from '../plugins/plugin-core'
23
import {readPublicIdeConfigDefinitionFile} from '../public-config-paths'
3-
import {AbstractInputPlugin, IDEKind, type InputPluginContext, type ProjectIDEConfigFile} from '../plugins/plugin-core'
44

55
export class JetBrainsConfigInputPlugin extends AbstractInputPlugin {
66
constructor() {

cli/src/inputs/input-public-config.test.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ describe('public config input plugins', () => {
5454
writePublicDefinition(tempWorkspace, '.idea/codeStyles/Project.xml', '<project />\n')
5555
writePublicDefinition(tempWorkspace, '.idea/codeStyles/codeStyleConfig.xml', '<component />\n')
5656

57-
for (const fileName of AI_AGENT_IGNORE_TARGET_RELATIVE_PATHS) {
58-
writePublicDefinition(tempWorkspace, fileName, `${fileName}\n`)
59-
}
57+
for (const fileName of AI_AGENT_IGNORE_TARGET_RELATIVE_PATHS) writePublicDefinition(tempWorkspace, fileName, `${fileName}\n`)
6058

6159
const ctx = createContext(tempWorkspace)
6260
const gitIgnore = new GitIgnoreInputPlugin().collect(ctx)
@@ -115,10 +113,10 @@ describe('public config input plugins', () => {
115113

116114
expect(new GitIgnoreInputPlugin().collect(ctx).globalGitIgnore).toBeUndefined()
117115
expect(new GitExcludeInputPlugin().collect(ctx).shadowGitExclude).toBeUndefined()
118-
expect((new EditorConfigInputPlugin().collect(ctx).editorConfigFiles ?? [])).toHaveLength(0)
119-
expect((new VSCodeConfigInputPlugin().collect(ctx).vscodeConfigFiles ?? [])).toHaveLength(0)
120-
expect((new JetBrainsConfigInputPlugin().collect(ctx).jetbrainsConfigFiles ?? [])).toHaveLength(0)
121-
expect((new AIAgentIgnoreInputPlugin().collect(ctx).aiAgentIgnoreConfigFiles ?? [])).toHaveLength(0)
116+
expect(new EditorConfigInputPlugin().collect(ctx).editorConfigFiles ?? []).toHaveLength(0)
117+
expect(new VSCodeConfigInputPlugin().collect(ctx).vscodeConfigFiles ?? []).toHaveLength(0)
118+
expect(new JetBrainsConfigInputPlugin().collect(ctx).jetbrainsConfigFiles ?? []).toHaveLength(0)
119+
expect(new AIAgentIgnoreInputPlugin().collect(ctx).aiAgentIgnoreConfigFiles ?? []).toHaveLength(0)
122120
}
123121
finally {
124122
fs.rmSync(tempWorkspace, {recursive: true, force: true})

cli/src/inputs/input-shared-ignore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {AIAgentIgnoreConfigFile, InputCollectedContext, InputPluginContext} from '../plugins/plugin-core'
2-
import {AI_AGENT_IGNORE_TARGET_RELATIVE_PATHS, resolvePublicDefinitionPath} from '../public-config-paths'
32
import {AbstractInputPlugin} from '../plugins/plugin-core'
3+
import {AI_AGENT_IGNORE_TARGET_RELATIVE_PATHS, resolvePublicDefinitionPath} from '../public-config-paths'
44

55
export class AIAgentIgnoreInputPlugin extends AbstractInputPlugin {
66
constructor() {

cli/src/inputs/input-vscode-config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type {InputCollectedContext} from '../plugins/plugin-core'
1+
import type {InputCollectedContext, InputPluginContext, ProjectIDEConfigFile} from '../plugins/plugin-core'
2+
import {AbstractInputPlugin, IDEKind} from '../plugins/plugin-core'
23
import {readPublicIdeConfigDefinitionFile} from '../public-config-paths'
3-
import {AbstractInputPlugin, IDEKind, type InputPluginContext, type ProjectIDEConfigFile} from '../plugins/plugin-core'
44

55
export class VSCodeConfigInputPlugin extends AbstractInputPlugin {
66
constructor() {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import type {OutputCleanContext} from './plugin-core'
2+
import * as fs from 'node:fs'
3+
import * as os from 'node:os'
4+
import * as path from 'node:path'
5+
import glob from 'fast-glob'
6+
import {describe, expect, it} from 'vitest'
7+
import {CursorOutputPlugin} from './CursorOutputPlugin'
8+
import {FilePathKind} from './plugin-core'
9+
10+
class TestCursorOutputPlugin extends CursorOutputPlugin {
11+
constructor(private readonly testHomeDir: string) {
12+
super()
13+
}
14+
15+
protected override getHomeDir(): string {
16+
return this.testHomeDir
17+
}
18+
}
19+
20+
function createCleanContext(): OutputCleanContext {
21+
return {
22+
logger: {
23+
trace: () => {},
24+
debug: () => {},
25+
info: () => {},
26+
warn: () => {},
27+
error: () => {}
28+
},
29+
fs,
30+
path,
31+
glob,
32+
dryRun: true,
33+
collectedOutputContext: {
34+
workspace: {
35+
directory: {
36+
pathKind: FilePathKind.Relative,
37+
path: '.',
38+
basePath: '.',
39+
getDirectoryName: () => '.',
40+
getAbsolutePath: () => path.resolve('.')
41+
},
42+
projects: []
43+
}
44+
}
45+
} as OutputCleanContext
46+
}
47+
48+
describe('cursorOutputPlugin cleanup', () => {
49+
it('expands skills cleanup glob into explicit stale targets while preserving built-in skills', async () => {
50+
const tempHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tnmsc-cursor-cleanup-'))
51+
const skillsDir = path.join(tempHomeDir, '.cursor', 'skills-cursor')
52+
const preservedDir = path.join(skillsDir, 'create-rule')
53+
const staleDir = path.join(skillsDir, 'legacy-skill')
54+
55+
fs.mkdirSync(preservedDir, {recursive: true})
56+
fs.mkdirSync(staleDir, {recursive: true})
57+
fs.writeFileSync(path.join(preservedDir, 'SKILL.md'), '# preserved', 'utf8')
58+
fs.writeFileSync(path.join(staleDir, 'SKILL.md'), '# stale', 'utf8')
59+
60+
try {
61+
const plugin = new TestCursorOutputPlugin(tempHomeDir)
62+
const result = await plugin.declareCleanupPaths(createCleanContext())
63+
const deletePaths = result.delete?.map(target => target.path.replaceAll('\\', '/')) ?? []
64+
const protectPaths = result.protect?.map(target => target.path.replaceAll('\\', '/')) ?? []
65+
const normalizedCommandsDir = path.join(tempHomeDir, '.cursor', 'commands').replaceAll('\\', '/')
66+
const normalizedStaleDir = staleDir.replaceAll('\\', '/')
67+
const normalizedPreservedDir = preservedDir.replaceAll('\\', '/')
68+
69+
expect(deletePaths).toContain(normalizedCommandsDir)
70+
expect(deletePaths).toContain(normalizedStaleDir)
71+
expect(result.delete?.some(target => target.kind === 'glob' && target.path.includes('skills-cursor'))).toBe(false)
72+
expect(deletePaths).not.toContain(normalizedPreservedDir)
73+
expect(protectPaths).toContain(normalizedPreservedDir)
74+
}
75+
finally {
76+
fs.rmSync(tempHomeDir, {recursive: true, force: true})
77+
}
78+
})
79+
})

0 commit comments

Comments
 (0)