memorix 1.1.7 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +6 -1
- package/README.md +21 -0
- package/README.zh-CN.md +21 -0
- package/TEAM.md +86 -86
- package/dist/cli/index.js +730 -150
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/index.html +201 -201
- package/dist/dashboard/static/style.css +3584 -3584
- package/dist/index.js +15 -8
- package/dist/index.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +13 -0
- package/dist/memcode-runtime/package.json +4 -4
- package/dist/sdk.js +15 -8
- package/dist/sdk.js.map +1 -1
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +18 -0
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/SETUP.md +10 -0
- package/docs/dev-log/progress.txt +35 -26
- package/package.json +1 -1
- package/src/audit/index.ts +156 -156
- package/src/cli/commands/agent-integrations.ts +623 -0
- package/src/cli/commands/audit-list.ts +89 -89
- package/src/cli/commands/background.ts +659 -659
- package/src/cli/commands/cleanup.ts +255 -255
- package/src/cli/commands/doctor.ts +23 -0
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +66 -66
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/ingest-commit.ts +153 -153
- package/src/cli/commands/ingest-image.ts +73 -73
- package/src/cli/commands/ingest-log.ts +180 -180
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/lock.ts +96 -96
- package/src/cli/commands/message.ts +121 -121
- package/src/cli/commands/poll.ts +70 -70
- package/src/cli/commands/purge-all-memory.ts +85 -85
- package/src/cli/commands/purge-project-memory.ts +83 -83
- package/src/cli/commands/reasoning.ts +132 -132
- package/src/cli/commands/repair.ts +60 -0
- package/src/cli/commands/retention.ts +108 -108
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/setup.ts +3 -3
- package/src/cli/commands/skills.ts +123 -123
- package/src/cli/commands/task.ts +192 -192
- package/src/cli/commands/transfer.ts +73 -73
- package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
- package/src/cli/index.ts +3 -1
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/Panels.tsx +632 -632
- package/src/cli/tui/Sidebar.tsx +179 -179
- package/src/cli/tui/chat-service.ts +742 -742
- package/src/cli/tui/data.ts +547 -547
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/tui/markdown-render.tsx +371 -371
- package/src/cli/tui/theme.ts +178 -178
- package/src/cli/tui/use-mouse.ts +157 -157
- package/src/cli/tui/useNavigation.ts +56 -56
- package/src/cli/update-checker.ts +211 -211
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/codegraph/context-pack.ts +5 -1
- package/src/codegraph/lite-provider.ts +5 -1
- package/src/codegraph/project-context.ts +5 -1
- package/src/compact/token-budget.ts +74 -74
- package/src/config/behavior.ts +59 -59
- package/src/dashboard/project-classification.ts +64 -64
- package/src/dashboard/static/index.html +201 -201
- package/src/dashboard/static/style.css +3584 -3584
- package/src/embedding/fastembed-provider.ts +142 -142
- package/src/embedding/transformers-provider.ts +111 -111
- package/src/git/extractor.ts +209 -209
- package/src/git/hooks-path.ts +85 -85
- package/src/git/noise-filter.ts +210 -210
- package/src/hooks/installers/index.ts +4 -4
- package/src/hooks/official-skills.ts +1 -1
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/rules/memorix-agent-rules.md +2 -2
- package/src/hooks/significance-filter.ts +250 -250
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/provider.ts +885 -885
- package/src/llm/quality.ts +248 -248
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/auto-relations.ts +107 -107
- package/src/memory/consolidation.ts +302 -302
- package/src/memory/disclosure-policy.ts +141 -141
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/formation/extract.ts +361 -361
- package/src/memory/formation/index.ts +417 -417
- package/src/memory/formation/resolve.ts +344 -344
- package/src/memory/formation/types.ts +315 -315
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph.ts +197 -197
- package/src/memory/refs.ts +94 -94
- package/src/memory/retention.ts +433 -433
- package/src/memory/secret-filter.ts +79 -79
- package/src/memory/session.ts +523 -523
- package/src/multimodal/image-loader.ts +143 -143
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/spawn-helper.ts +286 -286
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/context-compact.ts +188 -188
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +380 -380
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/orchestrate/verify-gate.ts +219 -219
- package/src/orchestrate/worktree.ts +232 -232
- package/src/project/aliases.ts +374 -374
- package/src/project/detector.ts +268 -268
- package/src/rules/adapters/claude-code.ts +99 -99
- package/src/rules/adapters/codex.ts +97 -97
- package/src/rules/adapters/copilot.ts +124 -124
- package/src/rules/adapters/cursor.ts +114 -114
- package/src/rules/adapters/kiro.ts +126 -126
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/adapters/windsurf.ts +83 -83
- package/src/rules/syncer.ts +235 -235
- package/src/sdk.ts +327 -327
- package/src/search/intent-detector.ts +289 -289
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/file-lock.ts +100 -100
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/obs-store.ts +255 -255
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/store/session-store.ts +259 -259
- package/src/store/sqlite-store.ts +339 -339
- package/src/team/event-bus.ts +76 -76
- package/src/team/file-locks.ts +173 -173
- package/src/team/handoff.ts +167 -167
- package/src/team/messages.ts +203 -203
- package/src/team/poll.ts +132 -132
- package/src/team/tasks.ts +211 -211
- package/src/wiki/generator.ts +237 -237
- package/src/wiki/knowledge-graph.ts +334 -334
- package/src/wiki/types.ts +85 -85
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
- package/src/workspace/workflow-sync.ts +131 -131
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Code Rule Format Adapter
|
|
3
|
-
*
|
|
4
|
-
* Parses and generates rules in Claude Code's formats:
|
|
5
|
-
* - CLAUDE.md / .claude/CLAUDE.md (project-level Markdown)
|
|
6
|
-
* - .claude/rules/*.md (Markdown with optional `paths` frontmatter)
|
|
7
|
-
*
|
|
8
|
-
* Source: Claude Code official documentation.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import matter from 'gray-matter';
|
|
12
|
-
import type { RuleFormatAdapter, UnifiedRule, RuleSource } from '../../types.js';
|
|
13
|
-
import { hashContent, generateRuleId } from '../utils.js';
|
|
14
|
-
|
|
15
|
-
export class ClaudeCodeAdapter implements RuleFormatAdapter {
|
|
16
|
-
readonly source: RuleSource = 'claude-code';
|
|
17
|
-
|
|
18
|
-
readonly filePatterns = [
|
|
19
|
-
'CLAUDE.md',
|
|
20
|
-
'.claude/CLAUDE.md',
|
|
21
|
-
'.claude/rules/*.md',
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
parse(filePath: string, content: string): UnifiedRule[] {
|
|
25
|
-
// .claude/rules/*.md may have `paths` frontmatter
|
|
26
|
-
if (filePath.includes('.claude/rules/')) {
|
|
27
|
-
return this.parseModularRule(filePath, content);
|
|
28
|
-
}
|
|
29
|
-
// CLAUDE.md — project-level
|
|
30
|
-
return this.parseClaudeMd(filePath, content);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
generate(rules: UnifiedRule[]): { filePath: string; content: string }[] {
|
|
34
|
-
const projectRules = rules.filter(r => r.scope !== 'path-specific');
|
|
35
|
-
const pathRules = rules.filter(r => r.scope === 'path-specific');
|
|
36
|
-
|
|
37
|
-
const files: { filePath: string; content: string }[] = [];
|
|
38
|
-
|
|
39
|
-
if (projectRules.length > 0) {
|
|
40
|
-
files.push({
|
|
41
|
-
filePath: 'CLAUDE.md',
|
|
42
|
-
content: projectRules.map(r => r.content).join('\n\n'),
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
for (const rule of pathRules) {
|
|
47
|
-
const fm: Record<string, unknown> = {};
|
|
48
|
-
if (rule.paths && rule.paths.length > 0) fm.paths = rule.paths;
|
|
49
|
-
|
|
50
|
-
const fileName = rule.id
|
|
51
|
-
.replace(/^claude-code:/, '')
|
|
52
|
-
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
53
|
-
|| 'rule';
|
|
54
|
-
|
|
55
|
-
files.push({
|
|
56
|
-
filePath: `.claude/rules/${fileName}.md`,
|
|
57
|
-
content: Object.keys(fm).length > 0
|
|
58
|
-
? matter.stringify(rule.content, fm)
|
|
59
|
-
: rule.content,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return files;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private parseClaudeMd(filePath: string, content: string): UnifiedRule[] {
|
|
67
|
-
const trimmed = content.trim();
|
|
68
|
-
if (!trimmed) return [];
|
|
69
|
-
|
|
70
|
-
return [{
|
|
71
|
-
id: generateRuleId('claude-code', filePath),
|
|
72
|
-
content: trimmed,
|
|
73
|
-
source: 'claude-code',
|
|
74
|
-
scope: 'project',
|
|
75
|
-
priority: 5,
|
|
76
|
-
hash: hashContent(trimmed),
|
|
77
|
-
}];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private parseModularRule(filePath: string, content: string): UnifiedRule[] {
|
|
81
|
-
const { data, content: body } = matter(content);
|
|
82
|
-
const trimmed = body.trim();
|
|
83
|
-
if (!trimmed) return [];
|
|
84
|
-
|
|
85
|
-
const paths = data.paths as string[] | undefined;
|
|
86
|
-
const hasPaths = Array.isArray(paths) && paths.length > 0;
|
|
87
|
-
|
|
88
|
-
return [{
|
|
89
|
-
id: generateRuleId('claude-code', filePath),
|
|
90
|
-
content: trimmed,
|
|
91
|
-
description: data.description as string | undefined,
|
|
92
|
-
source: 'claude-code',
|
|
93
|
-
scope: hasPaths ? 'path-specific' : 'project',
|
|
94
|
-
paths: hasPaths ? paths : undefined,
|
|
95
|
-
priority: 5,
|
|
96
|
-
hash: hashContent(trimmed),
|
|
97
|
-
}];
|
|
98
|
-
}
|
|
99
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Rule Format Adapter
|
|
3
|
+
*
|
|
4
|
+
* Parses and generates rules in Claude Code's formats:
|
|
5
|
+
* - CLAUDE.md / .claude/CLAUDE.md (project-level Markdown)
|
|
6
|
+
* - .claude/rules/*.md (Markdown with optional `paths` frontmatter)
|
|
7
|
+
*
|
|
8
|
+
* Source: Claude Code official documentation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import matter from 'gray-matter';
|
|
12
|
+
import type { RuleFormatAdapter, UnifiedRule, RuleSource } from '../../types.js';
|
|
13
|
+
import { hashContent, generateRuleId } from '../utils.js';
|
|
14
|
+
|
|
15
|
+
export class ClaudeCodeAdapter implements RuleFormatAdapter {
|
|
16
|
+
readonly source: RuleSource = 'claude-code';
|
|
17
|
+
|
|
18
|
+
readonly filePatterns = [
|
|
19
|
+
'CLAUDE.md',
|
|
20
|
+
'.claude/CLAUDE.md',
|
|
21
|
+
'.claude/rules/*.md',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
parse(filePath: string, content: string): UnifiedRule[] {
|
|
25
|
+
// .claude/rules/*.md may have `paths` frontmatter
|
|
26
|
+
if (filePath.includes('.claude/rules/')) {
|
|
27
|
+
return this.parseModularRule(filePath, content);
|
|
28
|
+
}
|
|
29
|
+
// CLAUDE.md — project-level
|
|
30
|
+
return this.parseClaudeMd(filePath, content);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
generate(rules: UnifiedRule[]): { filePath: string; content: string }[] {
|
|
34
|
+
const projectRules = rules.filter(r => r.scope !== 'path-specific');
|
|
35
|
+
const pathRules = rules.filter(r => r.scope === 'path-specific');
|
|
36
|
+
|
|
37
|
+
const files: { filePath: string; content: string }[] = [];
|
|
38
|
+
|
|
39
|
+
if (projectRules.length > 0) {
|
|
40
|
+
files.push({
|
|
41
|
+
filePath: 'CLAUDE.md',
|
|
42
|
+
content: projectRules.map(r => r.content).join('\n\n'),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const rule of pathRules) {
|
|
47
|
+
const fm: Record<string, unknown> = {};
|
|
48
|
+
if (rule.paths && rule.paths.length > 0) fm.paths = rule.paths;
|
|
49
|
+
|
|
50
|
+
const fileName = rule.id
|
|
51
|
+
.replace(/^claude-code:/, '')
|
|
52
|
+
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
53
|
+
|| 'rule';
|
|
54
|
+
|
|
55
|
+
files.push({
|
|
56
|
+
filePath: `.claude/rules/${fileName}.md`,
|
|
57
|
+
content: Object.keys(fm).length > 0
|
|
58
|
+
? matter.stringify(rule.content, fm)
|
|
59
|
+
: rule.content,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private parseClaudeMd(filePath: string, content: string): UnifiedRule[] {
|
|
67
|
+
const trimmed = content.trim();
|
|
68
|
+
if (!trimmed) return [];
|
|
69
|
+
|
|
70
|
+
return [{
|
|
71
|
+
id: generateRuleId('claude-code', filePath),
|
|
72
|
+
content: trimmed,
|
|
73
|
+
source: 'claude-code',
|
|
74
|
+
scope: 'project',
|
|
75
|
+
priority: 5,
|
|
76
|
+
hash: hashContent(trimmed),
|
|
77
|
+
}];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private parseModularRule(filePath: string, content: string): UnifiedRule[] {
|
|
81
|
+
const { data, content: body } = matter(content);
|
|
82
|
+
const trimmed = body.trim();
|
|
83
|
+
if (!trimmed) return [];
|
|
84
|
+
|
|
85
|
+
const paths = data.paths as string[] | undefined;
|
|
86
|
+
const hasPaths = Array.isArray(paths) && paths.length > 0;
|
|
87
|
+
|
|
88
|
+
return [{
|
|
89
|
+
id: generateRuleId('claude-code', filePath),
|
|
90
|
+
content: trimmed,
|
|
91
|
+
description: data.description as string | undefined,
|
|
92
|
+
source: 'claude-code',
|
|
93
|
+
scope: hasPaths ? 'path-specific' : 'project',
|
|
94
|
+
paths: hasPaths ? paths : undefined,
|
|
95
|
+
priority: 5,
|
|
96
|
+
hash: hashContent(trimmed),
|
|
97
|
+
}];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAI Codex Rule Format Adapter
|
|
3
|
-
*
|
|
4
|
-
* Parses and generates rules in Codex's formats:
|
|
5
|
-
* - .agents/skills/[name]/SKILL.md (Markdown + frontmatter with name, description)
|
|
6
|
-
* - AGENTS.md (pure Markdown)
|
|
7
|
-
*
|
|
8
|
-
* Source: OpenAI Codex Skills documentation.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import matter from 'gray-matter';
|
|
12
|
-
import type { RuleFormatAdapter, UnifiedRule, RuleSource } from '../../types.js';
|
|
13
|
-
import { hashContent, generateRuleId } from '../utils.js';
|
|
14
|
-
|
|
15
|
-
export class CodexAdapter implements RuleFormatAdapter {
|
|
16
|
-
readonly source: RuleSource = 'codex';
|
|
17
|
-
|
|
18
|
-
readonly filePatterns = [
|
|
19
|
-
'.agents/skills/*/SKILL.md',
|
|
20
|
-
'AGENTS.md',
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
parse(filePath: string, content: string): UnifiedRule[] {
|
|
24
|
-
if (filePath.includes('SKILL.md')) {
|
|
25
|
-
return this.parseSkillMd(filePath, content);
|
|
26
|
-
}
|
|
27
|
-
if (filePath.endsWith('AGENTS.md')) {
|
|
28
|
-
return this.parseAgentsMd(filePath, content);
|
|
29
|
-
}
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
generate(rules: UnifiedRule[]): { filePath: string; content: string }[] {
|
|
34
|
-
return rules.map((rule, i) => {
|
|
35
|
-
const skillName = rule.id
|
|
36
|
-
.replace(/^codex:/, '')
|
|
37
|
-
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
38
|
-
|| `skill-${i}`;
|
|
39
|
-
|
|
40
|
-
const fm: Record<string, unknown> = {
|
|
41
|
-
name: skillName,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// Codex needs description to know WHEN to trigger the skill.
|
|
45
|
-
// If no description exists, auto-generate from content (first 120 chars).
|
|
46
|
-
if (rule.description) {
|
|
47
|
-
fm.description = rule.description;
|
|
48
|
-
} else {
|
|
49
|
-
fm.description = this.autoDescription(rule.content);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
filePath: `.agents/skills/${skillName}/SKILL.md`,
|
|
54
|
-
content: matter.stringify(rule.content, fm),
|
|
55
|
-
};
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Extract a short description from rule content for Codex skill triggering */
|
|
60
|
-
private autoDescription(content: string): string {
|
|
61
|
-
// Take first meaningful line, strip markdown formatting
|
|
62
|
-
const lines = content.split('\n').map(l => l.trim()).filter(Boolean);
|
|
63
|
-
const first = lines[0]?.replace(/^#+\s*/, '').replace(/^[-*]\s*/, '') || 'Project rules';
|
|
64
|
-
if (first.length <= 120) return first;
|
|
65
|
-
return first.substring(0, 117) + '...';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private parseSkillMd(filePath: string, content: string): UnifiedRule[] {
|
|
69
|
-
const { data, content: body } = matter(content);
|
|
70
|
-
const trimmed = body.trim();
|
|
71
|
-
if (!trimmed) return [];
|
|
72
|
-
|
|
73
|
-
return [{
|
|
74
|
-
id: generateRuleId('codex', filePath),
|
|
75
|
-
content: trimmed,
|
|
76
|
-
description: (data.description as string) || undefined,
|
|
77
|
-
source: 'codex',
|
|
78
|
-
scope: 'project',
|
|
79
|
-
priority: 5,
|
|
80
|
-
hash: hashContent(trimmed),
|
|
81
|
-
}];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private parseAgentsMd(filePath: string, content: string): UnifiedRule[] {
|
|
85
|
-
const trimmed = content.trim();
|
|
86
|
-
if (!trimmed) return [];
|
|
87
|
-
|
|
88
|
-
return [{
|
|
89
|
-
id: generateRuleId('codex', filePath),
|
|
90
|
-
content: trimmed,
|
|
91
|
-
source: 'codex',
|
|
92
|
-
scope: 'project',
|
|
93
|
-
priority: 5,
|
|
94
|
-
hash: hashContent(trimmed),
|
|
95
|
-
}];
|
|
96
|
-
}
|
|
97
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Codex Rule Format Adapter
|
|
3
|
+
*
|
|
4
|
+
* Parses and generates rules in Codex's formats:
|
|
5
|
+
* - .agents/skills/[name]/SKILL.md (Markdown + frontmatter with name, description)
|
|
6
|
+
* - AGENTS.md (pure Markdown)
|
|
7
|
+
*
|
|
8
|
+
* Source: OpenAI Codex Skills documentation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import matter from 'gray-matter';
|
|
12
|
+
import type { RuleFormatAdapter, UnifiedRule, RuleSource } from '../../types.js';
|
|
13
|
+
import { hashContent, generateRuleId } from '../utils.js';
|
|
14
|
+
|
|
15
|
+
export class CodexAdapter implements RuleFormatAdapter {
|
|
16
|
+
readonly source: RuleSource = 'codex';
|
|
17
|
+
|
|
18
|
+
readonly filePatterns = [
|
|
19
|
+
'.agents/skills/*/SKILL.md',
|
|
20
|
+
'AGENTS.md',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
parse(filePath: string, content: string): UnifiedRule[] {
|
|
24
|
+
if (filePath.includes('SKILL.md')) {
|
|
25
|
+
return this.parseSkillMd(filePath, content);
|
|
26
|
+
}
|
|
27
|
+
if (filePath.endsWith('AGENTS.md')) {
|
|
28
|
+
return this.parseAgentsMd(filePath, content);
|
|
29
|
+
}
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
generate(rules: UnifiedRule[]): { filePath: string; content: string }[] {
|
|
34
|
+
return rules.map((rule, i) => {
|
|
35
|
+
const skillName = rule.id
|
|
36
|
+
.replace(/^codex:/, '')
|
|
37
|
+
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
38
|
+
|| `skill-${i}`;
|
|
39
|
+
|
|
40
|
+
const fm: Record<string, unknown> = {
|
|
41
|
+
name: skillName,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Codex needs description to know WHEN to trigger the skill.
|
|
45
|
+
// If no description exists, auto-generate from content (first 120 chars).
|
|
46
|
+
if (rule.description) {
|
|
47
|
+
fm.description = rule.description;
|
|
48
|
+
} else {
|
|
49
|
+
fm.description = this.autoDescription(rule.content);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
filePath: `.agents/skills/${skillName}/SKILL.md`,
|
|
54
|
+
content: matter.stringify(rule.content, fm),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Extract a short description from rule content for Codex skill triggering */
|
|
60
|
+
private autoDescription(content: string): string {
|
|
61
|
+
// Take first meaningful line, strip markdown formatting
|
|
62
|
+
const lines = content.split('\n').map(l => l.trim()).filter(Boolean);
|
|
63
|
+
const first = lines[0]?.replace(/^#+\s*/, '').replace(/^[-*]\s*/, '') || 'Project rules';
|
|
64
|
+
if (first.length <= 120) return first;
|
|
65
|
+
return first.substring(0, 117) + '...';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private parseSkillMd(filePath: string, content: string): UnifiedRule[] {
|
|
69
|
+
const { data, content: body } = matter(content);
|
|
70
|
+
const trimmed = body.trim();
|
|
71
|
+
if (!trimmed) return [];
|
|
72
|
+
|
|
73
|
+
return [{
|
|
74
|
+
id: generateRuleId('codex', filePath),
|
|
75
|
+
content: trimmed,
|
|
76
|
+
description: (data.description as string) || undefined,
|
|
77
|
+
source: 'codex',
|
|
78
|
+
scope: 'project',
|
|
79
|
+
priority: 5,
|
|
80
|
+
hash: hashContent(trimmed),
|
|
81
|
+
}];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private parseAgentsMd(filePath: string, content: string): UnifiedRule[] {
|
|
85
|
+
const trimmed = content.trim();
|
|
86
|
+
if (!trimmed) return [];
|
|
87
|
+
|
|
88
|
+
return [{
|
|
89
|
+
id: generateRuleId('codex', filePath),
|
|
90
|
+
content: trimmed,
|
|
91
|
+
source: 'codex',
|
|
92
|
+
scope: 'project',
|
|
93
|
+
priority: 5,
|
|
94
|
+
hash: hashContent(trimmed),
|
|
95
|
+
}];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,124 +1,124 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GitHub Copilot Rule Format Adapter
|
|
3
|
-
*
|
|
4
|
-
* Parses and generates rules in Copilot's formats:
|
|
5
|
-
* - .github/copilot-instructions.md (repository-wide instructions, plain Markdown)
|
|
6
|
-
* - .github/instructions/*.instructions.md (path-specific, YAML frontmatter with `applyTo` glob)
|
|
7
|
-
*
|
|
8
|
-
* Source: https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import matter from 'gray-matter';
|
|
12
|
-
import type { RuleFormatAdapter, UnifiedRule, RuleSource } from '../../types.js';
|
|
13
|
-
import { hashContent, generateRuleId } from '../utils.js';
|
|
14
|
-
|
|
15
|
-
export class CopilotAdapter implements RuleFormatAdapter {
|
|
16
|
-
readonly source: RuleSource = 'copilot';
|
|
17
|
-
|
|
18
|
-
readonly filePatterns = [
|
|
19
|
-
'.github/copilot-instructions.md',
|
|
20
|
-
'.github/instructions/*.instructions.md',
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
parse(filePath: string, content: string): UnifiedRule[] {
|
|
24
|
-
// Path-specific instruction files (.github/instructions/*.instructions.md)
|
|
25
|
-
if (filePath.includes('.instructions.md') && filePath.includes('.github/instructions')) {
|
|
26
|
-
return this.parsePathSpecific(filePath, content);
|
|
27
|
-
}
|
|
28
|
-
// Repository-wide instructions (.github/copilot-instructions.md)
|
|
29
|
-
if (filePath.includes('copilot-instructions.md')) {
|
|
30
|
-
return this.parseRepoWide(filePath, content);
|
|
31
|
-
}
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
generate(rules: UnifiedRule[]): { filePath: string; content: string }[] {
|
|
36
|
-
// If only one rule with no path-specific globs, output as copilot-instructions.md
|
|
37
|
-
if (rules.length === 1 && (!rules[0].paths || rules[0].paths.length === 0)) {
|
|
38
|
-
return [{
|
|
39
|
-
filePath: '.github/copilot-instructions.md',
|
|
40
|
-
content: rules[0].content,
|
|
41
|
-
}];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Multiple rules → output as path-specific instruction files
|
|
45
|
-
return rules.map((rule, i) => {
|
|
46
|
-
const fm: Record<string, unknown> = {};
|
|
47
|
-
|
|
48
|
-
// Preserve applyTo if available
|
|
49
|
-
if (rule.paths && rule.paths.length > 0) {
|
|
50
|
-
fm.applyTo = rule.paths.join(',');
|
|
51
|
-
}
|
|
52
|
-
if (rule.description) {
|
|
53
|
-
fm.description = rule.description;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const fileName = rule.id
|
|
57
|
-
.replace(/^copilot:/, '')
|
|
58
|
-
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
59
|
-
|| `instruction-${i}`;
|
|
60
|
-
|
|
61
|
-
const body = Object.keys(fm).length > 0
|
|
62
|
-
? matter.stringify(rule.content, fm)
|
|
63
|
-
: rule.content;
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
filePath: `.github/instructions/${fileName}.instructions.md`,
|
|
67
|
-
content: body,
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Parse repository-wide .github/copilot-instructions.md
|
|
74
|
-
* This is plain Markdown with no frontmatter.
|
|
75
|
-
*/
|
|
76
|
-
private parseRepoWide(filePath: string, content: string): UnifiedRule[] {
|
|
77
|
-
const trimmed = content.trim();
|
|
78
|
-
if (!trimmed) return [];
|
|
79
|
-
|
|
80
|
-
return [{
|
|
81
|
-
id: generateRuleId('copilot', filePath),
|
|
82
|
-
content: trimmed,
|
|
83
|
-
description: 'Repository-wide Copilot instructions',
|
|
84
|
-
source: 'copilot',
|
|
85
|
-
scope: 'project',
|
|
86
|
-
priority: 3,
|
|
87
|
-
hash: hashContent(trimmed),
|
|
88
|
-
}];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Parse path-specific .github/instructions/*.instructions.md
|
|
93
|
-
* These have YAML frontmatter with `applyTo` glob pattern(s).
|
|
94
|
-
*/
|
|
95
|
-
private parsePathSpecific(filePath: string, content: string): UnifiedRule[] {
|
|
96
|
-
const { data, content: body } = matter(content);
|
|
97
|
-
const trimmed = body.trim();
|
|
98
|
-
if (!trimmed) return [];
|
|
99
|
-
|
|
100
|
-
const applyTo = data.applyTo as string | undefined;
|
|
101
|
-
const hasApplyTo = !!applyTo;
|
|
102
|
-
|
|
103
|
-
const rule: UnifiedRule = {
|
|
104
|
-
id: generateRuleId('copilot', filePath),
|
|
105
|
-
content: trimmed,
|
|
106
|
-
source: 'copilot',
|
|
107
|
-
scope: hasApplyTo ? 'path-specific' : 'project',
|
|
108
|
-
priority: 5,
|
|
109
|
-
hash: hashContent(trimmed),
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// Extract applyTo glob pattern(s) → store in paths[]
|
|
113
|
-
if (hasApplyTo) {
|
|
114
|
-
rule.paths = applyTo!.split(',').map(p => p.trim());
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Extract description if present
|
|
118
|
-
if (data.description) {
|
|
119
|
-
rule.description = data.description as string;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return [rule];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot Rule Format Adapter
|
|
3
|
+
*
|
|
4
|
+
* Parses and generates rules in Copilot's formats:
|
|
5
|
+
* - .github/copilot-instructions.md (repository-wide instructions, plain Markdown)
|
|
6
|
+
* - .github/instructions/*.instructions.md (path-specific, YAML frontmatter with `applyTo` glob)
|
|
7
|
+
*
|
|
8
|
+
* Source: https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import matter from 'gray-matter';
|
|
12
|
+
import type { RuleFormatAdapter, UnifiedRule, RuleSource } from '../../types.js';
|
|
13
|
+
import { hashContent, generateRuleId } from '../utils.js';
|
|
14
|
+
|
|
15
|
+
export class CopilotAdapter implements RuleFormatAdapter {
|
|
16
|
+
readonly source: RuleSource = 'copilot';
|
|
17
|
+
|
|
18
|
+
readonly filePatterns = [
|
|
19
|
+
'.github/copilot-instructions.md',
|
|
20
|
+
'.github/instructions/*.instructions.md',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
parse(filePath: string, content: string): UnifiedRule[] {
|
|
24
|
+
// Path-specific instruction files (.github/instructions/*.instructions.md)
|
|
25
|
+
if (filePath.includes('.instructions.md') && filePath.includes('.github/instructions')) {
|
|
26
|
+
return this.parsePathSpecific(filePath, content);
|
|
27
|
+
}
|
|
28
|
+
// Repository-wide instructions (.github/copilot-instructions.md)
|
|
29
|
+
if (filePath.includes('copilot-instructions.md')) {
|
|
30
|
+
return this.parseRepoWide(filePath, content);
|
|
31
|
+
}
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
generate(rules: UnifiedRule[]): { filePath: string; content: string }[] {
|
|
36
|
+
// If only one rule with no path-specific globs, output as copilot-instructions.md
|
|
37
|
+
if (rules.length === 1 && (!rules[0].paths || rules[0].paths.length === 0)) {
|
|
38
|
+
return [{
|
|
39
|
+
filePath: '.github/copilot-instructions.md',
|
|
40
|
+
content: rules[0].content,
|
|
41
|
+
}];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Multiple rules → output as path-specific instruction files
|
|
45
|
+
return rules.map((rule, i) => {
|
|
46
|
+
const fm: Record<string, unknown> = {};
|
|
47
|
+
|
|
48
|
+
// Preserve applyTo if available
|
|
49
|
+
if (rule.paths && rule.paths.length > 0) {
|
|
50
|
+
fm.applyTo = rule.paths.join(',');
|
|
51
|
+
}
|
|
52
|
+
if (rule.description) {
|
|
53
|
+
fm.description = rule.description;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const fileName = rule.id
|
|
57
|
+
.replace(/^copilot:/, '')
|
|
58
|
+
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
59
|
+
|| `instruction-${i}`;
|
|
60
|
+
|
|
61
|
+
const body = Object.keys(fm).length > 0
|
|
62
|
+
? matter.stringify(rule.content, fm)
|
|
63
|
+
: rule.content;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
filePath: `.github/instructions/${fileName}.instructions.md`,
|
|
67
|
+
content: body,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parse repository-wide .github/copilot-instructions.md
|
|
74
|
+
* This is plain Markdown with no frontmatter.
|
|
75
|
+
*/
|
|
76
|
+
private parseRepoWide(filePath: string, content: string): UnifiedRule[] {
|
|
77
|
+
const trimmed = content.trim();
|
|
78
|
+
if (!trimmed) return [];
|
|
79
|
+
|
|
80
|
+
return [{
|
|
81
|
+
id: generateRuleId('copilot', filePath),
|
|
82
|
+
content: trimmed,
|
|
83
|
+
description: 'Repository-wide Copilot instructions',
|
|
84
|
+
source: 'copilot',
|
|
85
|
+
scope: 'project',
|
|
86
|
+
priority: 3,
|
|
87
|
+
hash: hashContent(trimmed),
|
|
88
|
+
}];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Parse path-specific .github/instructions/*.instructions.md
|
|
93
|
+
* These have YAML frontmatter with `applyTo` glob pattern(s).
|
|
94
|
+
*/
|
|
95
|
+
private parsePathSpecific(filePath: string, content: string): UnifiedRule[] {
|
|
96
|
+
const { data, content: body } = matter(content);
|
|
97
|
+
const trimmed = body.trim();
|
|
98
|
+
if (!trimmed) return [];
|
|
99
|
+
|
|
100
|
+
const applyTo = data.applyTo as string | undefined;
|
|
101
|
+
const hasApplyTo = !!applyTo;
|
|
102
|
+
|
|
103
|
+
const rule: UnifiedRule = {
|
|
104
|
+
id: generateRuleId('copilot', filePath),
|
|
105
|
+
content: trimmed,
|
|
106
|
+
source: 'copilot',
|
|
107
|
+
scope: hasApplyTo ? 'path-specific' : 'project',
|
|
108
|
+
priority: 5,
|
|
109
|
+
hash: hashContent(trimmed),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Extract applyTo glob pattern(s) → store in paths[]
|
|
113
|
+
if (hasApplyTo) {
|
|
114
|
+
rule.paths = applyTo!.split(',').map(p => p.trim());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Extract description if present
|
|
118
|
+
if (data.description) {
|
|
119
|
+
rule.description = data.description as string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return [rule];
|
|
123
|
+
}
|
|
124
|
+
}
|