@moreih29/nexus-core 0.11.0 → 0.13.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/README.md +48 -63
- package/assets/agents/architect/body.ko.md +177 -0
- package/{agents → assets/agents}/architect/body.md +16 -0
- package/assets/agents/designer/body.ko.md +125 -0
- package/{agents → assets/agents}/designer/body.md +16 -0
- package/assets/agents/engineer/body.ko.md +106 -0
- package/{agents → assets/agents}/engineer/body.md +14 -0
- package/assets/agents/lead/body.ko.md +70 -0
- package/assets/agents/lead/body.md +70 -0
- package/assets/agents/postdoc/body.ko.md +122 -0
- package/{agents → assets/agents}/postdoc/body.md +16 -0
- package/assets/agents/researcher/body.ko.md +137 -0
- package/{agents → assets/agents}/researcher/body.md +15 -0
- package/assets/agents/reviewer/body.ko.md +138 -0
- package/{agents → assets/agents}/reviewer/body.md +15 -0
- package/assets/agents/strategist/body.ko.md +116 -0
- package/{agents → assets/agents}/strategist/body.md +16 -0
- package/assets/agents/tester/body.ko.md +195 -0
- package/{agents → assets/agents}/tester/body.md +15 -0
- package/assets/agents/writer/body.ko.md +122 -0
- package/{agents → assets/agents}/writer/body.md +14 -0
- package/assets/capability-matrix.yml +198 -0
- package/assets/hooks/agent-bootstrap/handler.test.ts +368 -0
- package/assets/hooks/agent-bootstrap/handler.ts +119 -0
- package/assets/hooks/agent-bootstrap/meta.yml +10 -0
- package/assets/hooks/agent-finalize/handler.test.ts +368 -0
- package/assets/hooks/agent-finalize/handler.ts +76 -0
- package/assets/hooks/agent-finalize/meta.yml +10 -0
- package/assets/hooks/capability-matrix.yml +313 -0
- package/assets/hooks/post-tool-telemetry/handler.test.ts +302 -0
- package/assets/hooks/post-tool-telemetry/handler.ts +49 -0
- package/assets/hooks/post-tool-telemetry/meta.yml +11 -0
- package/assets/hooks/prompt-router/handler.test.ts +801 -0
- package/assets/hooks/prompt-router/handler.ts +261 -0
- package/assets/hooks/prompt-router/meta.yml +11 -0
- package/assets/hooks/session-init/handler.test.ts +274 -0
- package/assets/hooks/session-init/handler.ts +30 -0
- package/assets/hooks/session-init/meta.yml +9 -0
- package/assets/lsp-servers.json +55 -0
- package/assets/schema/lsp-servers.schema.json +67 -0
- package/assets/skills/nx-init/body.ko.md +197 -0
- package/{skills → assets/skills}/nx-init/body.md +11 -0
- package/assets/skills/nx-plan/body.ko.md +361 -0
- package/{skills → assets/skills}/nx-plan/body.md +13 -0
- package/assets/skills/nx-run/body.ko.md +161 -0
- package/{skills → assets/skills}/nx-run/body.md +11 -0
- package/assets/skills/nx-sync/body.ko.md +92 -0
- package/{skills → assets/skills}/nx-sync/body.md +10 -0
- package/assets/tools/tool-name-map.yml +353 -0
- package/dist/hooks/opencode-mount.d.ts +35 -0
- package/dist/hooks/opencode-mount.d.ts.map +1 -0
- package/dist/hooks/opencode-mount.js +332 -0
- package/dist/hooks/opencode-mount.js.map +1 -0
- package/dist/hooks/runtime.d.ts +37 -0
- package/dist/hooks/runtime.d.ts.map +1 -0
- package/dist/hooks/runtime.js +274 -0
- package/dist/hooks/runtime.js.map +1 -0
- package/dist/hooks/types.d.ts +196 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +85 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/lsp/cache.d.ts +9 -0
- package/dist/lsp/cache.d.ts.map +1 -0
- package/dist/lsp/cache.js +216 -0
- package/dist/lsp/cache.js.map +1 -0
- package/dist/lsp/client.d.ts +24 -0
- package/dist/lsp/client.d.ts.map +1 -0
- package/dist/lsp/client.js +166 -0
- package/dist/lsp/client.js.map +1 -0
- package/dist/lsp/detect.d.ts +77 -0
- package/dist/lsp/detect.d.ts.map +1 -0
- package/dist/lsp/detect.js +116 -0
- package/dist/lsp/detect.js.map +1 -0
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +34 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/artifact.d.ts +4 -0
- package/dist/mcp/tools/artifact.d.ts.map +1 -0
- package/dist/mcp/tools/artifact.js +36 -0
- package/dist/mcp/tools/artifact.js.map +1 -0
- package/dist/mcp/tools/history.d.ts +3 -0
- package/dist/mcp/tools/history.d.ts.map +1 -0
- package/dist/mcp/tools/history.js +29 -0
- package/dist/mcp/tools/history.js.map +1 -0
- package/dist/mcp/tools/lsp.d.ts +13 -0
- package/dist/mcp/tools/lsp.d.ts.map +1 -0
- package/dist/mcp/tools/lsp.js +225 -0
- package/dist/mcp/tools/lsp.js.map +1 -0
- package/dist/mcp/tools/plan.d.ts +3 -0
- package/dist/mcp/tools/plan.d.ts.map +1 -0
- package/dist/mcp/tools/plan.js +317 -0
- package/dist/mcp/tools/plan.js.map +1 -0
- package/dist/mcp/tools/task.d.ts +3 -0
- package/dist/mcp/tools/task.d.ts.map +1 -0
- package/dist/mcp/tools/task.js +252 -0
- package/dist/mcp/tools/task.js.map +1 -0
- package/dist/shared/invocations.d.ts +74 -0
- package/dist/shared/invocations.d.ts.map +1 -0
- package/dist/shared/invocations.js +247 -0
- package/dist/shared/invocations.js.map +1 -0
- package/dist/shared/json-store.d.ts +37 -0
- package/dist/shared/json-store.d.ts.map +1 -0
- package/dist/shared/json-store.js +163 -0
- package/dist/shared/json-store.js.map +1 -0
- package/dist/shared/mcp-utils.d.ts +3 -0
- package/dist/shared/mcp-utils.d.ts.map +1 -0
- package/dist/shared/mcp-utils.js +6 -0
- package/dist/shared/mcp-utils.js.map +1 -0
- package/dist/shared/paths.d.ts +21 -0
- package/dist/shared/paths.d.ts.map +1 -0
- package/dist/shared/paths.js +81 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/shared/tool-log.d.ts +8 -0
- package/dist/shared/tool-log.d.ts.map +1 -0
- package/dist/shared/tool-log.js +22 -0
- package/dist/shared/tool-log.js.map +1 -0
- package/dist/types/state.d.ts +862 -0
- package/dist/types/state.d.ts.map +1 -0
- package/dist/types/state.js +66 -0
- package/dist/types/state.js.map +1 -0
- package/docs/consuming/codex-lead-merge.md +106 -0
- package/docs/plugin-guide.md +360 -0
- package/docs/plugin-template/claude/.github/workflows/build.yml +60 -0
- package/docs/plugin-template/claude/README.md +110 -0
- package/docs/plugin-template/claude/package.json +16 -0
- package/docs/plugin-template/codex/.github/workflows/build.yml +51 -0
- package/docs/plugin-template/codex/README.md +147 -0
- package/docs/plugin-template/codex/package.json +17 -0
- package/docs/plugin-template/opencode/.github/workflows/build.yml +61 -0
- package/docs/plugin-template/opencode/README.md +121 -0
- package/docs/plugin-template/opencode/package.json +25 -0
- package/package.json +21 -21
- package/scripts/build-agents.test.ts +1279 -0
- package/scripts/build-agents.ts +978 -0
- package/scripts/build-hooks.test.ts +1385 -0
- package/scripts/build-hooks.ts +584 -0
- package/scripts/cli.test.ts +367 -0
- package/scripts/cli.ts +547 -0
- package/agents/architect/meta.yml +0 -13
- package/agents/designer/meta.yml +0 -13
- package/agents/engineer/meta.yml +0 -11
- package/agents/postdoc/meta.yml +0 -13
- package/agents/researcher/meta.yml +0 -12
- package/agents/reviewer/meta.yml +0 -12
- package/agents/strategist/meta.yml +0 -13
- package/agents/tester/meta.yml +0 -12
- package/agents/writer/meta.yml +0 -11
- package/conformance/README.md +0 -311
- package/conformance/examples/plan.extension.schema.example.json +0 -25
- package/conformance/lifecycle/README.md +0 -48
- package/conformance/lifecycle/agent-complete.json +0 -44
- package/conformance/lifecycle/agent-resume.json +0 -43
- package/conformance/lifecycle/agent-spawn.json +0 -36
- package/conformance/lifecycle/memory-access-record.json +0 -27
- package/conformance/lifecycle/session-end.json +0 -48
- package/conformance/scenarios/full-plan-cycle.json +0 -147
- package/conformance/scenarios/task-deps-ordering.json +0 -95
- package/conformance/schema/fixture.schema.json +0 -354
- package/conformance/state-schemas/agent-tracker.schema.json +0 -63
- package/conformance/state-schemas/history.schema.json +0 -134
- package/conformance/state-schemas/memory-access.schema.json +0 -36
- package/conformance/state-schemas/plan.schema.json +0 -77
- package/conformance/state-schemas/tasks.schema.json +0 -98
- package/conformance/tools/artifact-write.json +0 -97
- package/conformance/tools/context.json +0 -172
- package/conformance/tools/history-search.json +0 -219
- package/conformance/tools/plan-decide.json +0 -139
- package/conformance/tools/plan-start.json +0 -81
- package/conformance/tools/plan-status.json +0 -127
- package/conformance/tools/plan-update.json +0 -341
- package/conformance/tools/task-add.json +0 -156
- package/conformance/tools/task-close.json +0 -161
- package/conformance/tools/task-list.json +0 -177
- package/conformance/tools/task-update.json +0 -167
- package/docs/behavioral-contracts.md +0 -145
- package/docs/consumer-implementation-guide.md +0 -852
- package/docs/memory-lifecycle-contract.md +0 -119
- package/docs/nexus-layout.md +0 -224
- package/docs/nexus-outputs-contract.md +0 -344
- package/docs/nexus-state-overview.md +0 -170
- package/docs/nexus-tools-contract.md +0 -438
- package/manifest.json +0 -449
- package/schema/README.md +0 -69
- package/schema/agent.schema.json +0 -23
- package/schema/common.schema.json +0 -17
- package/schema/manifest.schema.json +0 -78
- package/schema/memory-policy.schema.json +0 -98
- package/schema/skill.schema.json +0 -54
- package/schema/task-exceptions.schema.json +0 -40
- package/schema/vocabulary.schema.json +0 -167
- package/scripts/.gitkeep +0 -0
- package/scripts/conformance-coverage.ts +0 -466
- package/scripts/import-from-claude-nexus.ts +0 -403
- package/scripts/lib/frontmatter.ts +0 -71
- package/scripts/lib/lint.ts +0 -348
- package/scripts/lib/structure.ts +0 -159
- package/scripts/lib/validate.ts +0 -794
- package/scripts/validate.ts +0 -90
- package/skills/nx-init/meta.yml +0 -8
- package/skills/nx-plan/meta.yml +0 -10
- package/skills/nx-run/meta.yml +0 -9
- package/skills/nx-sync/meta.yml +0 -7
- package/vocabulary/capabilities.yml +0 -65
- package/vocabulary/categories.yml +0 -11
- package/vocabulary/invocations.yml +0 -147
- package/vocabulary/memory_policy.yml +0 -88
- package/vocabulary/resume-tiers.yml +0 -11
- package/vocabulary/tags.yml +0 -60
- package/vocabulary/task-exceptions.yml +0 -29
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { readFile, writeFile, mkdir, readdir, rm, rename, stat } from 'node:fs/promises';
|
|
5
|
-
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
6
|
-
import { execSync } from 'node:child_process';
|
|
7
|
-
import { glob } from 'tinyglobby';
|
|
8
|
-
import { parseFrontmatter } from './lib/frontmatter.ts';
|
|
9
|
-
|
|
10
|
-
// Neutral layer allowed fields (bridge §2.1)
|
|
11
|
-
const AGENT_ALLOW_FIELDS = new Set([
|
|
12
|
-
'id', 'name', 'alias_ko', 'description', 'task',
|
|
13
|
-
'category', 'capabilities', 'resume_tier', 'model_tier',
|
|
14
|
-
]);
|
|
15
|
-
const SKILL_ALLOW_FIELDS = new Set([
|
|
16
|
-
'id', 'name', 'description', 'triggers', 'alias_ko', 'manual_only',
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
// Explicitly known drop list (warn + drop)
|
|
20
|
-
const AGENT_DROP_FIELDS = new Set(['maxTurns', 'tags', 'disallowedTools', 'model']);
|
|
21
|
-
// 'disable-model-invocation' is NOT in this set — it is mapped to manual_only below.
|
|
22
|
-
const SKILL_DROP_FIELDS = new Set(['trigger_display', 'purpose']);
|
|
23
|
-
|
|
24
|
-
// model name → model_tier abstraction
|
|
25
|
-
const MODEL_TIER_MAP: Record<string, 'high' | 'standard'> = {
|
|
26
|
-
opus: 'high',
|
|
27
|
-
sonnet: 'standard',
|
|
28
|
-
haiku: 'standard',
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
interface ImportOptions {
|
|
32
|
-
source: string;
|
|
33
|
-
apply: boolean;
|
|
34
|
-
agentsOnly: boolean;
|
|
35
|
-
skillsOnly: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function parseCli(): ImportOptions {
|
|
39
|
-
const args = process.argv.slice(2);
|
|
40
|
-
let source = process.env['NEXUS_CLAUDE_NEXUS_PATH'] ?? path.resolve('..', 'claude-nexus');
|
|
41
|
-
let apply = false;
|
|
42
|
-
let agentsOnly = false;
|
|
43
|
-
let skillsOnly = false;
|
|
44
|
-
for (let i = 0; i < args.length; i++) {
|
|
45
|
-
if (args[i] === '--source' && args[i + 1]) { source = path.resolve(args[++i]!); }
|
|
46
|
-
else if (args[i] === '--apply') { apply = true; }
|
|
47
|
-
else if (args[i] === '--agents-only') { agentsOnly = true; }
|
|
48
|
-
else if (args[i] === '--skills-only') { skillsOnly = true; }
|
|
49
|
-
}
|
|
50
|
-
if (agentsOnly && skillsOnly) {
|
|
51
|
-
throw new Error('--agents-only and --skills-only are mutually exclusive');
|
|
52
|
-
}
|
|
53
|
-
return { source, apply, agentsOnly, skillsOnly };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function verifySourceIsClaudeNexus(source: string): Promise<void> {
|
|
57
|
-
const pkgPath = path.join(source, 'package.json');
|
|
58
|
-
try {
|
|
59
|
-
const pkg = JSON.parse(await readFile(pkgPath, 'utf8')) as { name?: string };
|
|
60
|
-
if (pkg.name !== 'claude-nexus') {
|
|
61
|
-
throw new Error(`source package.json.name='${pkg.name}', expected 'claude-nexus'`);
|
|
62
|
-
}
|
|
63
|
-
} catch (err) {
|
|
64
|
-
throw new Error(`Failed to verify source at ${source}: ${(err as Error).message}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function gitWorkingTreeClean(root: string, scopes: string[]): boolean {
|
|
69
|
-
try {
|
|
70
|
-
const out = execSync(`git status --porcelain ${scopes.join(' ')}`, { cwd: root, encoding: 'utf8' });
|
|
71
|
-
return out.trim().length === 0;
|
|
72
|
-
} catch {
|
|
73
|
-
return true; // directory not yet created, treat as clean
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ---- Body transformation ----
|
|
78
|
-
// <role>...</role> → ## Role ... (top-level section marker only)
|
|
79
|
-
// <constraints>...</constraints> → ## Constraints ...
|
|
80
|
-
// <guidelines>...</guidelines> → ## Guidelines ...
|
|
81
|
-
// inline <example>, <thinking> etc. sub-XML blocks are preserved
|
|
82
|
-
function transformBody(rawBody: string): string {
|
|
83
|
-
const sections: Array<[RegExp, string]> = [
|
|
84
|
-
[/<role>\s*\n?/g, '## Role\n\n'],
|
|
85
|
-
[/<\/role>\s*\n?/g, '\n\n'],
|
|
86
|
-
[/<constraints>\s*\n?/g, '## Constraints\n\n'],
|
|
87
|
-
[/<\/constraints>\s*\n?/g, '\n\n'],
|
|
88
|
-
[/<guidelines>\s*\n?/g, '## Guidelines\n\n'],
|
|
89
|
-
[/<\/guidelines>\s*\n?/g, '\n\n'],
|
|
90
|
-
];
|
|
91
|
-
let result = rawBody;
|
|
92
|
-
for (const [re, replacement] of sections) {
|
|
93
|
-
result = result.replace(re, replacement);
|
|
94
|
-
}
|
|
95
|
-
// Trim excessive blank lines
|
|
96
|
-
return result.replace(/\n{3,}/g, '\n\n').trim() + '\n';
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ---- Capability reverse mapping ----
|
|
100
|
-
// disallowedTools → capabilities reverse mapping
|
|
101
|
-
interface CapabilityEntry {
|
|
102
|
-
id: string;
|
|
103
|
-
harness_mapping: {
|
|
104
|
-
claude_code: string[];
|
|
105
|
-
opencode: string[];
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
interface CapabilitiesFile {
|
|
110
|
-
capabilities: CapabilityEntry[];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function loadCapabilityMap(root: string): Promise<Map<string, string>> {
|
|
114
|
-
const raw = await readFile(path.join(root, 'vocabulary/capabilities.yml'), 'utf8');
|
|
115
|
-
const caps = parseYaml(raw) as CapabilitiesFile;
|
|
116
|
-
const map = new Map<string, string>();
|
|
117
|
-
for (const cap of caps.capabilities) {
|
|
118
|
-
for (const tool of cap.harness_mapping.claude_code) {
|
|
119
|
-
map.set(tool, cap.id);
|
|
120
|
-
}
|
|
121
|
-
for (const tool of cap.harness_mapping.opencode) {
|
|
122
|
-
map.set(tool, cap.id);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return map;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function reverseMapTools(disallowedTools: string[], capMap: Map<string, string>): string[] {
|
|
129
|
-
const capabilitySet = new Set<string>();
|
|
130
|
-
for (const tool of disallowedTools) {
|
|
131
|
-
const cap = capMap.get(tool);
|
|
132
|
-
if (cap) capabilitySet.add(cap);
|
|
133
|
-
}
|
|
134
|
-
return Array.from(capabilitySet).sort();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// ---- Agent transformation ----
|
|
138
|
-
interface TransformedAgent {
|
|
139
|
-
id: string;
|
|
140
|
-
meta: Record<string, unknown>;
|
|
141
|
-
body: string;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function transformAgent(
|
|
145
|
-
sourcePath: string,
|
|
146
|
-
capMap: Map<string, string>,
|
|
147
|
-
warnings: string[]
|
|
148
|
-
): Promise<TransformedAgent | null> {
|
|
149
|
-
const source = await readFile(sourcePath, 'utf8');
|
|
150
|
-
const { data, content } = parseFrontmatter(source);
|
|
151
|
-
const fileName = path.basename(sourcePath, '.md');
|
|
152
|
-
|
|
153
|
-
const meta: Record<string, unknown> = {};
|
|
154
|
-
for (const [key, value] of Object.entries(data)) {
|
|
155
|
-
if (AGENT_ALLOW_FIELDS.has(key)) {
|
|
156
|
-
if (key === 'capabilities') continue; // will be computed from disallowedTools
|
|
157
|
-
meta[key] = value;
|
|
158
|
-
} else if (AGENT_DROP_FIELDS.has(key)) {
|
|
159
|
-
warnings.push(` ${fileName}: dropping known field '${key}'`);
|
|
160
|
-
} else {
|
|
161
|
-
throw new Error(`${fileName}: unknown field '${key}' — add to allow list or drop list`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// model → model_tier
|
|
166
|
-
if (typeof data['model'] === 'string') {
|
|
167
|
-
const tier = MODEL_TIER_MAP[data['model']];
|
|
168
|
-
if (!tier) throw new Error(`${fileName}: unknown model '${data['model']}'`);
|
|
169
|
-
meta['model_tier'] = tier;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// disallowedTools → capabilities
|
|
173
|
-
const disallowed = Array.isArray(data['disallowedTools']) ? data['disallowedTools'] : [];
|
|
174
|
-
meta['capabilities'] = reverseMapTools(disallowed as string[], capMap);
|
|
175
|
-
|
|
176
|
-
// Ensure id present (use filename if not in frontmatter)
|
|
177
|
-
if (!meta['id']) meta['id'] = fileName;
|
|
178
|
-
|
|
179
|
-
// name default to id
|
|
180
|
-
if (!meta['name']) meta['name'] = meta['id'];
|
|
181
|
-
|
|
182
|
-
const body = transformBody(content);
|
|
183
|
-
|
|
184
|
-
return { id: meta['id'] as string, meta, body };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ---- Skill transformation ----
|
|
188
|
-
interface TransformedSkill {
|
|
189
|
-
id: string;
|
|
190
|
-
meta: Record<string, unknown>;
|
|
191
|
-
body: string;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async function transformSkill(
|
|
195
|
-
sourceDir: string,
|
|
196
|
-
warnings: string[]
|
|
197
|
-
): Promise<TransformedSkill | null> {
|
|
198
|
-
const skillFile = path.join(sourceDir, 'SKILL.md');
|
|
199
|
-
const source = await readFile(skillFile, 'utf8');
|
|
200
|
-
const { data, content } = parseFrontmatter(source);
|
|
201
|
-
const skillId = path.basename(sourceDir);
|
|
202
|
-
|
|
203
|
-
const meta: Record<string, unknown> = {};
|
|
204
|
-
for (const [key, value] of Object.entries(data)) {
|
|
205
|
-
if (SKILL_ALLOW_FIELDS.has(key)) {
|
|
206
|
-
meta[key] = value;
|
|
207
|
-
} else if (SKILL_DROP_FIELDS.has(key)) {
|
|
208
|
-
warnings.push(` ${skillId}: dropping known field '${key}'`);
|
|
209
|
-
} else if (key === 'disable-model-invocation') {
|
|
210
|
-
// Map to manual_only
|
|
211
|
-
meta['manual_only'] = Boolean(value);
|
|
212
|
-
warnings.push(` ${skillId}: mapping disable-model-invocation → manual_only`);
|
|
213
|
-
} else {
|
|
214
|
-
throw new Error(`${skillId}: unknown field '${key}' — add to allow list or drop list`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Normalize triggers: bracket strings → tag ids; slash commands → drop + manual_only
|
|
219
|
-
if (Array.isArray(meta['triggers'])) {
|
|
220
|
-
const rawTriggers = meta['triggers'] as string[];
|
|
221
|
-
// Bracket triggers (e.g., [plan], [plan:auto]): strip brackets, split on ':' for parent id
|
|
222
|
-
const bracketTriggers = rawTriggers
|
|
223
|
-
.filter((t) => t.startsWith('[') && t.endsWith(']'))
|
|
224
|
-
.map((t) => t.slice(1, -1).split(':')[0]!)
|
|
225
|
-
.filter((t, i, arr) => arr.indexOf(t) === i); // dedupe
|
|
226
|
-
// Slash command triggers (e.g., /claude-nexus:nx-init): harness-specific, drop them
|
|
227
|
-
const slashTriggers = rawTriggers.filter((t) => t.startsWith('/'));
|
|
228
|
-
if (slashTriggers.length > 0) {
|
|
229
|
-
warnings.push(` ${skillId}: slash command triggers detected (${slashTriggers.join(', ')}) → dropping + setting manual_only: true`);
|
|
230
|
-
meta['manual_only'] = true;
|
|
231
|
-
}
|
|
232
|
-
if (bracketTriggers.length > 0) {
|
|
233
|
-
meta['triggers'] = bracketTriggers;
|
|
234
|
-
} else {
|
|
235
|
-
delete meta['triggers'];
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (!meta['id']) meta['id'] = skillId;
|
|
240
|
-
if (!meta['name']) meta['name'] = meta['id'];
|
|
241
|
-
|
|
242
|
-
const body = transformBody(content);
|
|
243
|
-
|
|
244
|
-
return { id: meta['id'] as string, meta, body };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// ---- Staging + atomic rename ----
|
|
248
|
-
async function writeToStaging(
|
|
249
|
-
root: string,
|
|
250
|
-
kind: 'agents' | 'skills',
|
|
251
|
-
items: Array<{ id: string; meta: Record<string, unknown>; body: string }>
|
|
252
|
-
): Promise<void> {
|
|
253
|
-
const stagingDir = path.join(root, `${kind}.staging`);
|
|
254
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
255
|
-
await mkdir(stagingDir, { recursive: true });
|
|
256
|
-
for (const item of items) {
|
|
257
|
-
const dir = path.join(stagingDir, item.id);
|
|
258
|
-
await mkdir(dir, { recursive: true });
|
|
259
|
-
await writeFile(path.join(dir, 'meta.yml'), stringifyYaml(item.meta));
|
|
260
|
-
await writeFile(path.join(dir, 'body.md'), item.body);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async function atomicSwap(root: string, kind: 'agents' | 'skills'): Promise<void> {
|
|
265
|
-
const live = path.join(root, kind);
|
|
266
|
-
const staging = path.join(root, `${kind}.staging`);
|
|
267
|
-
const backup = path.join(root, `${kind}.backup`);
|
|
268
|
-
// If live exists, move it to backup
|
|
269
|
-
try {
|
|
270
|
-
await stat(live);
|
|
271
|
-
await rm(backup, { recursive: true, force: true });
|
|
272
|
-
await rename(live, backup);
|
|
273
|
-
} catch {
|
|
274
|
-
// live doesn't exist — first bootstrap
|
|
275
|
-
}
|
|
276
|
-
await rename(staging, live);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async function cleanupBackup(root: string, kind: 'agents' | 'skills'): Promise<void> {
|
|
280
|
-
const backup = path.join(root, `${kind}.backup`);
|
|
281
|
-
await rm(backup, { recursive: true, force: true });
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
async function restoreBackup(root: string, kind: 'agents' | 'skills'): Promise<void> {
|
|
285
|
-
const live = path.join(root, kind);
|
|
286
|
-
const backup = path.join(root, `${kind}.backup`);
|
|
287
|
-
try {
|
|
288
|
-
await rm(live, { recursive: true, force: true });
|
|
289
|
-
await rename(backup, live);
|
|
290
|
-
} catch {
|
|
291
|
-
/* nothing to restore */
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// ---- Main ----
|
|
296
|
-
async function main(): Promise<void> {
|
|
297
|
-
const opts = parseCli();
|
|
298
|
-
const root = process.cwd();
|
|
299
|
-
|
|
300
|
-
console.log('# nexus-core import-from-claude-nexus');
|
|
301
|
-
console.log(`Source: ${opts.source}`);
|
|
302
|
-
console.log(`Target: ${root}`);
|
|
303
|
-
console.log(`Mode: ${opts.apply ? 'APPLY' : 'DRY-RUN'}`);
|
|
304
|
-
console.log('');
|
|
305
|
-
|
|
306
|
-
// Verify source
|
|
307
|
-
await verifySourceIsClaudeNexus(opts.source);
|
|
308
|
-
|
|
309
|
-
// Git working tree dirty check
|
|
310
|
-
if (opts.apply) {
|
|
311
|
-
const scopes = ['agents/', 'skills/', 'manifest.json'];
|
|
312
|
-
if (!gitWorkingTreeClean(root, scopes)) {
|
|
313
|
-
throw new Error('Working tree has uncommitted changes in agents/, skills/, or manifest.json. Commit or stash first.');
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Load capability map
|
|
318
|
-
const capMap = await loadCapabilityMap(root);
|
|
319
|
-
|
|
320
|
-
// Collect source files
|
|
321
|
-
const agentFiles = opts.skillsOnly
|
|
322
|
-
? []
|
|
323
|
-
: await glob(['agents/*.md'], { cwd: opts.source, absolute: true });
|
|
324
|
-
const skillDirs = opts.agentsOnly
|
|
325
|
-
? []
|
|
326
|
-
: await glob(['skills/*'], { cwd: opts.source, absolute: true, onlyDirectories: true });
|
|
327
|
-
|
|
328
|
-
const warnings: string[] = [];
|
|
329
|
-
const transformedAgents: TransformedAgent[] = [];
|
|
330
|
-
const transformedSkills: TransformedSkill[] = [];
|
|
331
|
-
|
|
332
|
-
// Transform agents
|
|
333
|
-
for (const f of agentFiles) {
|
|
334
|
-
const agent = await transformAgent(f, capMap, warnings);
|
|
335
|
-
if (agent) transformedAgents.push(agent);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Transform skills
|
|
339
|
-
for (const d of skillDirs) {
|
|
340
|
-
const skill = await transformSkill(d, warnings);
|
|
341
|
-
if (skill) transformedSkills.push(skill);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
console.log('# Transformation summary');
|
|
345
|
-
console.log(`Agents: ${transformedAgents.length}`);
|
|
346
|
-
console.log(`Skills: ${transformedSkills.length}`);
|
|
347
|
-
if (warnings.length > 0) {
|
|
348
|
-
console.log('# Warnings');
|
|
349
|
-
for (const w of warnings) console.log(w);
|
|
350
|
-
}
|
|
351
|
-
console.log('');
|
|
352
|
-
|
|
353
|
-
if (!opts.apply) {
|
|
354
|
-
console.log('# Files that would be written (dry-run):');
|
|
355
|
-
for (const a of transformedAgents) {
|
|
356
|
-
console.log(` agents/${a.id}/meta.yml`);
|
|
357
|
-
console.log(` agents/${a.id}/body.md`);
|
|
358
|
-
}
|
|
359
|
-
for (const s of transformedSkills) {
|
|
360
|
-
console.log(` skills/${s.id}/meta.yml`);
|
|
361
|
-
console.log(` skills/${s.id}/body.md`);
|
|
362
|
-
}
|
|
363
|
-
console.log('');
|
|
364
|
-
console.log('Run with --apply to write.');
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Apply — all-or-nothing transaction via staging
|
|
369
|
-
try {
|
|
370
|
-
if (!opts.skillsOnly) {
|
|
371
|
-
await writeToStaging(root, 'agents', transformedAgents);
|
|
372
|
-
await atomicSwap(root, 'agents');
|
|
373
|
-
}
|
|
374
|
-
if (!opts.agentsOnly) {
|
|
375
|
-
await writeToStaging(root, 'skills', transformedSkills);
|
|
376
|
-
await atomicSwap(root, 'skills');
|
|
377
|
-
}
|
|
378
|
-
} catch (err) {
|
|
379
|
-
console.error('Import failed during write phase. Attempting restore...');
|
|
380
|
-
if (!opts.skillsOnly) await restoreBackup(root, 'agents');
|
|
381
|
-
if (!opts.agentsOnly) await restoreBackup(root, 'skills');
|
|
382
|
-
throw err;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Cleanup backup on success
|
|
386
|
-
if (!opts.skillsOnly) await cleanupBackup(root, 'agents');
|
|
387
|
-
if (!opts.agentsOnly) await cleanupBackup(root, 'skills');
|
|
388
|
-
|
|
389
|
-
console.log('');
|
|
390
|
-
console.log('Import complete. Running validate...');
|
|
391
|
-
// Auto-validate after apply — run validate via spawnSync
|
|
392
|
-
const { spawnSync } = await import('node:child_process');
|
|
393
|
-
const res = spawnSync('bun', ['run', 'validate'], { cwd: root, stdio: 'inherit' });
|
|
394
|
-
if (res.status !== 0) {
|
|
395
|
-
throw new Error('Validation failed after import. Check output above.');
|
|
396
|
-
}
|
|
397
|
-
console.log('Import + validation complete.');
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
main().catch((err) => {
|
|
401
|
-
console.error('Error:', (err as Error).message);
|
|
402
|
-
process.exit(1);
|
|
403
|
-
});
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { parse as parseYaml } from 'yaml';
|
|
2
|
-
|
|
3
|
-
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
|
|
4
|
-
|
|
5
|
-
export interface ParsedFrontmatter {
|
|
6
|
-
/** parsed YAML object */
|
|
7
|
-
data: Record<string, unknown>;
|
|
8
|
-
/** body content (after closing ---) */
|
|
9
|
-
content: string;
|
|
10
|
-
/** 1-based line number where frontmatter YAML starts (after opening ---) */
|
|
11
|
-
frontmatterStartLine: number;
|
|
12
|
-
/** 1-based line number where body content starts (after closing ---) */
|
|
13
|
-
contentStartLine: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Parses a markdown file with optional YAML frontmatter.
|
|
18
|
-
*
|
|
19
|
-
* Format:
|
|
20
|
-
* ---
|
|
21
|
-
* {frontmatter YAML}
|
|
22
|
-
* ---
|
|
23
|
-
* {body content}
|
|
24
|
-
*
|
|
25
|
-
* If no frontmatter is present, returns {data: {}, content: source, frontmatterStartLine: 0, contentStartLine: 1}.
|
|
26
|
-
*
|
|
27
|
-
* Line numbers are 1-based to match editor conventions.
|
|
28
|
-
*/
|
|
29
|
-
export function parseFrontmatter(source: string): ParsedFrontmatter {
|
|
30
|
-
const match = source.match(FRONTMATTER_RE);
|
|
31
|
-
if (!match) {
|
|
32
|
-
return {
|
|
33
|
-
data: {},
|
|
34
|
-
content: source,
|
|
35
|
-
frontmatterStartLine: 0,
|
|
36
|
-
contentStartLine: 1,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const frontmatterText = match[1];
|
|
41
|
-
const body = match[2];
|
|
42
|
-
const parsed = parseYaml(frontmatterText);
|
|
43
|
-
|
|
44
|
-
// Opening '---' is line 1, frontmatter YAML starts at line 2
|
|
45
|
-
const frontmatterStartLine = 2;
|
|
46
|
-
// Count lines in frontmatter text + 2 (opening --- and closing ---)
|
|
47
|
-
const frontmatterLineCount = frontmatterText.split('\n').length;
|
|
48
|
-
const contentStartLine = frontmatterStartLine + frontmatterLineCount + 1; // +1 for closing ---
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
data: (parsed ?? {}) as Record<string, unknown>,
|
|
52
|
-
content: body,
|
|
53
|
-
frontmatterStartLine,
|
|
54
|
-
contentStartLine,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Reverse: translates a line number within the frontmatter YAML (1-based, as reported
|
|
60
|
-
* by a YAML parser operating on the frontmatter text alone) back to the line in the
|
|
61
|
-
* original source file.
|
|
62
|
-
*/
|
|
63
|
-
export function frontmatterLineToSourceLine(
|
|
64
|
-
parsed: ParsedFrontmatter,
|
|
65
|
-
frontmatterLine: number
|
|
66
|
-
): number {
|
|
67
|
-
if (parsed.frontmatterStartLine === 0) {
|
|
68
|
-
throw new Error('Source has no frontmatter');
|
|
69
|
-
}
|
|
70
|
-
return parsed.frontmatterStartLine + frontmatterLine - 1;
|
|
71
|
-
}
|