@codemcp/ade-core 0.0.2
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/.prettierignore +1 -0
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-format.log +6 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +21 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/LICENSE +21 -0
- package/dist/catalog/facets/architecture.d.ts +2 -0
- package/dist/catalog/facets/architecture.js +424 -0
- package/dist/catalog/facets/backpressure.d.ts +2 -0
- package/dist/catalog/facets/backpressure.js +123 -0
- package/dist/catalog/facets/practices.d.ts +2 -0
- package/dist/catalog/facets/practices.js +163 -0
- package/dist/catalog/facets/process.d.ts +2 -0
- package/dist/catalog/facets/process.js +47 -0
- package/dist/catalog/index.d.ts +14 -0
- package/dist/catalog/index.js +71 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +29 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +6 -0
- package/dist/registry.d.ts +7 -0
- package/dist/registry.js +41 -0
- package/dist/resolver.d.ts +7 -0
- package/dist/resolver.js +142 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.js +2 -0
- package/dist/writers/git-hooks.d.ts +2 -0
- package/dist/writers/git-hooks.js +7 -0
- package/dist/writers/instruction.d.ts +2 -0
- package/dist/writers/instruction.js +6 -0
- package/dist/writers/knowledge.d.ts +2 -0
- package/dist/writers/knowledge.js +9 -0
- package/dist/writers/setup-note.d.ts +2 -0
- package/dist/writers/setup-note.js +7 -0
- package/dist/writers/skills.d.ts +2 -0
- package/dist/writers/skills.js +7 -0
- package/dist/writers/workflows.d.ts +2 -0
- package/dist/writers/workflows.js +16 -0
- package/eslint.config.mjs +40 -0
- package/nodemon.json +7 -0
- package/package.json +34 -0
- package/src/catalog/catalog.spec.ts +531 -0
- package/src/catalog/facets/architecture.ts +438 -0
- package/src/catalog/facets/backpressure.ts +143 -0
- package/src/catalog/facets/practices.ts +173 -0
- package/src/catalog/facets/process.ts +50 -0
- package/src/catalog/index.ts +86 -0
- package/src/config.spec.ts +165 -0
- package/src/config.ts +39 -0
- package/src/index.ts +49 -0
- package/src/registry.spec.ts +144 -0
- package/src/registry.ts +68 -0
- package/src/resolver.spec.ts +581 -0
- package/src/resolver.ts +170 -0
- package/src/types.ts +151 -0
- package/src/writers/git-hooks.ts +9 -0
- package/src/writers/instruction.spec.ts +42 -0
- package/src/writers/instruction.ts +8 -0
- package/src/writers/knowledge.spec.ts +26 -0
- package/src/writers/knowledge.ts +15 -0
- package/src/writers/setup-note.ts +9 -0
- package/src/writers/skills.spec.ts +109 -0
- package/src/writers/skills.ts +9 -0
- package/src/writers/workflows.spec.ts +72 -0
- package/src/writers/workflows.ts +26 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +7 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsconfig.vitest.json +7 -0
- package/vitest.config.ts +5 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
export const practicesFacet = {
|
|
2
|
+
id: "practices",
|
|
3
|
+
label: "Practices",
|
|
4
|
+
description: "Composable development practices — mix and match regardless of stack",
|
|
5
|
+
required: false,
|
|
6
|
+
multiSelect: true,
|
|
7
|
+
options: [
|
|
8
|
+
{
|
|
9
|
+
id: "conventional-commits",
|
|
10
|
+
label: "Conventional Commits",
|
|
11
|
+
description: "Structured commit messages following the Conventional Commits specification",
|
|
12
|
+
recipe: [
|
|
13
|
+
{
|
|
14
|
+
writer: "skills",
|
|
15
|
+
config: {
|
|
16
|
+
skills: [
|
|
17
|
+
{
|
|
18
|
+
name: "conventional-commits",
|
|
19
|
+
description: "Conventional Commits specification for structured commit messages",
|
|
20
|
+
body: [
|
|
21
|
+
"# Conventional Commits",
|
|
22
|
+
"",
|
|
23
|
+
"## Format",
|
|
24
|
+
"```",
|
|
25
|
+
"<type>[optional scope]: <description>",
|
|
26
|
+
"",
|
|
27
|
+
"[optional body]",
|
|
28
|
+
"",
|
|
29
|
+
"[optional footer(s)]",
|
|
30
|
+
"```",
|
|
31
|
+
"",
|
|
32
|
+
"## Types",
|
|
33
|
+
"- `feat`: A new feature (correlates with MINOR in SemVer)",
|
|
34
|
+
"- `fix`: A bug fix (correlates with PATCH in SemVer)",
|
|
35
|
+
"- `docs`: Documentation only changes",
|
|
36
|
+
"- `style`: Changes that do not affect the meaning of the code",
|
|
37
|
+
"- `refactor`: A code change that neither fixes a bug nor adds a feature",
|
|
38
|
+
"- `perf`: A code change that improves performance",
|
|
39
|
+
"- `test`: Adding missing tests or correcting existing tests",
|
|
40
|
+
"- `chore`: Changes to the build process or auxiliary tools",
|
|
41
|
+
"",
|
|
42
|
+
"## Rules",
|
|
43
|
+
"- Subject line must not exceed 72 characters",
|
|
44
|
+
'- Use imperative mood in the subject line ("add" not "added")',
|
|
45
|
+
"- Do not end the subject line with a period",
|
|
46
|
+
"- Separate subject from body with a blank line",
|
|
47
|
+
"- Use the body to explain what and why, not how",
|
|
48
|
+
"- `BREAKING CHANGE:` footer or `!` after type/scope for breaking changes"
|
|
49
|
+
].join("\n")
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
docsets: [
|
|
56
|
+
{
|
|
57
|
+
id: "conventional-commits-spec",
|
|
58
|
+
label: "Conventional Commits Spec",
|
|
59
|
+
origin: "https://github.com/conventional-commits/conventionalcommits.org.git",
|
|
60
|
+
description: "The Conventional Commits specification"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "tdd-london",
|
|
66
|
+
label: "TDD (London Style)",
|
|
67
|
+
description: "Test-Driven Development using the London school (mockist) approach",
|
|
68
|
+
recipe: [
|
|
69
|
+
{
|
|
70
|
+
writer: "skills",
|
|
71
|
+
config: {
|
|
72
|
+
skills: [
|
|
73
|
+
{
|
|
74
|
+
name: "tdd-london",
|
|
75
|
+
description: "London-school TDD methodology with outside-in design",
|
|
76
|
+
body: [
|
|
77
|
+
"# TDD — London Style (Mockist)",
|
|
78
|
+
"",
|
|
79
|
+
"## Core Cycle",
|
|
80
|
+
"1. **Red** — Write a failing test for the next behavior",
|
|
81
|
+
"2. **Green** — Write the minimum code to make the test pass",
|
|
82
|
+
"3. **Refactor** — Improve the code while keeping tests green",
|
|
83
|
+
"",
|
|
84
|
+
"## London School Principles",
|
|
85
|
+
"- Work **outside-in**: start from the outermost layer (API / UI) and drive inward",
|
|
86
|
+
"- **Mock collaborators**: each unit test isolates the unit under test by mocking its direct dependencies",
|
|
87
|
+
"- Discover interfaces through tests — let the test define the collaborator contract before implementing it",
|
|
88
|
+
"- Prefer **role-based interfaces** over concrete classes",
|
|
89
|
+
"",
|
|
90
|
+
"## Test Structure",
|
|
91
|
+
"- **Arrange**: Set up mocks and the unit under test",
|
|
92
|
+
"- **Act**: Call the method being tested",
|
|
93
|
+
"- **Assert**: Verify the unit's output and interactions with mocks",
|
|
94
|
+
"",
|
|
95
|
+
"## Guidelines",
|
|
96
|
+
"- One logical assertion per test",
|
|
97
|
+
'- Test names describe behavior, not methods (e.g. "notifies user when order is placed")',
|
|
98
|
+
"- Only mock types you own — wrap third-party APIs in adapters and mock those",
|
|
99
|
+
"- Use the test doubles: stubs for queries, mocks for commands",
|
|
100
|
+
"- Do not test implementation details — test observable behavior",
|
|
101
|
+
"- Refactor step is mandatory, not optional"
|
|
102
|
+
].join("\n")
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "adr-nygard",
|
|
111
|
+
label: "ADR (Nygard)",
|
|
112
|
+
description: "Architecture Decision Records following Michael Nygard's template",
|
|
113
|
+
recipe: [
|
|
114
|
+
{
|
|
115
|
+
writer: "skills",
|
|
116
|
+
config: {
|
|
117
|
+
skills: [
|
|
118
|
+
{
|
|
119
|
+
name: "adr-nygard",
|
|
120
|
+
description: "Architecture Decision Records following Nygard's lightweight template",
|
|
121
|
+
body: [
|
|
122
|
+
"# Architecture Decision Records (Nygard)",
|
|
123
|
+
"",
|
|
124
|
+
"## When to Write an ADR",
|
|
125
|
+
"- When making a significant architectural decision",
|
|
126
|
+
"- When choosing between multiple viable options",
|
|
127
|
+
"- When the decision will be hard to reverse",
|
|
128
|
+
'- When future developers will ask "why did we do this?"',
|
|
129
|
+
"",
|
|
130
|
+
"## Template",
|
|
131
|
+
"Store ADRs in `docs/adr/` as numbered markdown files: `NNNN-title-with-dashes.md`",
|
|
132
|
+
"",
|
|
133
|
+
"```markdown",
|
|
134
|
+
"# N. Title",
|
|
135
|
+
"",
|
|
136
|
+
"## Status",
|
|
137
|
+
"Proposed | Accepted | Deprecated | Superseded by [ADR-NNNN]",
|
|
138
|
+
"",
|
|
139
|
+
"## Context",
|
|
140
|
+
"What is the issue that we're seeing that is motivating this decision or change?",
|
|
141
|
+
"",
|
|
142
|
+
"## Decision",
|
|
143
|
+
"What is the change that we're proposing and/or doing?",
|
|
144
|
+
"",
|
|
145
|
+
"## Consequences",
|
|
146
|
+
"What becomes easier or more difficult to do because of this change?",
|
|
147
|
+
"```",
|
|
148
|
+
"",
|
|
149
|
+
"## Rules",
|
|
150
|
+
"- ADRs are immutable once accepted — supersede, don't edit",
|
|
151
|
+
"- Keep context focused on forces at play at the time of the decision",
|
|
152
|
+
"- Write consequences as both positive and negative impacts",
|
|
153
|
+
"- Number sequentially, never reuse numbers",
|
|
154
|
+
'- Title should be a short noun phrase (e.g. "Use PostgreSQL for persistence")'
|
|
155
|
+
].join("\n")
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const processFacet = {
|
|
2
|
+
id: "process",
|
|
3
|
+
label: "Process",
|
|
4
|
+
description: "How your AI agent receives and executes tasks",
|
|
5
|
+
required: true,
|
|
6
|
+
options: [
|
|
7
|
+
{
|
|
8
|
+
id: "codemcp-workflows",
|
|
9
|
+
label: "CodeMCP Workflows",
|
|
10
|
+
description: "Use @codemcp/workflows to drive agent tasks with structured engineering workflows",
|
|
11
|
+
recipe: [
|
|
12
|
+
{
|
|
13
|
+
writer: "workflows",
|
|
14
|
+
config: {
|
|
15
|
+
package: "@codemcp/workflows-server@latest",
|
|
16
|
+
ref: "workflows"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
writer: "instruction",
|
|
21
|
+
config: {
|
|
22
|
+
text: [
|
|
23
|
+
"You are an AI assistant that helps users develop software features using the workflows server.",
|
|
24
|
+
"IMPORTANT: Call whats_next() after each user message to get phase-specific instructions and maintain the development workflow.",
|
|
25
|
+
'Each tool call returns a JSON response with an "instructions" field. Follow these instructions immediately after you receive them.',
|
|
26
|
+
"Use the development plan which you will retrieve via whats_next() to record important insights and decisions as per the structure of the plan.",
|
|
27
|
+
"Do not use your own task management tools."
|
|
28
|
+
].join("\n")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "native-agents-md",
|
|
35
|
+
label: "Native agents.md",
|
|
36
|
+
description: "Use a plain agents.md instruction file",
|
|
37
|
+
recipe: [
|
|
38
|
+
{
|
|
39
|
+
writer: "instruction",
|
|
40
|
+
config: {
|
|
41
|
+
text: "Read AGENTS.md for project conventions and task instructions."
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Catalog, Facet, Option } from "../types.js";
|
|
2
|
+
export declare function getDefaultCatalog(): Catalog;
|
|
3
|
+
export declare function getFacet(catalog: Catalog, id: string): Facet | undefined;
|
|
4
|
+
export declare function getOption(facet: Facet, id: string): Option | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Topologically sort facets so that dependency facets come before dependent ones.
|
|
7
|
+
* Uses Kahn's algorithm. Throws if a cycle is detected.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sortFacets(catalog: Catalog): Facet[];
|
|
10
|
+
/**
|
|
11
|
+
* Returns only the options of a facet that are visible given the current choices.
|
|
12
|
+
* Options without an `available()` function are always visible.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getVisibleOptions(facet: Facet, choices: Record<string, string | string[]>, catalog: Catalog): Option[];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { processFacet } from "./facets/process.js";
|
|
2
|
+
import { architectureFacet } from "./facets/architecture.js";
|
|
3
|
+
import { practicesFacet } from "./facets/practices.js";
|
|
4
|
+
import { backpressureFacet } from "./facets/backpressure.js";
|
|
5
|
+
export function getDefaultCatalog() {
|
|
6
|
+
return {
|
|
7
|
+
facets: [processFacet, architectureFacet, practicesFacet, backpressureFacet]
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function getFacet(catalog, id) {
|
|
11
|
+
return catalog.facets.find((f) => f.id === id);
|
|
12
|
+
}
|
|
13
|
+
export function getOption(facet, id) {
|
|
14
|
+
return facet.options.find((o) => o.id === id);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Topologically sort facets so that dependency facets come before dependent ones.
|
|
18
|
+
* Uses Kahn's algorithm. Throws if a cycle is detected.
|
|
19
|
+
*/
|
|
20
|
+
export function sortFacets(catalog) {
|
|
21
|
+
const facets = catalog.facets;
|
|
22
|
+
const idToFacet = new Map(facets.map((f) => [f.id, f]));
|
|
23
|
+
// Build in-degree and adjacency (dependsOn edge: dep → dependent)
|
|
24
|
+
const inDegree = new Map(facets.map((f) => [f.id, 0]));
|
|
25
|
+
const dependents = new Map(facets.map((f) => [f.id, []]));
|
|
26
|
+
for (const facet of facets) {
|
|
27
|
+
for (const dep of facet.dependsOn ?? []) {
|
|
28
|
+
if (idToFacet.has(dep)) {
|
|
29
|
+
inDegree.set(facet.id, (inDegree.get(facet.id) ?? 0) + 1);
|
|
30
|
+
dependents.get(dep).push(facet.id);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const queue = facets.filter((f) => (inDegree.get(f.id) ?? 0) === 0);
|
|
35
|
+
const result = [];
|
|
36
|
+
while (queue.length > 0) {
|
|
37
|
+
const facet = queue.shift();
|
|
38
|
+
result.push(facet);
|
|
39
|
+
for (const depId of dependents.get(facet.id) ?? []) {
|
|
40
|
+
const newDegree = (inDegree.get(depId) ?? 0) - 1;
|
|
41
|
+
inDegree.set(depId, newDegree);
|
|
42
|
+
if (newDegree === 0) {
|
|
43
|
+
queue.push(idToFacet.get(depId));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (result.length !== facets.length) {
|
|
48
|
+
throw new Error("Cycle detected in facet dependsOn graph");
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Returns only the options of a facet that are visible given the current choices.
|
|
54
|
+
* Options without an `available()` function are always visible.
|
|
55
|
+
*/
|
|
56
|
+
export function getVisibleOptions(facet, choices, catalog) {
|
|
57
|
+
return facet.options.filter((option) => {
|
|
58
|
+
if (!option.available)
|
|
59
|
+
return true;
|
|
60
|
+
const deps = {};
|
|
61
|
+
for (const depFacetId of facet.dependsOn ?? []) {
|
|
62
|
+
const depFacet = getFacet(catalog, depFacetId);
|
|
63
|
+
const choiceVal = choices[depFacetId];
|
|
64
|
+
deps[depFacetId] =
|
|
65
|
+
depFacet && typeof choiceVal === "string"
|
|
66
|
+
? getOption(depFacet, choiceVal)
|
|
67
|
+
: undefined;
|
|
68
|
+
}
|
|
69
|
+
return option.available(deps);
|
|
70
|
+
});
|
|
71
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { UserConfig, LockFile } from "./types.js";
|
|
2
|
+
export declare function readUserConfig(dir: string): Promise<UserConfig | null>;
|
|
3
|
+
export declare function writeUserConfig(dir: string, config: UserConfig): Promise<void>;
|
|
4
|
+
export declare function readLockFile(dir: string): Promise<LockFile | null>;
|
|
5
|
+
export declare function writeLockFile(dir: string, lock: LockFile): Promise<void>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parse, stringify } from "yaml";
|
|
4
|
+
const CONFIG_FILE = "config.yaml";
|
|
5
|
+
const LOCK_FILE = "config.lock.yaml";
|
|
6
|
+
export async function readUserConfig(dir) {
|
|
7
|
+
try {
|
|
8
|
+
const raw = await readFile(join(dir, CONFIG_FILE), "utf-8");
|
|
9
|
+
return parse(raw);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function writeUserConfig(dir, config) {
|
|
16
|
+
await writeFile(join(dir, CONFIG_FILE), stringify(config), "utf-8");
|
|
17
|
+
}
|
|
18
|
+
export async function readLockFile(dir) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = await readFile(join(dir, LOCK_FILE), "utf-8");
|
|
21
|
+
return parse(raw);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function writeLockFile(dir, lock) {
|
|
28
|
+
await writeFile(join(dir, LOCK_FILE), stringify(lock), "utf-8");
|
|
29
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { type Catalog, type Facet, type Option, type Provision, type DocsetDef } from "./types.js";
|
|
2
|
+
export { type LogicalConfig, type McpServerEntry, type CliAction, type KnowledgeSource, type SkillDefinition, type InlineSkill, type ExternalSkill, type GitHook } from "./types.js";
|
|
3
|
+
export { type ResolutionContext, type ResolvedFacet } from "./types.js";
|
|
4
|
+
export { type UserConfig, type LockFile } from "./types.js";
|
|
5
|
+
export { type ProvisionWriter } from "./types.js";
|
|
6
|
+
export { readUserConfig, writeUserConfig, readLockFile, writeLockFile } from "./config.js";
|
|
7
|
+
export { type ProvisionWriterDef, type AgentWriterDef, type WriterRegistry } from "./types.js";
|
|
8
|
+
export { createRegistry, registerProvisionWriter, getProvisionWriter, registerAgentWriter, getAgentWriter, createDefaultRegistry } from "./registry.js";
|
|
9
|
+
export { resolve, collectDocsets } from "./resolver.js";
|
|
10
|
+
export { getDefaultCatalog, getFacet, getOption, sortFacets, getVisibleOptions } from "./catalog/index.js";
|
|
11
|
+
export { skillsWriter } from "./writers/skills.js";
|
|
12
|
+
export { knowledgeWriter } from "./writers/knowledge.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { readUserConfig, writeUserConfig, readLockFile, writeLockFile } from "./config.js";
|
|
2
|
+
export { createRegistry, registerProvisionWriter, getProvisionWriter, registerAgentWriter, getAgentWriter, createDefaultRegistry } from "./registry.js";
|
|
3
|
+
export { resolve, collectDocsets } from "./resolver.js";
|
|
4
|
+
export { getDefaultCatalog, getFacet, getOption, sortFacets, getVisibleOptions } from "./catalog/index.js";
|
|
5
|
+
export { skillsWriter } from "./writers/skills.js";
|
|
6
|
+
export { knowledgeWriter } from "./writers/knowledge.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WriterRegistry, ProvisionWriterDef, AgentWriterDef } from "./types.js";
|
|
2
|
+
export declare function createRegistry(): WriterRegistry;
|
|
3
|
+
export declare function registerProvisionWriter(registry: WriterRegistry, writer: ProvisionWriterDef): void;
|
|
4
|
+
export declare function getProvisionWriter(registry: WriterRegistry, id: string): ProvisionWriterDef | undefined;
|
|
5
|
+
export declare function registerAgentWriter(registry: WriterRegistry, agent: AgentWriterDef): void;
|
|
6
|
+
export declare function getAgentWriter(registry: WriterRegistry, id: string): AgentWriterDef | undefined;
|
|
7
|
+
export declare function createDefaultRegistry(): WriterRegistry;
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { instructionWriter } from "./writers/instruction.js";
|
|
2
|
+
import { workflowsWriter } from "./writers/workflows.js";
|
|
3
|
+
import { skillsWriter } from "./writers/skills.js";
|
|
4
|
+
import { knowledgeWriter } from "./writers/knowledge.js";
|
|
5
|
+
import { gitHooksWriter } from "./writers/git-hooks.js";
|
|
6
|
+
import { setupNoteWriter } from "./writers/setup-note.js";
|
|
7
|
+
export function createRegistry() {
|
|
8
|
+
return {
|
|
9
|
+
provisions: new Map(),
|
|
10
|
+
agents: new Map()
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function registerProvisionWriter(registry, writer) {
|
|
14
|
+
registry.provisions.set(writer.id, writer);
|
|
15
|
+
}
|
|
16
|
+
export function getProvisionWriter(registry, id) {
|
|
17
|
+
return registry.provisions.get(id);
|
|
18
|
+
}
|
|
19
|
+
export function registerAgentWriter(registry, agent) {
|
|
20
|
+
registry.agents.set(agent.id, agent);
|
|
21
|
+
}
|
|
22
|
+
export function getAgentWriter(registry, id) {
|
|
23
|
+
return registry.agents.get(id);
|
|
24
|
+
}
|
|
25
|
+
export function createDefaultRegistry() {
|
|
26
|
+
const registry = createRegistry();
|
|
27
|
+
registerProvisionWriter(registry, instructionWriter);
|
|
28
|
+
registerProvisionWriter(registry, workflowsWriter);
|
|
29
|
+
registerProvisionWriter(registry, skillsWriter);
|
|
30
|
+
registerProvisionWriter(registry, knowledgeWriter);
|
|
31
|
+
registerProvisionWriter(registry, gitHooksWriter);
|
|
32
|
+
registerProvisionWriter(registry, setupNoteWriter);
|
|
33
|
+
// Stub writers for types not yet implemented
|
|
34
|
+
for (const id of ["mcp-server", "installable"]) {
|
|
35
|
+
registerProvisionWriter(registry, {
|
|
36
|
+
id,
|
|
37
|
+
write: async () => ({})
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return registry;
|
|
41
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { UserConfig, Catalog, WriterRegistry, LogicalConfig, DocsetDef } from "./types.js";
|
|
2
|
+
export declare function resolve(userConfig: UserConfig, catalog: Catalog, registry: WriterRegistry): Promise<LogicalConfig>;
|
|
3
|
+
/**
|
|
4
|
+
* Collect all unique docsets implied by the given choices.
|
|
5
|
+
* Used by the TUI to present docsets for confirmation before resolution.
|
|
6
|
+
*/
|
|
7
|
+
export declare function collectDocsets(choices: Record<string, string | string[]>, catalog: Catalog): DocsetDef[];
|
package/dist/resolver.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { getFacet, getOption } from "./catalog/index.js";
|
|
2
|
+
import { getProvisionWriter } from "./registry.js";
|
|
3
|
+
export async function resolve(userConfig, catalog, registry) {
|
|
4
|
+
const result = {
|
|
5
|
+
mcp_servers: [],
|
|
6
|
+
instructions: [],
|
|
7
|
+
cli_actions: [],
|
|
8
|
+
knowledge_sources: [],
|
|
9
|
+
skills: [],
|
|
10
|
+
git_hooks: [],
|
|
11
|
+
setup_notes: []
|
|
12
|
+
};
|
|
13
|
+
const context = { resolved: {} };
|
|
14
|
+
for (const [facetId, optionId] of Object.entries(userConfig.choices)) {
|
|
15
|
+
const facet = getFacet(catalog, facetId);
|
|
16
|
+
if (!facet) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const selectedIds = Array.isArray(optionId) ? optionId : [optionId];
|
|
20
|
+
for (const selectedId of selectedIds) {
|
|
21
|
+
const option = getOption(facet, selectedId);
|
|
22
|
+
if (!option) {
|
|
23
|
+
throw new Error(`Unknown option "${selectedId}" for facet "${facetId}"`);
|
|
24
|
+
}
|
|
25
|
+
context.resolved[facetId] = { optionId: selectedId, option };
|
|
26
|
+
for (const provision of option.recipe) {
|
|
27
|
+
const writer = getProvisionWriter(registry, provision.writer);
|
|
28
|
+
if (!writer) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const partial = await writer.write(provision.config, context);
|
|
32
|
+
if (partial.mcp_servers) {
|
|
33
|
+
result.mcp_servers.push(...partial.mcp_servers);
|
|
34
|
+
}
|
|
35
|
+
if (partial.instructions) {
|
|
36
|
+
result.instructions.push(...partial.instructions);
|
|
37
|
+
}
|
|
38
|
+
if (partial.cli_actions) {
|
|
39
|
+
result.cli_actions.push(...partial.cli_actions);
|
|
40
|
+
}
|
|
41
|
+
if (partial.knowledge_sources) {
|
|
42
|
+
result.knowledge_sources.push(...partial.knowledge_sources);
|
|
43
|
+
}
|
|
44
|
+
if (partial.skills) {
|
|
45
|
+
result.skills.push(...partial.skills);
|
|
46
|
+
}
|
|
47
|
+
if (partial.git_hooks) {
|
|
48
|
+
result.git_hooks.push(...partial.git_hooks);
|
|
49
|
+
}
|
|
50
|
+
if (partial.setup_notes) {
|
|
51
|
+
result.setup_notes.push(...partial.setup_notes);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Collect docsets from all selected options, dedup by id, filter exclusions
|
|
57
|
+
const seenDocsets = new Map();
|
|
58
|
+
for (const [facetId, optionId] of Object.entries(userConfig.choices)) {
|
|
59
|
+
const facet = getFacet(catalog, facetId);
|
|
60
|
+
if (!facet)
|
|
61
|
+
continue;
|
|
62
|
+
const selectedIds = Array.isArray(optionId) ? optionId : [optionId];
|
|
63
|
+
for (const selectedId of selectedIds) {
|
|
64
|
+
const option = getOption(facet, selectedId);
|
|
65
|
+
if (!option?.docsets)
|
|
66
|
+
continue;
|
|
67
|
+
for (const docset of option.docsets) {
|
|
68
|
+
if (!seenDocsets.has(docset.id)) {
|
|
69
|
+
seenDocsets.set(docset.id, docset);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const excludedSet = new Set(userConfig.excluded_docsets ?? []);
|
|
75
|
+
for (const [id, docset] of seenDocsets) {
|
|
76
|
+
if (excludedSet.has(id))
|
|
77
|
+
continue;
|
|
78
|
+
result.knowledge_sources.push({
|
|
79
|
+
name: docset.id,
|
|
80
|
+
origin: docset.origin,
|
|
81
|
+
description: docset.description
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Add knowledge-server MCP entry if any knowledge_sources were collected
|
|
85
|
+
if (result.knowledge_sources.length > 0) {
|
|
86
|
+
result.mcp_servers.push({
|
|
87
|
+
ref: "knowledge",
|
|
88
|
+
command: "npx",
|
|
89
|
+
args: ["-y", "@codemcp/knowledge-server"],
|
|
90
|
+
env: {}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// Add skills-server MCP entry if any skills were collected
|
|
94
|
+
if (result.skills.length > 0) {
|
|
95
|
+
result.mcp_servers.push({
|
|
96
|
+
ref: "agentskills",
|
|
97
|
+
command: "npx",
|
|
98
|
+
args: ["-y", "@codemcp/skills-server"],
|
|
99
|
+
env: {}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// Merge custom section
|
|
103
|
+
if (userConfig.custom) {
|
|
104
|
+
if (userConfig.custom.instructions) {
|
|
105
|
+
result.instructions.push(...userConfig.custom.instructions);
|
|
106
|
+
}
|
|
107
|
+
if (userConfig.custom.mcp_servers) {
|
|
108
|
+
result.mcp_servers.push(...userConfig.custom.mcp_servers);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Dedup mcp_servers by ref (last wins)
|
|
112
|
+
const serversByRef = new Map();
|
|
113
|
+
for (const server of result.mcp_servers) {
|
|
114
|
+
serversByRef.set(server.ref, server);
|
|
115
|
+
}
|
|
116
|
+
result.mcp_servers = Array.from(serversByRef.values());
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Collect all unique docsets implied by the given choices.
|
|
121
|
+
* Used by the TUI to present docsets for confirmation before resolution.
|
|
122
|
+
*/
|
|
123
|
+
export function collectDocsets(choices, catalog) {
|
|
124
|
+
const seen = new Map();
|
|
125
|
+
for (const [facetId, optionId] of Object.entries(choices)) {
|
|
126
|
+
const facet = getFacet(catalog, facetId);
|
|
127
|
+
if (!facet)
|
|
128
|
+
continue;
|
|
129
|
+
const selectedIds = Array.isArray(optionId) ? optionId : [optionId];
|
|
130
|
+
for (const selectedId of selectedIds) {
|
|
131
|
+
const option = getOption(facet, selectedId);
|
|
132
|
+
if (!option?.docsets)
|
|
133
|
+
continue;
|
|
134
|
+
for (const docset of option.docsets) {
|
|
135
|
+
if (!seen.has(docset.id)) {
|
|
136
|
+
seen.set(docset.id, docset);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Array.from(seen.values());
|
|
142
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export interface Catalog {
|
|
2
|
+
facets: Facet[];
|
|
3
|
+
}
|
|
4
|
+
export interface Facet {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
description: string;
|
|
8
|
+
required: boolean;
|
|
9
|
+
multiSelect?: boolean;
|
|
10
|
+
dependsOn?: string[];
|
|
11
|
+
options: Option[];
|
|
12
|
+
}
|
|
13
|
+
export interface Option {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
description: string;
|
|
17
|
+
recipe: Provision[];
|
|
18
|
+
docsets?: DocsetDef[];
|
|
19
|
+
available?: (deps: Record<string, Option | undefined>) => boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface DocsetDef {
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
origin: string;
|
|
25
|
+
description: string;
|
|
26
|
+
}
|
|
27
|
+
export interface Provision {
|
|
28
|
+
writer: ProvisionWriter;
|
|
29
|
+
config: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
export type ProvisionWriter = "workflows" | "skills" | "knowledge" | "mcp-server" | "instruction" | "installable" | "git-hooks" | "setup-note";
|
|
32
|
+
export interface InlineSkill {
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
body: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ExternalSkill {
|
|
38
|
+
name: string;
|
|
39
|
+
source: string;
|
|
40
|
+
}
|
|
41
|
+
export type SkillDefinition = InlineSkill | ExternalSkill;
|
|
42
|
+
export interface GitHook {
|
|
43
|
+
phase: "pre-commit" | "pre-push";
|
|
44
|
+
script: string;
|
|
45
|
+
}
|
|
46
|
+
export interface LogicalConfig {
|
|
47
|
+
mcp_servers: McpServerEntry[];
|
|
48
|
+
instructions: string[];
|
|
49
|
+
cli_actions: CliAction[];
|
|
50
|
+
knowledge_sources: KnowledgeSource[];
|
|
51
|
+
skills: SkillDefinition[];
|
|
52
|
+
git_hooks: GitHook[];
|
|
53
|
+
setup_notes: string[];
|
|
54
|
+
}
|
|
55
|
+
export interface McpServerEntry {
|
|
56
|
+
ref: string;
|
|
57
|
+
command: string;
|
|
58
|
+
args: string[];
|
|
59
|
+
env: Record<string, string>;
|
|
60
|
+
/**
|
|
61
|
+
* Tool names the agent is pre-approved to use from this server.
|
|
62
|
+
* Defaults to `["*"]` (all tools) when not specified.
|
|
63
|
+
*/
|
|
64
|
+
allowedTools?: string[];
|
|
65
|
+
}
|
|
66
|
+
export interface CliAction {
|
|
67
|
+
command: string;
|
|
68
|
+
args: string[];
|
|
69
|
+
phase: "setup" | "install";
|
|
70
|
+
}
|
|
71
|
+
export interface KnowledgeSource {
|
|
72
|
+
name: string;
|
|
73
|
+
origin: string;
|
|
74
|
+
description: string;
|
|
75
|
+
}
|
|
76
|
+
export interface ResolutionContext {
|
|
77
|
+
resolved: Record<string, ResolvedFacet>;
|
|
78
|
+
}
|
|
79
|
+
export interface ResolvedFacet {
|
|
80
|
+
optionId: string;
|
|
81
|
+
option: Option;
|
|
82
|
+
}
|
|
83
|
+
export interface UserConfig {
|
|
84
|
+
choices: Record<string, string | string[]>;
|
|
85
|
+
excluded_docsets?: string[];
|
|
86
|
+
harnesses?: string[];
|
|
87
|
+
custom?: {
|
|
88
|
+
mcp_servers?: McpServerEntry[];
|
|
89
|
+
instructions?: string[];
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export interface LockFile {
|
|
93
|
+
version: 1;
|
|
94
|
+
generated_at: string;
|
|
95
|
+
choices: Record<string, string | string[]>;
|
|
96
|
+
harnesses?: string[];
|
|
97
|
+
logical_config: LogicalConfig;
|
|
98
|
+
}
|
|
99
|
+
export interface ProvisionWriterDef {
|
|
100
|
+
id: string;
|
|
101
|
+
write(config: Record<string, unknown>, context: ResolutionContext): Promise<Partial<LogicalConfig>>;
|
|
102
|
+
}
|
|
103
|
+
export interface AgentWriterDef {
|
|
104
|
+
id: string;
|
|
105
|
+
install(config: LogicalConfig, projectRoot: string): Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
export interface WriterRegistry {
|
|
108
|
+
provisions: Map<string, ProvisionWriterDef>;
|
|
109
|
+
agents: Map<string, AgentWriterDef>;
|
|
110
|
+
}
|