@deimoscloud/coreai 0.1.0
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/.prettierrc +9 -0
- package/AGENT_SPEC.md +347 -0
- package/ARCHITECTURE.md +547 -0
- package/DRAFT_PRD.md +1440 -0
- package/IMPLEMENTATION_PLAN.md +256 -0
- package/PRODUCT.md +473 -0
- package/README.md +303 -0
- package/WORKFLOWS.md +295 -0
- package/agents/_templates/ic-engineer.md +185 -0
- package/agents/_templates/reviewer.md +182 -0
- package/agents/backend-engineer.yaml +72 -0
- package/agents/devops-engineer.yaml +72 -0
- package/agents/engineering-manager.yaml +70 -0
- package/agents/examples/android-engineer.md +302 -0
- package/agents/examples/backend-engineer.md +320 -0
- package/agents/examples/devops-engineer.md +742 -0
- package/agents/examples/engineering-manager.md +469 -0
- package/agents/examples/frontend-engineer.md +58 -0
- package/agents/examples/product-manager.md +315 -0
- package/agents/examples/qa-engineer.md +371 -0
- package/agents/examples/security-engineer.md +525 -0
- package/agents/examples/solutions-architect.md +351 -0
- package/agents/examples/wearos-engineer.md +359 -0
- package/agents/frontend-engineer.yaml +72 -0
- package/commands/core/check-inbox.md +34 -0
- package/commands/core/delegate.md +30 -0
- package/commands/core/git-commit.md +144 -0
- package/commands/core/pr-create.md +193 -0
- package/commands/core/review.md +56 -0
- package/commands/core/sprint-status.md +65 -0
- package/commands/optional/docs-update.md +200 -0
- package/commands/optional/jira-create.md +200 -0
- package/commands/optional/jira-transition.md +184 -0
- package/commands/optional/worktree-cleanup.md +167 -0
- package/commands/optional/worktree-setup.md +110 -0
- package/dist/cli/index.js +4037 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +2978 -0
- package/dist/index.js +3867 -0
- package/dist/index.js.map +1 -0
- package/eslint.config.js +29 -0
- package/jest.config.js +22 -0
- package/knowledge-library/README.md +118 -0
- package/knowledge-library/android-engineer/context/current.txt +42 -0
- package/knowledge-library/android-engineer/control/decisions.txt +9 -0
- package/knowledge-library/android-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/android-engineer/control/objectives.txt +26 -0
- package/knowledge-library/android-engineer/history/.gitkeep +0 -0
- package/knowledge-library/android-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/android-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/android-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/architecture.txt +61 -0
- package/knowledge-library/backend-engineer/context/current.txt +42 -0
- package/knowledge-library/backend-engineer/control/decisions.txt +9 -0
- package/knowledge-library/backend-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/backend-engineer/control/objectives.txt +26 -0
- package/knowledge-library/backend-engineer/history/.gitkeep +0 -0
- package/knowledge-library/backend-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/backend-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/backend-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/context.txt +52 -0
- package/knowledge-library/devops-engineer/context/current.txt +42 -0
- package/knowledge-library/devops-engineer/control/decisions.txt +9 -0
- package/knowledge-library/devops-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/devops-engineer/control/objectives.txt +26 -0
- package/knowledge-library/devops-engineer/history/.gitkeep +0 -0
- package/knowledge-library/devops-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/devops-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/devops-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/context/current.txt +40 -0
- package/knowledge-library/engineering-manager/control/decisions.txt +9 -0
- package/knowledge-library/engineering-manager/control/objectives.txt +27 -0
- package/knowledge-library/engineering-manager/history/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/outbox/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/tech/.gitkeep +0 -0
- package/knowledge-library/prd.txt +81 -0
- package/knowledge-library/product-manager/context/current.txt +42 -0
- package/knowledge-library/product-manager/control/decisions.txt +9 -0
- package/knowledge-library/product-manager/control/dependencies.txt +19 -0
- package/knowledge-library/product-manager/control/objectives.txt +26 -0
- package/knowledge-library/product-manager/history/.gitkeep +0 -0
- package/knowledge-library/product-manager/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/product-manager/outbox/.gitkeep +0 -0
- package/knowledge-library/product-manager/tech/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/context/current.txt +42 -0
- package/knowledge-library/qa-engineer/control/decisions.txt +9 -0
- package/knowledge-library/qa-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/qa-engineer/control/objectives.txt +26 -0
- package/knowledge-library/qa-engineer/history/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/security-engineer/context/current.txt +42 -0
- package/knowledge-library/security-engineer/control/decisions.txt +9 -0
- package/knowledge-library/security-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/security-engineer/control/objectives.txt +26 -0
- package/knowledge-library/security-engineer/history/.gitkeep +0 -0
- package/knowledge-library/security-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/security-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/security-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/context/current.txt +42 -0
- package/knowledge-library/solutions-architect/control/decisions.txt +9 -0
- package/knowledge-library/solutions-architect/control/dependencies.txt +19 -0
- package/knowledge-library/solutions-architect/control/objectives.txt +26 -0
- package/knowledge-library/solutions-architect/history/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/outbox/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/tech/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/context/current.txt +42 -0
- package/knowledge-library/wearos-engineer/control/decisions.txt +9 -0
- package/knowledge-library/wearos-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/wearos-engineer/control/objectives.txt +26 -0
- package/knowledge-library/wearos-engineer/history/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/tech/.gitkeep +0 -0
- package/package.json +66 -0
- package/schemas/agent.schema.json +171 -0
- package/schemas/coreai.config.schema.json +257 -0
- package/scripts/add-agent.sh +323 -0
- package/scripts/install.sh +354 -0
- package/src/adapters/factory.test.ts +386 -0
- package/src/adapters/factory.ts +305 -0
- package/src/adapters/index.ts +113 -0
- package/src/adapters/interfaces.ts +268 -0
- package/src/adapters/mcp/client.test.ts +130 -0
- package/src/adapters/mcp/client.ts +451 -0
- package/src/adapters/mcp/discovery.test.ts +315 -0
- package/src/adapters/mcp/discovery.ts +340 -0
- package/src/adapters/mcp/index.ts +66 -0
- package/src/adapters/mcp/mapper.test.ts +218 -0
- package/src/adapters/mcp/mapper.ts +536 -0
- package/src/adapters/mcp/registry.test.ts +433 -0
- package/src/adapters/mcp/registry.ts +550 -0
- package/src/adapters/mcp/types.ts +258 -0
- package/src/adapters/native/filesystem.test.ts +350 -0
- package/src/adapters/native/filesystem.ts +393 -0
- package/src/adapters/native/github.test.ts +173 -0
- package/src/adapters/native/github.ts +627 -0
- package/src/adapters/native/index.ts +22 -0
- package/src/adapters/native/selector.test.ts +224 -0
- package/src/adapters/native/selector.ts +150 -0
- package/src/adapters/types.ts +270 -0
- package/src/agents/compiler.test.ts +399 -0
- package/src/agents/compiler.ts +359 -0
- package/src/agents/index.ts +36 -0
- package/src/agents/loader.test.ts +319 -0
- package/src/agents/loader.ts +143 -0
- package/src/agents/resolver.test.ts +282 -0
- package/src/agents/resolver.ts +262 -0
- package/src/agents/types.ts +87 -0
- package/src/cache/index.ts +38 -0
- package/src/cache/interfaces.ts +283 -0
- package/src/cache/manager.test.ts +266 -0
- package/src/cache/manager.ts +388 -0
- package/src/cache/provider.test.ts +485 -0
- package/src/cache/provider.ts +745 -0
- package/src/cache/types.test.ts +192 -0
- package/src/cache/types.ts +313 -0
- package/src/cli/commands/build.test.ts +248 -0
- package/src/cli/commands/build.ts +244 -0
- package/src/cli/commands/cache.test.ts +221 -0
- package/src/cli/commands/cache.ts +229 -0
- package/src/cli/commands/index.ts +63 -0
- package/src/cli/commands/init.test.ts +173 -0
- package/src/cli/commands/init.ts +296 -0
- package/src/cli/commands/skills.test.ts +272 -0
- package/src/cli/commands/skills.ts +348 -0
- package/src/cli/commands/status.test.ts +392 -0
- package/src/cli/commands/status.ts +332 -0
- package/src/cli/commands/sync.test.ts +213 -0
- package/src/cli/commands/sync.ts +251 -0
- package/src/cli/commands/validate.test.ts +216 -0
- package/src/cli/commands/validate.ts +340 -0
- package/src/cli/index.test.ts +190 -0
- package/src/cli/index.ts +493 -0
- package/src/commands/context.test.ts +163 -0
- package/src/commands/context.ts +111 -0
- package/src/commands/index.ts +56 -0
- package/src/commands/loader.test.ts +273 -0
- package/src/commands/loader.ts +355 -0
- package/src/commands/registry.test.ts +384 -0
- package/src/commands/registry.ts +248 -0
- package/src/commands/runner.test.ts +297 -0
- package/src/commands/runner.ts +222 -0
- package/src/commands/types.ts +361 -0
- package/src/config/index.ts +19 -0
- package/src/config/loader.test.ts +262 -0
- package/src/config/loader.ts +188 -0
- package/src/config/types.ts +154 -0
- package/src/context/index.ts +14 -0
- package/src/context/loader.test.ts +334 -0
- package/src/context/loader.ts +357 -0
- package/src/index.test.ts +13 -0
- package/src/index.ts +244 -0
- package/src/knowledge-library/index.ts +44 -0
- package/src/knowledge-library/manager.test.ts +536 -0
- package/src/knowledge-library/manager.ts +804 -0
- package/src/knowledge-library/types.ts +432 -0
- package/src/skills/generator.test.ts +602 -0
- package/src/skills/generator.ts +491 -0
- package/src/skills/index.ts +27 -0
- package/src/skills/templates.ts +520 -0
- package/src/skills/types.ts +251 -0
- package/templates/completion-report.md +72 -0
- package/templates/feedback.md +56 -0
- package/templates/project-files/CLAUDE.md.template +109 -0
- package/templates/project-files/coreai.json.example +47 -0
- package/templates/project-files/mcp.json.template +20 -0
- package/templates/review-complete.md +64 -0
- package/templates/review-request.md +67 -0
- package/templates/task-assignment.md +51 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +26 -0
- package/tsup.config.ts +23 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Context
|
|
3
|
+
*
|
|
4
|
+
* Runtime context for command execution with graceful degradation support.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ResolvedCoreAIConfig } from '../config/types.js';
|
|
8
|
+
import type { AdapterType } from '../adapters/types.js';
|
|
9
|
+
import type { AdapterFactory } from '../adapters/factory.js';
|
|
10
|
+
import { createAdapterFactory } from '../adapters/factory.js';
|
|
11
|
+
import { loadConfig, configExists } from '../config/loader.js';
|
|
12
|
+
import type { CommandContext } from './types.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Options for creating a command context
|
|
16
|
+
*/
|
|
17
|
+
export interface CreateContextOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Project root directory
|
|
20
|
+
*/
|
|
21
|
+
projectRoot?: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Pre-loaded configuration (if already loaded)
|
|
25
|
+
*/
|
|
26
|
+
config?: ResolvedCoreAIConfig;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Pre-created adapter factory (if already created)
|
|
30
|
+
*/
|
|
31
|
+
adapterFactory?: AdapterFactory;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a command execution context
|
|
36
|
+
*
|
|
37
|
+
* Handles configuration loading and adapter factory creation with graceful fallbacks.
|
|
38
|
+
*/
|
|
39
|
+
export function createCommandContext(options: CreateContextOptions = {}): CommandContext {
|
|
40
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
41
|
+
|
|
42
|
+
// Load config if not provided
|
|
43
|
+
let config: ResolvedCoreAIConfig | null = options.config ?? null;
|
|
44
|
+
if (!config && configExists(projectRoot)) {
|
|
45
|
+
try {
|
|
46
|
+
config = loadConfig(projectRoot);
|
|
47
|
+
} catch {
|
|
48
|
+
// Config loading failed, continue without config
|
|
49
|
+
config = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Create adapter factory if config is available and factory not provided
|
|
54
|
+
let adapterFactory: AdapterFactory | null = options.adapterFactory ?? null;
|
|
55
|
+
if (!adapterFactory && config) {
|
|
56
|
+
try {
|
|
57
|
+
adapterFactory = createAdapterFactory(config);
|
|
58
|
+
} catch {
|
|
59
|
+
// Factory creation failed, continue without adapters
|
|
60
|
+
adapterFactory = null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
config,
|
|
66
|
+
adapterFactory,
|
|
67
|
+
projectRoot,
|
|
68
|
+
|
|
69
|
+
hasIntegration(type: AdapterType): boolean {
|
|
70
|
+
if (!adapterFactory) return false;
|
|
71
|
+
return adapterFactory.hasIntegration(type);
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
async getAdapterSafe<T>(type: AdapterType): Promise<T | null> {
|
|
75
|
+
if (!adapterFactory) return null;
|
|
76
|
+
if (!adapterFactory.hasIntegration(type)) return null;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const adapter = await adapterFactory.getAdapter(type);
|
|
80
|
+
return adapter as T;
|
|
81
|
+
} catch {
|
|
82
|
+
// Adapter creation failed, return null for graceful degradation
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Cleanup resources in a command context
|
|
91
|
+
*/
|
|
92
|
+
export async function cleanupContext(context: CommandContext): Promise<void> {
|
|
93
|
+
if (context.adapterFactory) {
|
|
94
|
+
await context.adapterFactory.disconnectAll();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Execute a function with automatic context cleanup
|
|
100
|
+
*/
|
|
101
|
+
export async function withContext<T>(
|
|
102
|
+
options: CreateContextOptions,
|
|
103
|
+
fn: (context: CommandContext) => Promise<T>
|
|
104
|
+
): Promise<T> {
|
|
105
|
+
const context = createCommandContext(options);
|
|
106
|
+
try {
|
|
107
|
+
return await fn(context);
|
|
108
|
+
} finally {
|
|
109
|
+
await cleanupContext(context);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Framework
|
|
3
|
+
*
|
|
4
|
+
* Registration, discovery, and execution of CoreAI commands.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export type {
|
|
9
|
+
CommandCategory,
|
|
10
|
+
IntegrationDependency,
|
|
11
|
+
CommandMetadata,
|
|
12
|
+
CommandContext,
|
|
13
|
+
BaseCommandOptions,
|
|
14
|
+
CommandResult,
|
|
15
|
+
CommandHandler,
|
|
16
|
+
CommandDefinition,
|
|
17
|
+
CommandOptionDefinition,
|
|
18
|
+
CommandArgumentDefinition,
|
|
19
|
+
MarkdownCommand,
|
|
20
|
+
RegistryEntry,
|
|
21
|
+
CommandLoaderOptions,
|
|
22
|
+
CommandLoadResult,
|
|
23
|
+
} from './types.js';
|
|
24
|
+
|
|
25
|
+
// Context
|
|
26
|
+
export {
|
|
27
|
+
createCommandContext,
|
|
28
|
+
cleanupContext,
|
|
29
|
+
withContext,
|
|
30
|
+
type CreateContextOptions,
|
|
31
|
+
} from './context.js';
|
|
32
|
+
|
|
33
|
+
// Registry
|
|
34
|
+
export {
|
|
35
|
+
CommandRegistry,
|
|
36
|
+
createCommandRegistry,
|
|
37
|
+
getGlobalRegistry,
|
|
38
|
+
resetGlobalRegistry,
|
|
39
|
+
} from './registry.js';
|
|
40
|
+
|
|
41
|
+
// Loader
|
|
42
|
+
export {
|
|
43
|
+
loadCommandFromFile,
|
|
44
|
+
loadCommandsFromDirectory,
|
|
45
|
+
loadCoreAICommands,
|
|
46
|
+
loadAllCommands,
|
|
47
|
+
} from './loader.js';
|
|
48
|
+
|
|
49
|
+
// Runner
|
|
50
|
+
export {
|
|
51
|
+
runCommand,
|
|
52
|
+
executeWithDegradation,
|
|
53
|
+
createDegradingHandler,
|
|
54
|
+
StepTracker,
|
|
55
|
+
type RunCommandOptions,
|
|
56
|
+
} from './runner.js';
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Loader Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { promises as fs } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { tmpdir } from 'os';
|
|
8
|
+
import { loadCommandFromFile, loadCommandsFromDirectory, loadAllCommands } from './loader.js';
|
|
9
|
+
|
|
10
|
+
describe('Command Loader', () => {
|
|
11
|
+
let testDir: string;
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
testDir = join(
|
|
15
|
+
tmpdir(),
|
|
16
|
+
`cmd-loader-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
17
|
+
);
|
|
18
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(async () => {
|
|
22
|
+
try {
|
|
23
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
24
|
+
} catch {
|
|
25
|
+
// Ignore cleanup errors
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('loadCommandFromFile', () => {
|
|
30
|
+
it('should load command with frontmatter', async () => {
|
|
31
|
+
const cmdPath = join(testDir, 'test-command.md');
|
|
32
|
+
await fs.writeFile(
|
|
33
|
+
cmdPath,
|
|
34
|
+
`---
|
|
35
|
+
description: A test command
|
|
36
|
+
argument-hint: <task>
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
# Test Command
|
|
40
|
+
|
|
41
|
+
Instructions for the command.
|
|
42
|
+
`
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const cmd = await loadCommandFromFile(cmdPath, testDir);
|
|
46
|
+
|
|
47
|
+
expect(cmd.metadata.name).toBe('test-command');
|
|
48
|
+
expect(cmd.metadata.description).toBe('A test command');
|
|
49
|
+
expect(cmd.metadata.argumentHint).toBe('<task>');
|
|
50
|
+
expect(cmd.sections.title).toBe('Test Command');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should load command without frontmatter', async () => {
|
|
54
|
+
const cmdPath = join(testDir, 'simple.md');
|
|
55
|
+
await fs.writeFile(cmdPath, '# Simple Command\n\nJust instructions.');
|
|
56
|
+
|
|
57
|
+
const cmd = await loadCommandFromFile(cmdPath, testDir);
|
|
58
|
+
|
|
59
|
+
expect(cmd.metadata.name).toBe('simple');
|
|
60
|
+
expect(cmd.metadata.description).toBe('Simple Command');
|
|
61
|
+
expect(cmd.content).toContain('Just instructions');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should parse dependencies from frontmatter', async () => {
|
|
65
|
+
const cmdPath = join(testDir, 'with-deps.md');
|
|
66
|
+
await fs.writeFile(
|
|
67
|
+
cmdPath,
|
|
68
|
+
`---
|
|
69
|
+
description: Command with deps
|
|
70
|
+
requires: [jira, github]
|
|
71
|
+
optional: [confluence]
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
# With Dependencies
|
|
75
|
+
`
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const cmd = await loadCommandFromFile(cmdPath, testDir);
|
|
79
|
+
|
|
80
|
+
expect(cmd.metadata.dependencies).toHaveLength(3);
|
|
81
|
+
|
|
82
|
+
const jiraDep = cmd.metadata.dependencies.find((d) => d.type === 'issue_tracker');
|
|
83
|
+
expect(jiraDep?.required).toBe(true);
|
|
84
|
+
|
|
85
|
+
const gitDep = cmd.metadata.dependencies.find((d) => d.type === 'git');
|
|
86
|
+
expect(gitDep?.required).toBe(true);
|
|
87
|
+
|
|
88
|
+
const docsDep = cmd.metadata.dependencies.find((d) => d.type === 'documentation');
|
|
89
|
+
expect(docsDep?.required).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should extract sections from markdown', async () => {
|
|
93
|
+
const cmdPath = join(testDir, 'sectioned.md');
|
|
94
|
+
await fs.writeFile(
|
|
95
|
+
cmdPath,
|
|
96
|
+
`---
|
|
97
|
+
description: Sectioned command
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
# Sectioned Command
|
|
101
|
+
|
|
102
|
+
## Instructions
|
|
103
|
+
|
|
104
|
+
Do this thing.
|
|
105
|
+
|
|
106
|
+
## Fallbacks
|
|
107
|
+
|
|
108
|
+
If integration missing, do this instead.
|
|
109
|
+
|
|
110
|
+
## Output Format
|
|
111
|
+
|
|
112
|
+
Return JSON.
|
|
113
|
+
`
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const cmd = await loadCommandFromFile(cmdPath, testDir);
|
|
117
|
+
|
|
118
|
+
expect(cmd.sections.title).toBe('Sectioned Command');
|
|
119
|
+
expect(cmd.sections.instructions).toContain('Do this thing');
|
|
120
|
+
expect(cmd.sections.fallbacks).toContain('integration missing');
|
|
121
|
+
expect(cmd.sections.outputFormat).toContain('JSON');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should derive category from path', async () => {
|
|
125
|
+
// Create core subdirectory
|
|
126
|
+
const coreDir = join(testDir, 'core');
|
|
127
|
+
await fs.mkdir(coreDir, { recursive: true });
|
|
128
|
+
const corePath = join(coreDir, 'core-cmd.md');
|
|
129
|
+
await fs.writeFile(corePath, '# Core Command');
|
|
130
|
+
|
|
131
|
+
// Create optional subdirectory
|
|
132
|
+
const optDir = join(testDir, 'optional');
|
|
133
|
+
await fs.mkdir(optDir, { recursive: true });
|
|
134
|
+
const optPath = join(optDir, 'opt-cmd.md');
|
|
135
|
+
await fs.writeFile(optPath, '# Optional Command');
|
|
136
|
+
|
|
137
|
+
const coreCmd = await loadCommandFromFile(corePath, testDir);
|
|
138
|
+
const optCmd = await loadCommandFromFile(optPath, testDir);
|
|
139
|
+
|
|
140
|
+
expect(coreCmd.metadata.category).toBe('core');
|
|
141
|
+
expect(optCmd.metadata.category).toBe('optional');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should use category override', async () => {
|
|
145
|
+
const cmdPath = join(testDir, 'cmd.md');
|
|
146
|
+
await fs.writeFile(cmdPath, '# Command');
|
|
147
|
+
|
|
148
|
+
const cmd = await loadCommandFromFile(cmdPath, testDir, 'custom');
|
|
149
|
+
|
|
150
|
+
expect(cmd.metadata.category).toBe('custom');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('loadCommandsFromDirectory', () => {
|
|
155
|
+
it('should load all markdown files', async () => {
|
|
156
|
+
await fs.writeFile(join(testDir, 'cmd1.md'), '# Command 1');
|
|
157
|
+
await fs.writeFile(join(testDir, 'cmd2.md'), '# Command 2');
|
|
158
|
+
await fs.writeFile(join(testDir, 'not-md.txt'), 'Not a command');
|
|
159
|
+
|
|
160
|
+
const result = await loadCommandsFromDirectory({
|
|
161
|
+
directory: testDir,
|
|
162
|
+
category: 'core',
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(result.loaded).toHaveLength(2);
|
|
166
|
+
expect(result.errors).toHaveLength(0);
|
|
167
|
+
expect(result.loaded.map((c) => c.metadata.name)).toContain('cmd1');
|
|
168
|
+
expect(result.loaded.map((c) => c.metadata.name)).toContain('cmd2');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should load recursively by default', async () => {
|
|
172
|
+
await fs.writeFile(join(testDir, 'root.md'), '# Root');
|
|
173
|
+
|
|
174
|
+
const subDir = join(testDir, 'sub');
|
|
175
|
+
await fs.mkdir(subDir, { recursive: true });
|
|
176
|
+
await fs.writeFile(join(subDir, 'nested.md'), '# Nested');
|
|
177
|
+
|
|
178
|
+
const result = await loadCommandsFromDirectory({
|
|
179
|
+
directory: testDir,
|
|
180
|
+
category: 'core',
|
|
181
|
+
recursive: true,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(result.loaded).toHaveLength(2);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should not load recursively when disabled', async () => {
|
|
188
|
+
await fs.writeFile(join(testDir, 'root.md'), '# Root');
|
|
189
|
+
|
|
190
|
+
const subDir = join(testDir, 'sub');
|
|
191
|
+
await fs.mkdir(subDir, { recursive: true });
|
|
192
|
+
await fs.writeFile(join(subDir, 'nested.md'), '# Nested');
|
|
193
|
+
|
|
194
|
+
const result = await loadCommandsFromDirectory({
|
|
195
|
+
directory: testDir,
|
|
196
|
+
category: 'core',
|
|
197
|
+
recursive: false,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(result.loaded).toHaveLength(1);
|
|
201
|
+
expect(result.loaded[0].metadata.name).toBe('root');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should apply filter', async () => {
|
|
205
|
+
await fs.writeFile(join(testDir, 'include-me.md'), '# Include');
|
|
206
|
+
await fs.writeFile(join(testDir, 'exclude-me.md'), '# Exclude');
|
|
207
|
+
|
|
208
|
+
const result = await loadCommandsFromDirectory({
|
|
209
|
+
directory: testDir,
|
|
210
|
+
category: 'core',
|
|
211
|
+
filter: (metadata) => metadata.name.startsWith('include'),
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
expect(result.loaded).toHaveLength(1);
|
|
215
|
+
expect(result.loaded[0].metadata.name).toBe('include-me');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should handle non-existent directory', async () => {
|
|
219
|
+
const result = await loadCommandsFromDirectory({
|
|
220
|
+
directory: join(testDir, 'non-existent'),
|
|
221
|
+
category: 'core',
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
expect(result.loaded).toHaveLength(0);
|
|
225
|
+
expect(result.errors).toHaveLength(0);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should handle directories with .md extension gracefully', async () => {
|
|
229
|
+
// Create a valid file
|
|
230
|
+
await fs.writeFile(join(testDir, 'valid.md'), '# Valid');
|
|
231
|
+
|
|
232
|
+
// Create a directory with .md name (should be skipped, not errored)
|
|
233
|
+
await fs.mkdir(join(testDir, 'not-a-file.md'), { recursive: true });
|
|
234
|
+
|
|
235
|
+
const result = await loadCommandsFromDirectory({
|
|
236
|
+
directory: testDir,
|
|
237
|
+
category: 'core',
|
|
238
|
+
recursive: false,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Only the valid file should be loaded, directory should be ignored
|
|
242
|
+
expect(result.loaded).toHaveLength(1);
|
|
243
|
+
expect(result.loaded[0].metadata.name).toBe('valid');
|
|
244
|
+
expect(result.errors).toHaveLength(0);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('loadAllCommands', () => {
|
|
249
|
+
it('should load from multiple directories', async () => {
|
|
250
|
+
const coreDir = join(testDir, 'core');
|
|
251
|
+
const customDir = join(testDir, 'custom');
|
|
252
|
+
await fs.mkdir(coreDir, { recursive: true });
|
|
253
|
+
await fs.mkdir(customDir, { recursive: true });
|
|
254
|
+
|
|
255
|
+
await fs.writeFile(join(coreDir, 'core-cmd.md'), '# Core');
|
|
256
|
+
await fs.writeFile(join(customDir, 'custom-cmd.md'), '# Custom');
|
|
257
|
+
|
|
258
|
+
const result = await loadAllCommands(coreDir, customDir);
|
|
259
|
+
|
|
260
|
+
expect(result.loaded).toHaveLength(2);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should work without custom directory', async () => {
|
|
264
|
+
const coreDir = join(testDir, 'core');
|
|
265
|
+
await fs.mkdir(coreDir, { recursive: true });
|
|
266
|
+
await fs.writeFile(join(coreDir, 'cmd.md'), '# Command');
|
|
267
|
+
|
|
268
|
+
const result = await loadAllCommands(coreDir);
|
|
269
|
+
|
|
270
|
+
expect(result.loaded).toHaveLength(1);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|