@devflow-tools/sdk 0.16.10 → 0.16.11
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/dist/host-adapter.d.ts +100 -0
- package/dist/host-adapter.d.ts.map +1 -0
- package/dist/host-adapter.js +84 -0
- package/dist/host-adapter.js.map +1 -0
- package/dist/host-adapter.test.d.ts +2 -0
- package/dist/host-adapter.test.d.ts.map +1 -0
- package/dist/host-adapter.test.js +340 -0
- package/dist/host-adapter.test.js.map +1 -0
- package/dist/host-adapters/claude-code.d.ts +3 -0
- package/dist/host-adapters/claude-code.d.ts.map +1 -0
- package/dist/host-adapters/claude-code.js +61 -0
- package/dist/host-adapters/claude-code.js.map +1 -0
- package/dist/host-adapters/codex.d.ts +3 -0
- package/dist/host-adapters/codex.d.ts.map +1 -0
- package/dist/host-adapters/codex.js +54 -0
- package/dist/host-adapters/codex.js.map +1 -0
- package/dist/host-adapters/generic-mcp.d.ts +3 -0
- package/dist/host-adapters/generic-mcp.d.ts.map +1 -0
- package/dist/host-adapters/generic-mcp.js +37 -0
- package/dist/host-adapters/generic-mcp.js.map +1 -0
- package/dist/host-adapters/index.d.ts +8 -0
- package/dist/host-adapters/index.d.ts.map +1 -0
- package/dist/host-adapters/index.js +16 -0
- package/dist/host-adapters/index.js.map +1 -0
- package/dist/host-adapters/projection.d.ts +8 -0
- package/dist/host-adapters/projection.d.ts.map +1 -0
- package/dist/host-adapters/projection.js +396 -0
- package/dist/host-adapters/projection.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-inventory.d.ts +5 -1
- package/dist/mcp-inventory.d.ts.map +1 -1
- package/dist/mcp-inventory.js +177 -12
- package/dist/mcp-inventory.js.map +1 -1
- package/dist/skill-overlay.d.ts +16 -0
- package/dist/skill-overlay.d.ts.map +1 -0
- package/dist/skill-overlay.js +99 -0
- package/dist/skill-overlay.js.map +1 -0
- package/dist/skill-overlay.test.d.ts +2 -0
- package/dist/skill-overlay.test.d.ts.map +1 -0
- package/dist/skill-overlay.test.js +39 -0
- package/dist/skill-overlay.test.js.map +1 -0
- package/dist/types/memory.d.ts +2 -0
- package/dist/types/memory.d.ts.map +1 -1
- package/dist/types/memory.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { capability, pathScore, resolvedHome, } from '../host-adapter.js';
|
|
3
|
+
import { projectJsonMcpServer, registerClaudePlugin } from './projection.js';
|
|
4
|
+
const HOOK_CAPABILITIES = [
|
|
5
|
+
'hooks.session_start',
|
|
6
|
+
'hooks.prompt_submit',
|
|
7
|
+
'hooks.pre_tool_use',
|
|
8
|
+
'hooks.post_tool_use',
|
|
9
|
+
'hooks.stop',
|
|
10
|
+
'hooks.session_end',
|
|
11
|
+
];
|
|
12
|
+
export const claudeCodeHostAdapter = {
|
|
13
|
+
id: 'claude-code',
|
|
14
|
+
aliases: ['claude', 'claude_code'],
|
|
15
|
+
async detect(context) {
|
|
16
|
+
if (context.env?.CLAUDE_PROJECT_DIR || context.env?.CLAUDE_CODE_ENTRYPOINT)
|
|
17
|
+
return 100;
|
|
18
|
+
return Math.max(pathScore(join(context.projectRoot, '.claude'), 70), pathScore(join(resolvedHome(context), '.claude'), 30));
|
|
19
|
+
},
|
|
20
|
+
async capabilities() {
|
|
21
|
+
return [
|
|
22
|
+
...HOOK_CAPABILITIES.map(name => capability(name, 'supported', 'claude-plugin-hook')),
|
|
23
|
+
capability('mcp.stdio', 'supported', 'claude-mcp-config'),
|
|
24
|
+
capability('mcp.http', 'supported', 'claude-mcp-config'),
|
|
25
|
+
capability('skills.native', 'supported', 'claude-plugin-skills'),
|
|
26
|
+
capability('instructions.project', 'supported', 'CLAUDE.md'),
|
|
27
|
+
capability('session.history', 'supported', 'claude-project-history'),
|
|
28
|
+
];
|
|
29
|
+
},
|
|
30
|
+
mcp: {
|
|
31
|
+
hostId: 'claude-code',
|
|
32
|
+
format: 'json',
|
|
33
|
+
ownedServerName: 'devflow',
|
|
34
|
+
configPaths(context) {
|
|
35
|
+
const home = resolvedHome(context);
|
|
36
|
+
return [
|
|
37
|
+
join(context.projectRoot, '.mcp.json'),
|
|
38
|
+
join(context.projectRoot, '.claude', 'mcp.json'),
|
|
39
|
+
join(home, '.claude', '.mcp.json'),
|
|
40
|
+
join(home, '.claude.json'),
|
|
41
|
+
join(home, '.claude', 'settings.json'),
|
|
42
|
+
];
|
|
43
|
+
},
|
|
44
|
+
project(context, server) {
|
|
45
|
+
return projectJsonMcpServer(join(context.projectRoot, '.mcp.json'), 'devflow', server);
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
skills: {
|
|
49
|
+
native: true,
|
|
50
|
+
targetDirectories: context => [join(resolvedHome(context), '.claude', 'plugins')],
|
|
51
|
+
project(context, source) {
|
|
52
|
+
return [registerClaudePlugin(join(resolvedHome(context), '.claude', 'plugins', 'known_marketplaces.json'), source.pluginRoot)];
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
sessions: {
|
|
56
|
+
status: 'supported',
|
|
57
|
+
historyPaths: context => [join(resolvedHome(context), '.claude', 'projects')],
|
|
58
|
+
},
|
|
59
|
+
hooks: { nativeEvents: HOOK_CAPABILITIES, profileSupport: ['minimal', 'standard', 'strict'] },
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/host-adapters/claude-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/host-adapters/codex.ts"],"names":[],"mappings":"AACA,OAAO,EAAuC,KAAK,WAAW,EAAuB,MAAM,oBAAoB,CAAC;AAYhH,eAAO,MAAM,gBAAgB,EAAE,WAsD9B,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { capability, pathScore, resolvedHome } from '../host-adapter.js';
|
|
3
|
+
import { projectBoundedInstructions, projectCodexMcpServer, projectNativeSkills } from './projection.js';
|
|
4
|
+
const HOOKS = [
|
|
5
|
+
'hooks.session_start', 'hooks.prompt_submit', 'hooks.pre_tool_use',
|
|
6
|
+
'hooks.post_tool_use', 'hooks.stop', 'hooks.session_end',
|
|
7
|
+
];
|
|
8
|
+
function codexHome(context) {
|
|
9
|
+
return context.env?.CODEX_HOME || join(resolvedHome(context), '.codex');
|
|
10
|
+
}
|
|
11
|
+
export const codexHostAdapter = {
|
|
12
|
+
id: 'codex',
|
|
13
|
+
aliases: ['openai-codex'],
|
|
14
|
+
async detect(context) {
|
|
15
|
+
if (context.env?.CODEX_HOME || context.env?.CODEX_THREAD_ID)
|
|
16
|
+
return 100;
|
|
17
|
+
return Math.max(pathScore(join(context.projectRoot, '.codex'), 70), pathScore(codexHome(context), 30));
|
|
18
|
+
},
|
|
19
|
+
async capabilities() {
|
|
20
|
+
return [
|
|
21
|
+
...HOOKS.map(name => capability(name, 'unavailable', undefined, 'Codex does not expose Claude-compatible lifecycle Hooks.', 'Use MCP tools, native Skills, project instructions, and durable server-side receipts.')),
|
|
22
|
+
capability('mcp.stdio', 'supported', 'codex-config-toml'),
|
|
23
|
+
capability('mcp.http', 'supported', 'codex-config-toml'),
|
|
24
|
+
capability('skills.native', 'supported', 'codex-skills'),
|
|
25
|
+
capability('instructions.project', 'supported', 'AGENTS.md'),
|
|
26
|
+
capability('session.history', 'degraded', 'codex-session-metadata', 'Lifecycle boundaries are not Hook-backed.', 'Use durable MCP receipts and queue state.'),
|
|
27
|
+
];
|
|
28
|
+
},
|
|
29
|
+
mcp: {
|
|
30
|
+
hostId: 'codex',
|
|
31
|
+
format: 'toml',
|
|
32
|
+
ownedServerName: 'devflow',
|
|
33
|
+
configPaths: context => [
|
|
34
|
+
join(context.projectRoot, '.codex', 'config.toml'),
|
|
35
|
+
join(codexHome(context), 'config.toml'),
|
|
36
|
+
],
|
|
37
|
+
project: (context, server) => projectCodexMcpServer(join(codexHome(context), 'config.toml'), 'devflow', server),
|
|
38
|
+
},
|
|
39
|
+
skills: {
|
|
40
|
+
native: true,
|
|
41
|
+
targetDirectories: context => [join(codexHome(context), 'skills')],
|
|
42
|
+
project(context, source) {
|
|
43
|
+
return [
|
|
44
|
+
projectNativeSkills(join(codexHome(context), 'skills'), source.skillsDirectory),
|
|
45
|
+
projectBoundedInstructions(join(context.projectRoot, 'AGENTS.md'), source.instructionContent),
|
|
46
|
+
];
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
sessions: {
|
|
50
|
+
status: 'degraded',
|
|
51
|
+
historyPaths: context => [join(codexHome(context), 'sessions')],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/host-adapters/codex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic-mcp.d.ts","sourceRoot":"","sources":["../../src/host-adapters/generic-mcp.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,WAAW,EAAuB,MAAM,oBAAoB,CAAC;AAQvF,eAAO,MAAM,qBAAqB,EAAE,WAmCnC,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { capability } from '../host-adapter.js';
|
|
3
|
+
import { projectInstructionFallback, projectJsonMcpServer } from './projection.js';
|
|
4
|
+
const HOOKS = [
|
|
5
|
+
'hooks.session_start', 'hooks.prompt_submit', 'hooks.pre_tool_use',
|
|
6
|
+
'hooks.post_tool_use', 'hooks.stop', 'hooks.session_end',
|
|
7
|
+
];
|
|
8
|
+
export const genericMcpHostAdapter = {
|
|
9
|
+
id: 'generic-mcp',
|
|
10
|
+
aliases: ['generic', 'mcp', 'cursor', 'cline'],
|
|
11
|
+
detect: async () => 1,
|
|
12
|
+
async capabilities() {
|
|
13
|
+
return [
|
|
14
|
+
...HOOKS.map(name => capability(name, 'unavailable', undefined, 'The generic MCP contract has no lifecycle Hook API.', 'Use explicit MCP calls and canonical receipts.')),
|
|
15
|
+
capability('mcp.stdio', 'supported', 'host-managed-mcp'),
|
|
16
|
+
capability('mcp.http', 'supported', 'host-managed-mcp'),
|
|
17
|
+
capability('skills.native', 'unavailable', undefined, 'No host-native Skill format is assumed.', 'Project a concise instruction file and expose MCP schemas.'),
|
|
18
|
+
capability('instructions.project', 'degraded', 'host-managed-instructions', 'Instruction filename is host-defined.', 'Configure the host to load DevFlow project instructions.'),
|
|
19
|
+
capability('session.history', 'unavailable', undefined, 'No session history contract is assumed.', 'Use MCP session IDs supplied by the host.'),
|
|
20
|
+
];
|
|
21
|
+
},
|
|
22
|
+
mcp: {
|
|
23
|
+
hostId: 'generic-mcp',
|
|
24
|
+
format: 'json',
|
|
25
|
+
ownedServerName: 'devflow',
|
|
26
|
+
configPaths: context => [join(context.projectRoot, '.mcp.json')],
|
|
27
|
+
project: (context, server) => projectJsonMcpServer(join(context.projectRoot, '.mcp.json'), 'devflow', server),
|
|
28
|
+
},
|
|
29
|
+
skills: {
|
|
30
|
+
native: false,
|
|
31
|
+
targetDirectories: () => [],
|
|
32
|
+
fallback: 'project-instructions-plus-mcp',
|
|
33
|
+
project: (context, source) => [projectInstructionFallback(join(context.projectRoot, '.devflow', 'instructions.md'), source.instructionContent)],
|
|
34
|
+
},
|
|
35
|
+
sessions: { status: 'unavailable', historyPaths: () => [] },
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=generic-mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic-mcp.js","sourceRoot":"","sources":["../../src/host-adapters/generic-mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAyC,MAAM,oBAAoB,CAAC;AACvF,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEnF,MAAM,KAAK,GAAqB;IAC9B,qBAAqB,EAAE,qBAAqB,EAAE,oBAAoB;IAClE,qBAAqB,EAAE,YAAY,EAAE,mBAAmB;CACzD,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAgB;IAChD,EAAE,EAAE,aAAa;IACjB,OAAO,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC;IAC9C,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IACrB,KAAK,CAAC,YAAY;QAChB,OAAO;YACL,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,qDAAqD,EAAE,gDAAgD,CAAC,CAAC;YACzK,UAAU,CAAC,WAAW,EAAE,WAAW,EAAE,kBAAkB,CAAC;YACxD,UAAU,CAAC,UAAU,EAAE,WAAW,EAAE,kBAAkB,CAAC;YACvD,UAAU,CAAC,eAAe,EAAE,aAAa,EAAE,SAAS,EAAE,yCAAyC,EAAE,4DAA4D,CAAC;YAC9J,UAAU,CAAC,sBAAsB,EAAE,UAAU,EAAE,2BAA2B,EAAE,uCAAuC,EAAE,0DAA0D,CAAC;YAChL,UAAU,CAAC,iBAAiB,EAAE,aAAa,EAAE,SAAS,EAAE,yCAAyC,EAAE,2CAA2C,CAAC;SAChJ,CAAC;IACJ,CAAC;IACD,GAAG,EAAE;QACH,MAAM,EAAE,aAAa;QACrB,MAAM,EAAE,MAAM;QACd,eAAe,EAAE,SAAS;QAC1B,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;QAChE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,oBAAoB,CAChD,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC,EACtC,SAAS,EACT,MAAM,CACP;KACF;IACD,MAAM,EAAE;QACN,MAAM,EAAE,KAAK;QACb,iBAAiB,EAAE,GAAG,EAAE,CAAC,EAAE;QAC3B,QAAQ,EAAE,+BAA+B;QACzC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,0BAA0B,CACvD,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,UAAU,EAAE,iBAAiB,CAAC,EACxD,MAAM,CAAC,kBAAkB,CAC1B,CAAC;KACH;IACD,QAAQ,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE;CAC5D,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { HostAdapterRegistry, type HostDetectionContext, type HostAdapterSelection } from '../host-adapter.js';
|
|
2
|
+
import { claudeCodeHostAdapter } from './claude-code.js';
|
|
3
|
+
import { codexHostAdapter } from './codex.js';
|
|
4
|
+
import { genericMcpHostAdapter } from './generic-mcp.js';
|
|
5
|
+
export { claudeCodeHostAdapter, codexHostAdapter, genericMcpHostAdapter };
|
|
6
|
+
export declare function createDefaultHostAdapterRegistry(): HostAdapterRegistry;
|
|
7
|
+
export declare function selectHostAdapter(context: HostDetectionContext): Promise<HostAdapterSelection>;
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host-adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,KAAK,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/G,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,CAAC;AAE1E,wBAAgB,gCAAgC,IAAI,mBAAmB,CAMtE;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAEpG"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { HostAdapterRegistry } from '../host-adapter.js';
|
|
2
|
+
import { claudeCodeHostAdapter } from './claude-code.js';
|
|
3
|
+
import { codexHostAdapter } from './codex.js';
|
|
4
|
+
import { genericMcpHostAdapter } from './generic-mcp.js';
|
|
5
|
+
export { claudeCodeHostAdapter, codexHostAdapter, genericMcpHostAdapter };
|
|
6
|
+
export function createDefaultHostAdapterRegistry() {
|
|
7
|
+
const registry = new HostAdapterRegistry();
|
|
8
|
+
registry.register(claudeCodeHostAdapter);
|
|
9
|
+
registry.register(codexHostAdapter);
|
|
10
|
+
registry.register(genericMcpHostAdapter);
|
|
11
|
+
return registry;
|
|
12
|
+
}
|
|
13
|
+
export async function selectHostAdapter(context) {
|
|
14
|
+
return createDefaultHostAdapterRegistry().select(context);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/host-adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAwD,MAAM,oBAAoB,CAAC;AAC/G,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,CAAC;AAE1E,MAAM,UAAU,gCAAgC;IAC9C,MAAM,QAAQ,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAC3C,QAAQ,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACpC,QAAQ,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IACzC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAA6B;IACnE,OAAO,gCAAgC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpServerProjection, ProjectionResult } from '../host-adapter.js';
|
|
2
|
+
export declare function projectJsonMcpServer(path: string, serverName: string, server: McpServerProjection): ProjectionResult;
|
|
3
|
+
export declare function projectCodexMcpServer(path: string, serverName: string, server: McpServerProjection): ProjectionResult;
|
|
4
|
+
export declare function registerClaudePlugin(path: string, pluginRoot: string | undefined): ProjectionResult;
|
|
5
|
+
export declare function projectNativeSkills(path: string, source: string | undefined): ProjectionResult;
|
|
6
|
+
export declare function projectBoundedInstructions(path: string, content?: string): ProjectionResult;
|
|
7
|
+
export declare function projectInstructionFallback(path: string, content?: string): ProjectionResult;
|
|
8
|
+
//# sourceMappingURL=projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/host-adapters/projection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAsB,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAqCpG,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,mBAAmB,GAC1B,gBAAgB,CAmBlB;AA+ED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,mBAAmB,GAC1B,gBAAgB,CAclB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAqGnG;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,CA6C9F;AA6CD,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,gBAAgB,CA2B3F;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAmB3F"}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, relative } from 'node:path';
|
|
3
|
+
function failure(artifact, path, error) {
|
|
4
|
+
return {
|
|
5
|
+
artifact,
|
|
6
|
+
status: 'failed',
|
|
7
|
+
changed: false,
|
|
8
|
+
path,
|
|
9
|
+
message: error instanceof Error ? error.message : String(error),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function readJsonObject(path) {
|
|
13
|
+
if (!existsSync(path))
|
|
14
|
+
return {};
|
|
15
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
16
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
17
|
+
throw new Error(`Expected a JSON object in ${path}; no changes were written.`);
|
|
18
|
+
}
|
|
19
|
+
return parsed;
|
|
20
|
+
}
|
|
21
|
+
function sameJson(left, right) {
|
|
22
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
23
|
+
}
|
|
24
|
+
function normalizedServer(server) {
|
|
25
|
+
if (!server.command && !server.url) {
|
|
26
|
+
throw new Error('An MCP projection requires either command or url.');
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
...(server.command ? { command: server.command } : {}),
|
|
30
|
+
...(server.args && server.args.length > 0 ? { args: server.args } : {}),
|
|
31
|
+
...(server.url ? { url: server.url } : {}),
|
|
32
|
+
...(server.env && Object.keys(server.env).length > 0 ? { env: server.env } : {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function projectJsonMcpServer(path, serverName, server) {
|
|
36
|
+
try {
|
|
37
|
+
const existing = readJsonObject(path);
|
|
38
|
+
const rawServers = existing.mcpServers;
|
|
39
|
+
if (rawServers !== undefined && (!rawServers || typeof rawServers !== 'object' || Array.isArray(rawServers))) {
|
|
40
|
+
throw new Error(`Expected mcpServers to be a JSON object in ${path}; no changes were written.`);
|
|
41
|
+
}
|
|
42
|
+
const servers = { ...(rawServers ?? {}) };
|
|
43
|
+
const projected = normalizedServer(server);
|
|
44
|
+
if (sameJson(servers[serverName], projected)) {
|
|
45
|
+
return { artifact: 'mcp', status: 'unchanged', changed: false, path, message: `${serverName} MCP server is already configured.` };
|
|
46
|
+
}
|
|
47
|
+
servers[serverName] = projected;
|
|
48
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
49
|
+
writeFileSync(path, `${JSON.stringify({ ...existing, mcpServers: servers }, null, 2)}\n`);
|
|
50
|
+
return { artifact: 'mcp', status: 'written', changed: true, path, message: `${serverName} MCP server configured.` };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return failure('mcp', path, error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function quoteToml(value) {
|
|
57
|
+
return JSON.stringify(value);
|
|
58
|
+
}
|
|
59
|
+
function parseTablePath(header) {
|
|
60
|
+
const trimmed = header.trim();
|
|
61
|
+
const match = trimmed.startsWith('[[')
|
|
62
|
+
? /^\[\[\s*([^\]]+)\s*\]\](?:\s*#.*)?$/.exec(trimmed)
|
|
63
|
+
: /^\[\s*([^\]]+)\s*\](?:\s*#.*)?$/.exec(trimmed);
|
|
64
|
+
if (!match)
|
|
65
|
+
return null;
|
|
66
|
+
const path = [];
|
|
67
|
+
let cursor = 0;
|
|
68
|
+
const input = match[1];
|
|
69
|
+
while (cursor < input.length) {
|
|
70
|
+
while (/\s/.test(input[cursor] ?? ''))
|
|
71
|
+
cursor += 1;
|
|
72
|
+
const quote = input[cursor] === '"' || input[cursor] === "'" ? input[cursor++] : undefined;
|
|
73
|
+
let segment = '';
|
|
74
|
+
while (cursor < input.length) {
|
|
75
|
+
const char = input[cursor];
|
|
76
|
+
if (quote) {
|
|
77
|
+
if (char === quote) {
|
|
78
|
+
cursor += 1;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (quote === '"' && char === '\\' && cursor + 1 < input.length) {
|
|
82
|
+
segment += input[cursor + 1];
|
|
83
|
+
cursor += 2;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (char === '.' || /\s/.test(char)) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
segment += char;
|
|
91
|
+
cursor += 1;
|
|
92
|
+
}
|
|
93
|
+
if (!segment)
|
|
94
|
+
return null;
|
|
95
|
+
path.push(segment);
|
|
96
|
+
while (/\s/.test(input[cursor] ?? ''))
|
|
97
|
+
cursor += 1;
|
|
98
|
+
if (cursor >= input.length)
|
|
99
|
+
break;
|
|
100
|
+
if (input[cursor] !== '.')
|
|
101
|
+
return null;
|
|
102
|
+
cursor += 1;
|
|
103
|
+
}
|
|
104
|
+
return path;
|
|
105
|
+
}
|
|
106
|
+
function removeOwnedTomlTables(content, serverName) {
|
|
107
|
+
const lines = content.split(/\r?\n/);
|
|
108
|
+
const kept = [];
|
|
109
|
+
let skip = false;
|
|
110
|
+
for (const line of lines) {
|
|
111
|
+
const table = parseTablePath(line);
|
|
112
|
+
if (table) {
|
|
113
|
+
skip = table[0] === 'mcp_servers' && table[1] === serverName;
|
|
114
|
+
}
|
|
115
|
+
if (!skip)
|
|
116
|
+
kept.push(line);
|
|
117
|
+
}
|
|
118
|
+
while (kept.length > 0 && kept[kept.length - 1] === '')
|
|
119
|
+
kept.pop();
|
|
120
|
+
return kept.join('\n');
|
|
121
|
+
}
|
|
122
|
+
function renderCodexServer(serverName, server) {
|
|
123
|
+
normalizedServer(server);
|
|
124
|
+
const lines = [`[mcp_servers.${quoteToml(serverName)}]`];
|
|
125
|
+
if (server.command)
|
|
126
|
+
lines.push(`command = ${quoteToml(server.command)}`);
|
|
127
|
+
if (server.args && server.args.length > 0) {
|
|
128
|
+
lines.push(`args = [${server.args.map(quoteToml).join(', ')}]`);
|
|
129
|
+
}
|
|
130
|
+
if (server.url)
|
|
131
|
+
lines.push(`url = ${quoteToml(server.url)}`);
|
|
132
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
133
|
+
lines.push('', `[mcp_servers.${quoteToml(serverName)}.env]`);
|
|
134
|
+
for (const key of Object.keys(server.env).sort()) {
|
|
135
|
+
lines.push(`${quoteToml(key)} = ${quoteToml(server.env[key])}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return lines.join('\n');
|
|
139
|
+
}
|
|
140
|
+
export function projectCodexMcpServer(path, serverName, server) {
|
|
141
|
+
try {
|
|
142
|
+
const existing = existsSync(path) ? readFileSync(path, 'utf8') : '';
|
|
143
|
+
const preserved = removeOwnedTomlTables(existing, serverName);
|
|
144
|
+
const projected = [preserved, renderCodexServer(serverName, server)].filter(Boolean).join('\n\n') + '\n';
|
|
145
|
+
if (existing === projected) {
|
|
146
|
+
return { artifact: 'mcp', status: 'unchanged', changed: false, path, message: `${serverName} MCP server is already configured.` };
|
|
147
|
+
}
|
|
148
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
149
|
+
writeFileSync(path, projected);
|
|
150
|
+
return { artifact: 'mcp', status: 'written', changed: true, path, message: `${serverName} MCP server configured.` };
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return failure('mcp', path, error);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export function registerClaudePlugin(path, pluginRoot) {
|
|
157
|
+
if (!pluginRoot) {
|
|
158
|
+
return { artifact: 'plugin', status: 'failed', changed: false, path, message: 'Claude plugin directory was not found.' };
|
|
159
|
+
}
|
|
160
|
+
const pluginManifestPath = join(pluginRoot, '.claude-plugin', 'plugin.json');
|
|
161
|
+
if (!existsSync(pluginManifestPath)) {
|
|
162
|
+
return { artifact: 'plugin', status: 'failed', changed: false, path, message: `Claude plugin manifest was not found at ${pluginManifestPath}.` };
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const pluginsDir = dirname(path);
|
|
166
|
+
const marketplaceRoot = join(pluginsDir, 'marketplaces', 'devflow-local');
|
|
167
|
+
const marketplacePath = join(marketplaceRoot, '.claude-plugin', 'marketplace.json');
|
|
168
|
+
const installedPath = join(pluginsDir, 'installed_plugins.json');
|
|
169
|
+
const settingsPath = join(dirname(pluginsDir), 'settings.json');
|
|
170
|
+
const known = readJsonObject(path);
|
|
171
|
+
const installed = readJsonObject(installedPath);
|
|
172
|
+
const settings = readJsonObject(settingsPath);
|
|
173
|
+
const rawPlugins = installed.plugins;
|
|
174
|
+
if (rawPlugins !== undefined && (!rawPlugins || typeof rawPlugins !== 'object' || Array.isArray(rawPlugins))) {
|
|
175
|
+
throw new Error(`Invalid Claude plugin registry plugins in ${installedPath}; no changes were written.`);
|
|
176
|
+
}
|
|
177
|
+
const plugins = { ...(rawPlugins ?? {}) };
|
|
178
|
+
const rawDevflowEntries = plugins['devflow@devflow-local'];
|
|
179
|
+
if (rawDevflowEntries !== undefined && !Array.isArray(rawDevflowEntries)) {
|
|
180
|
+
throw new Error(`Invalid Claude plugin registration in ${installedPath}; no changes were written.`);
|
|
181
|
+
}
|
|
182
|
+
const rawEnabled = settings.enabledPlugins;
|
|
183
|
+
const rawExtraMarketplaces = settings.extraKnownMarketplaces;
|
|
184
|
+
if (rawEnabled !== undefined && (!rawEnabled || typeof rawEnabled !== 'object' || Array.isArray(rawEnabled))) {
|
|
185
|
+
throw new Error(`Invalid enabledPlugins in ${settingsPath}; no changes were written.`);
|
|
186
|
+
}
|
|
187
|
+
if (rawExtraMarketplaces !== undefined && (!rawExtraMarketplaces || typeof rawExtraMarketplaces !== 'object' || Array.isArray(rawExtraMarketplaces))) {
|
|
188
|
+
throw new Error(`Invalid extraKnownMarketplaces in ${settingsPath}; no changes were written.`);
|
|
189
|
+
}
|
|
190
|
+
const version = readPluginVersion(pluginRoot);
|
|
191
|
+
const existingEntries = rawDevflowEntries ?? [];
|
|
192
|
+
const existingUser = existingEntries.find(entry => isRecord(entry) && entry.scope === 'user');
|
|
193
|
+
const otherEntries = existingEntries.filter(entry => !isRecord(entry) || entry.scope !== 'user');
|
|
194
|
+
const unchangedInstall = isRecord(existingUser)
|
|
195
|
+
&& existingUser.installPath === pluginRoot
|
|
196
|
+
&& existingUser.version === version;
|
|
197
|
+
const now = new Date().toISOString();
|
|
198
|
+
const installedAt = isRecord(existingUser) && typeof existingUser.installedAt === 'string'
|
|
199
|
+
? existingUser.installedAt
|
|
200
|
+
: now;
|
|
201
|
+
const lastUpdated = unchangedInstall && isRecord(existingUser) && typeof existingUser.lastUpdated === 'string'
|
|
202
|
+
? existingUser.lastUpdated
|
|
203
|
+
: now;
|
|
204
|
+
plugins['devflow@devflow-local'] = [
|
|
205
|
+
...otherEntries,
|
|
206
|
+
{
|
|
207
|
+
...(isRecord(existingUser) ? existingUser : {}),
|
|
208
|
+
scope: 'user',
|
|
209
|
+
installPath: pluginRoot,
|
|
210
|
+
version,
|
|
211
|
+
installedAt,
|
|
212
|
+
lastUpdated,
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
let pluginSource = relative(marketplaceRoot, pluginRoot).replaceAll('\\', '/');
|
|
216
|
+
if (!pluginSource.startsWith('.'))
|
|
217
|
+
pluginSource = `./${pluginSource}`;
|
|
218
|
+
const marketplace = {
|
|
219
|
+
name: 'devflow-local',
|
|
220
|
+
description: 'DevFlow local Claude Code plugin',
|
|
221
|
+
owner: { name: 'DevFlow' },
|
|
222
|
+
plugins: [{ name: 'devflow', source: pluginSource }],
|
|
223
|
+
};
|
|
224
|
+
const knownEntry = isRecord(known['devflow-local']) ? known['devflow-local'] : {};
|
|
225
|
+
const projectedKnown = {
|
|
226
|
+
...known,
|
|
227
|
+
'devflow-local': {
|
|
228
|
+
...knownEntry,
|
|
229
|
+
source: { source: 'directory', path: marketplaceRoot },
|
|
230
|
+
installLocation: marketplaceRoot,
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
const enabledPlugins = { ...(rawEnabled ?? {}) };
|
|
234
|
+
enabledPlugins['devflow@devflow-local'] = true;
|
|
235
|
+
const extraKnown = { ...(rawExtraMarketplaces ?? {}) };
|
|
236
|
+
extraKnown['devflow-local'] = { source: { source: 'directory', path: marketplaceRoot } };
|
|
237
|
+
const projectedSettings = { ...settings, enabledPlugins, extraKnownMarketplaces: extraKnown };
|
|
238
|
+
const projectedInstalled = { ...installed, version: 2, plugins };
|
|
239
|
+
const targets = [
|
|
240
|
+
{ targetPath: marketplacePath, existing: readJsonObject(marketplacePath), projected: marketplace },
|
|
241
|
+
{ targetPath: path, existing: known, projected: projectedKnown },
|
|
242
|
+
{ targetPath: installedPath, existing: installed, projected: projectedInstalled },
|
|
243
|
+
{ targetPath: settingsPath, existing: settings, projected: projectedSettings },
|
|
244
|
+
];
|
|
245
|
+
const changedTargets = targets.filter(target => !sameJson(target.existing, target.projected));
|
|
246
|
+
for (const target of changedTargets) {
|
|
247
|
+
mkdirSync(dirname(target.targetPath), { recursive: true });
|
|
248
|
+
writeFileSync(target.targetPath, `${JSON.stringify(target.projected, null, 2)}\n`);
|
|
249
|
+
}
|
|
250
|
+
return changedTargets.length === 0
|
|
251
|
+
? { artifact: 'plugin', status: 'unchanged', changed: false, path: marketplacePath, message: 'Claude plugin is already registered.' }
|
|
252
|
+
: { artifact: 'plugin', status: 'written', changed: true, path: marketplacePath, message: 'Claude plugin marketplace, installation, and enablement registered.' };
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
return failure('plugin', path, error);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
export function projectNativeSkills(path, source) {
|
|
259
|
+
if (!source || !existsSync(source)) {
|
|
260
|
+
return { artifact: 'skills', status: 'failed', changed: false, path, message: 'DevFlow Skill source directory was not found.' };
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const projections = readdirSync(source)
|
|
264
|
+
.filter(entry => entry.startsWith('devflow') && statSync(join(source, entry)).isDirectory())
|
|
265
|
+
.map(entry => ({ sourceName: entry, targetName: normalizeCodexSkillName(entry) }));
|
|
266
|
+
const byTarget = new Map();
|
|
267
|
+
for (const projection of projections) {
|
|
268
|
+
const names = byTarget.get(projection.targetName) ?? [];
|
|
269
|
+
names.push(projection.sourceName);
|
|
270
|
+
byTarget.set(projection.targetName, names);
|
|
271
|
+
}
|
|
272
|
+
const collision = [...byTarget.entries()].find(([, names]) => names.length > 1);
|
|
273
|
+
if (collision) {
|
|
274
|
+
return {
|
|
275
|
+
artifact: 'skills',
|
|
276
|
+
status: 'failed',
|
|
277
|
+
changed: false,
|
|
278
|
+
path,
|
|
279
|
+
message: `Codex Skill name collision for ${collision[0]}: ${collision[1].join(', ')}.`,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
mkdirSync(path, { recursive: true });
|
|
283
|
+
let count = 0;
|
|
284
|
+
for (const projection of projections) {
|
|
285
|
+
const sourcePath = join(source, projection.sourceName);
|
|
286
|
+
const targetPath = join(path, projection.targetName);
|
|
287
|
+
cpSync(sourcePath, targetPath, { recursive: true, force: true });
|
|
288
|
+
const skillPath = join(targetPath, 'SKILL.md');
|
|
289
|
+
if (!existsSync(skillPath)) {
|
|
290
|
+
throw new Error(`Codex Skill source is missing SKILL.md: ${sourcePath}`);
|
|
291
|
+
}
|
|
292
|
+
const content = readFileSync(skillPath, 'utf8');
|
|
293
|
+
writeFileSync(skillPath, rewriteSkillFrontmatterName(content, projection.targetName));
|
|
294
|
+
count += 1;
|
|
295
|
+
}
|
|
296
|
+
if (count === 0) {
|
|
297
|
+
return { artifact: 'skills', status: 'failed', changed: false, path, message: 'No DevFlow Skills were found to project.' };
|
|
298
|
+
}
|
|
299
|
+
return { artifact: 'skills', status: 'written', changed: true, path, message: `${count} native DevFlow Skills projected.` };
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
return failure('skills', path, error);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function normalizeCodexSkillName(name) {
|
|
306
|
+
const normalized = name.toLowerCase()
|
|
307
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
308
|
+
.replace(/-+/g, '-')
|
|
309
|
+
.replace(/^-|-$/g, '');
|
|
310
|
+
if (!normalized || !/^[a-z0-9-]+$/.test(normalized)) {
|
|
311
|
+
throw new Error(`Cannot derive a valid Codex Skill name from ${name}.`);
|
|
312
|
+
}
|
|
313
|
+
return normalized;
|
|
314
|
+
}
|
|
315
|
+
function rewriteSkillFrontmatterName(content, name) {
|
|
316
|
+
if (!content.startsWith('---\n')) {
|
|
317
|
+
return `---\nname: ${name}\n---\n${content}`;
|
|
318
|
+
}
|
|
319
|
+
const end = content.indexOf('\n---', 4);
|
|
320
|
+
if (end < 0)
|
|
321
|
+
throw new Error('Skill frontmatter is missing its closing marker.');
|
|
322
|
+
const frontmatter = content.slice(4, end);
|
|
323
|
+
const rewritten = /^name\s*:.*$/m.test(frontmatter)
|
|
324
|
+
? frontmatter.replace(/^name\s*:.*$/m, `name: ${name}`)
|
|
325
|
+
: `name: ${name}\n${frontmatter}`;
|
|
326
|
+
return `---\n${rewritten}${content.slice(end)}`;
|
|
327
|
+
}
|
|
328
|
+
function readPluginVersion(pluginRoot) {
|
|
329
|
+
for (const path of [join(pluginRoot, 'package.json'), join(pluginRoot, '.claude-plugin', 'plugin.json')]) {
|
|
330
|
+
try {
|
|
331
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
332
|
+
if (typeof value.version === 'string' && value.version)
|
|
333
|
+
return value.version;
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
// Try the next plugin metadata source.
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return 'unknown';
|
|
340
|
+
}
|
|
341
|
+
function isRecord(value) {
|
|
342
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
343
|
+
}
|
|
344
|
+
const DEVFLOW_INSTRUCTIONS_START = '<!-- devflow:instructions:start -->';
|
|
345
|
+
const DEVFLOW_INSTRUCTIONS_END = '<!-- devflow:instructions:end -->';
|
|
346
|
+
export function projectBoundedInstructions(path, content) {
|
|
347
|
+
const body = (content ?? [
|
|
348
|
+
'## DevFlow',
|
|
349
|
+
'',
|
|
350
|
+
'Use the projected DevFlow Skills and the configured `devflow` MCP server for context, memory, knowledge, workflow, and telemetry operations.',
|
|
351
|
+
'Lifecycle Hooks are unavailable on this host. Rely on canonical MCP receipts and explicit capability degradation instead of assuming native tool blocking.',
|
|
352
|
+
].join('\n')).trim();
|
|
353
|
+
const section = `${DEVFLOW_INSTRUCTIONS_START}\n${body}\n${DEVFLOW_INSTRUCTIONS_END}`;
|
|
354
|
+
try {
|
|
355
|
+
const existing = existsSync(path) ? readFileSync(path, 'utf8') : '';
|
|
356
|
+
const start = existing.indexOf(DEVFLOW_INSTRUCTIONS_START);
|
|
357
|
+
const end = existing.indexOf(DEVFLOW_INSTRUCTIONS_END);
|
|
358
|
+
if ((start >= 0) !== (end >= 0) || (start >= 0 && end < start)) {
|
|
359
|
+
throw new Error(`DevFlow instruction markers are incomplete in ${path}; no changes were written.`);
|
|
360
|
+
}
|
|
361
|
+
const projected = start >= 0
|
|
362
|
+
? `${existing.slice(0, start)}${section}${existing.slice(end + DEVFLOW_INSTRUCTIONS_END.length)}`
|
|
363
|
+
: `${existing.trimEnd()}${existing.trimEnd() ? '\n\n' : ''}${section}\n`;
|
|
364
|
+
if (existing === projected) {
|
|
365
|
+
return { artifact: 'instructions', status: 'unchanged', changed: false, path, message: 'DevFlow project instructions are already configured.' };
|
|
366
|
+
}
|
|
367
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
368
|
+
writeFileSync(path, projected);
|
|
369
|
+
return { artifact: 'instructions', status: 'written', changed: true, path, message: 'DevFlow project instructions configured.' };
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
return failure('instructions', path, error);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
export function projectInstructionFallback(path, content) {
|
|
376
|
+
const instructions = content ?? [
|
|
377
|
+
'# DevFlow',
|
|
378
|
+
'',
|
|
379
|
+
'Use the configured `devflow` MCP server for DevFlow context, memory, knowledge, workflow, and telemetry operations.',
|
|
380
|
+
'This host has no assumed native Skill or lifecycle Hook integration. Treat MCP receipts as the authoritative result.',
|
|
381
|
+
'',
|
|
382
|
+
].join('\n');
|
|
383
|
+
try {
|
|
384
|
+
const existing = existsSync(path) ? readFileSync(path, 'utf8') : undefined;
|
|
385
|
+
if (existing === instructions) {
|
|
386
|
+
return { artifact: 'instructions', status: 'degraded', changed: false, path, message: 'Generic host instructions already exist; configure the host to load this file.' };
|
|
387
|
+
}
|
|
388
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
389
|
+
writeFileSync(path, instructions);
|
|
390
|
+
return { artifact: 'instructions', status: 'degraded', changed: true, path, message: 'Generic host instructions written; configure the host to load this file.' };
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
return failure('instructions', path, error);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
//# sourceMappingURL=projection.js.map
|