marcos-ai-bootstrap 0.1.0 → 0.1.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/.agents/skills/implement/SKILL.md +48 -48
- package/.agents/skills/initialize/SKILL.md +50 -50
- package/.agents/skills/planner/SKILL.md +30 -30
- package/.agents/skills/watch-ci/SKILL.md +49 -49
- package/.claude/agents/code-claude.md +18 -18
- package/.claude/agents/docs-claude.md +14 -14
- package/.claude/agents/explorer-claude.md +14 -14
- package/.claude/agents/infra-claude.md +17 -17
- package/.claude/agents/investigate-claude.md +25 -25
- package/.claude/agents/log-reader-claude.md +21 -21
- package/.claude/agents/planner-claude.md +37 -37
- package/.claude/agents/planner-discovery-claude.md +24 -24
- package/.claude/agents/test-runner-claude.md +16 -16
- package/.claude/agents/triage-claude.md +46 -46
- package/.claude/skills/implement/SKILL.md +48 -48
- package/.claude/skills/initialize/SKILL.md +50 -50
- package/.claude/skills/planner/SKILL.md +30 -30
- package/.claude/skills/watch-ci/SKILL.md +49 -49
- package/.codex/agents/code-codex.toml +19 -19
- package/.codex/agents/docs-codex.toml +16 -16
- package/.codex/agents/explorer-codex.toml +15 -15
- package/.codex/agents/infra-codex.toml +18 -18
- package/.codex/agents/investigate-codex.toml +25 -25
- package/.codex/agents/log-reader-codex.toml +22 -22
- package/.codex/agents/planner-codex.toml +37 -37
- package/.codex/agents/planner-discovery-codex.toml +24 -24
- package/.codex/agents/test-runner-codex.toml +17 -17
- package/.codex/agents/triage-codex.toml +46 -46
- package/.github/agents/code-copilot.agent.md +18 -18
- package/.github/agents/docs-copilot.agent.md +14 -14
- package/.github/agents/explorer-copilot.agent.md +14 -14
- package/.github/agents/infra-copilot.agent.md +17 -17
- package/.github/agents/investigate-copilot.agent.md +25 -25
- package/.github/agents/log-reader-copilot.agent.md +21 -21
- package/.github/agents/planner-copilot.agent.md +37 -37
- package/.github/agents/planner-discovery-copilot.agent.md +24 -24
- package/.github/agents/test-runner-copilot.agent.md +16 -16
- package/.github/agents/triage-copilot.agent.md +46 -46
- package/.github/skills/implement/SKILL.md +44 -44
- package/.github/skills/initialize/SKILL.md +52 -52
- package/.github/skills/planner/SKILL.md +30 -30
- package/.github/skills/watch-ci/SKILL.md +47 -47
- package/LICENSE +21 -21
- package/README.md +116 -117
- package/package.json +37 -37
- package/src/AGENTS.md +200 -200
- package/src/HUMAN.md +31 -31
- package/src/bin/ai-bootstrap.js +115 -115
- package/src/lib/materialize.js +140 -140
package/src/bin/ai-bootstrap.js
CHANGED
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const { materialize, TOOLS } = require("../lib/materialize");
|
|
6
|
-
|
|
7
|
-
const TOOL_NAMES = Object.keys(TOOLS);
|
|
8
|
-
|
|
9
|
-
const HELP = `
|
|
10
|
-
marcos-ai-bootstrap - materialise a tool-agnostic AI agent/skill network into any repository
|
|
11
|
-
|
|
12
|
-
Usage:
|
|
13
|
-
marcos-ai-bootstrap [--claude] [--codex] [--copilot] [--all] [options]
|
|
14
|
-
|
|
15
|
-
Tool flags (combine as many as you like):
|
|
16
|
-
--claude Claude Code agents (.claude/agents), skills (.claude/skills), CLAUDE.md
|
|
17
|
-
--codex Codex agents (.codex/agents), skills (.agents/skills)
|
|
18
|
-
--copilot GitHub Copilot CLI agents (.github/agents), skills (.github/skills),
|
|
19
|
-
.github/copilot-instructions.md
|
|
20
|
-
--all All of the above
|
|
21
|
-
|
|
22
|
-
Always written alongside any tool flag:
|
|
23
|
-
AGENTS.md, HUMAN.md (tool-agnostic rules + human guide)
|
|
24
|
-
|
|
25
|
-
Options:
|
|
26
|
-
--dest <path> Target directory (default: current working directory)
|
|
27
|
-
--force Overwrite files that already exist (default: skip existing files)
|
|
28
|
-
--dry-run Show what would be written without writing anything
|
|
29
|
-
-h, --help Show this help text
|
|
30
|
-
|
|
31
|
-
Examples:
|
|
32
|
-
npx marcos-ai-bootstrap --copilot
|
|
33
|
-
npx marcos-ai-bootstrap --claude --codex --dry-run
|
|
34
|
-
npx marcos-ai-bootstrap --all --force
|
|
35
|
-
`;
|
|
36
|
-
|
|
37
|
-
function parseArgs(argv) {
|
|
38
|
-
const opts = { tools: [], dest: undefined, force: false, dryRun: false, help: false };
|
|
39
|
-
for (let i = 0; i < argv.length; i++) {
|
|
40
|
-
const arg = argv[i];
|
|
41
|
-
switch (arg) {
|
|
42
|
-
case "--all":
|
|
43
|
-
opts.tools = TOOL_NAMES.slice();
|
|
44
|
-
break;
|
|
45
|
-
case "--claude":
|
|
46
|
-
case "--codex":
|
|
47
|
-
case "--copilot":
|
|
48
|
-
opts.tools.push(arg.slice(2));
|
|
49
|
-
break;
|
|
50
|
-
case "--dest":
|
|
51
|
-
opts.dest = argv[++i];
|
|
52
|
-
break;
|
|
53
|
-
case "--force":
|
|
54
|
-
opts.force = true;
|
|
55
|
-
break;
|
|
56
|
-
case "--dry-run":
|
|
57
|
-
opts.dryRun = true;
|
|
58
|
-
break;
|
|
59
|
-
case "-h":
|
|
60
|
-
case "--help":
|
|
61
|
-
opts.help = true;
|
|
62
|
-
break;
|
|
63
|
-
default:
|
|
64
|
-
console.error(`Unknown argument: ${arg}\n`);
|
|
65
|
-
opts.help = true;
|
|
66
|
-
opts.unknown = true;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
opts.tools = Array.from(new Set(opts.tools));
|
|
70
|
-
return opts;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function main() {
|
|
74
|
-
const opts = parseArgs(process.argv.slice(2));
|
|
75
|
-
|
|
76
|
-
if (opts.help || opts.tools.length === 0) {
|
|
77
|
-
console.log(HELP);
|
|
78
|
-
process.exit(opts.unknown || opts.tools.length === 0 ? 1 : 0);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const destRoot = path.resolve(opts.dest || process.cwd());
|
|
82
|
-
const { results } = materialize(opts.tools, {
|
|
83
|
-
destRoot,
|
|
84
|
-
force: opts.force,
|
|
85
|
-
dryRun: opts.dryRun,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const toolLabels = opts.tools.map((t) => TOOLS[t].label).join(", ");
|
|
89
|
-
console.log(
|
|
90
|
-
`${opts.dryRun ? "[dry-run] " : ""}Materialising ${toolLabels} into ${destRoot}\n`
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
const byStatus = { created: [], overwritten: [], "skipped-exists": [], "missing-source": [] };
|
|
94
|
-
for (const r of results) {
|
|
95
|
-
(byStatus[r.status] || (byStatus[r.status] = [])).push(r.relPath);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
for (const r of byStatus.created) console.log(` created ${r}`);
|
|
99
|
-
for (const r of byStatus.overwritten) console.log(` overwritten ${r}`);
|
|
100
|
-
for (const r of byStatus["skipped-exists"]) console.log(` skipped ${r} (already exists, use --force to overwrite)`);
|
|
101
|
-
for (const r of byStatus["missing-source"]) console.log(` MISSING ${r} (not bundled in this package)`);
|
|
102
|
-
|
|
103
|
-
console.log(
|
|
104
|
-
`\n${byStatus.created.length} created, ${byStatus.overwritten.length} overwritten, ` +
|
|
105
|
-
`${byStatus["skipped-exists"].length} skipped, ${results.length} total.`
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
if (!opts.dryRun) {
|
|
109
|
-
console.log(
|
|
110
|
-
`\nDone. Open AGENTS.md / HUMAN.md, then start your AI tool in ${destRoot} to begin.`
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { materialize, TOOLS } = require("../lib/materialize");
|
|
6
|
+
|
|
7
|
+
const TOOL_NAMES = Object.keys(TOOLS);
|
|
8
|
+
|
|
9
|
+
const HELP = `
|
|
10
|
+
marcos-ai-bootstrap - materialise a tool-agnostic AI agent/skill network into any repository
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
marcos-ai-bootstrap [--claude] [--codex] [--copilot] [--all] [options]
|
|
14
|
+
|
|
15
|
+
Tool flags (combine as many as you like):
|
|
16
|
+
--claude Claude Code agents (.claude/agents), skills (.claude/skills), CLAUDE.md
|
|
17
|
+
--codex Codex agents (.codex/agents), skills (.agents/skills)
|
|
18
|
+
--copilot GitHub Copilot CLI agents (.github/agents), skills (.github/skills),
|
|
19
|
+
.github/copilot-instructions.md
|
|
20
|
+
--all All of the above
|
|
21
|
+
|
|
22
|
+
Always written alongside any tool flag:
|
|
23
|
+
AGENTS.md, HUMAN.md (tool-agnostic rules + human guide)
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
--dest <path> Target directory (default: current working directory)
|
|
27
|
+
--force Overwrite files that already exist (default: skip existing files)
|
|
28
|
+
--dry-run Show what would be written without writing anything
|
|
29
|
+
-h, --help Show this help text
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
npx marcos-ai-bootstrap --copilot
|
|
33
|
+
npx marcos-ai-bootstrap --claude --codex --dry-run
|
|
34
|
+
npx marcos-ai-bootstrap --all --force
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
function parseArgs(argv) {
|
|
38
|
+
const opts = { tools: [], dest: undefined, force: false, dryRun: false, help: false };
|
|
39
|
+
for (let i = 0; i < argv.length; i++) {
|
|
40
|
+
const arg = argv[i];
|
|
41
|
+
switch (arg) {
|
|
42
|
+
case "--all":
|
|
43
|
+
opts.tools = TOOL_NAMES.slice();
|
|
44
|
+
break;
|
|
45
|
+
case "--claude":
|
|
46
|
+
case "--codex":
|
|
47
|
+
case "--copilot":
|
|
48
|
+
opts.tools.push(arg.slice(2));
|
|
49
|
+
break;
|
|
50
|
+
case "--dest":
|
|
51
|
+
opts.dest = argv[++i];
|
|
52
|
+
break;
|
|
53
|
+
case "--force":
|
|
54
|
+
opts.force = true;
|
|
55
|
+
break;
|
|
56
|
+
case "--dry-run":
|
|
57
|
+
opts.dryRun = true;
|
|
58
|
+
break;
|
|
59
|
+
case "-h":
|
|
60
|
+
case "--help":
|
|
61
|
+
opts.help = true;
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
console.error(`Unknown argument: ${arg}\n`);
|
|
65
|
+
opts.help = true;
|
|
66
|
+
opts.unknown = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
opts.tools = Array.from(new Set(opts.tools));
|
|
70
|
+
return opts;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function main() {
|
|
74
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
75
|
+
|
|
76
|
+
if (opts.help || opts.tools.length === 0) {
|
|
77
|
+
console.log(HELP);
|
|
78
|
+
process.exit(opts.unknown || opts.tools.length === 0 ? 1 : 0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const destRoot = path.resolve(opts.dest || process.cwd());
|
|
82
|
+
const { results } = materialize(opts.tools, {
|
|
83
|
+
destRoot,
|
|
84
|
+
force: opts.force,
|
|
85
|
+
dryRun: opts.dryRun,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const toolLabels = opts.tools.map((t) => TOOLS[t].label).join(", ");
|
|
89
|
+
console.log(
|
|
90
|
+
`${opts.dryRun ? "[dry-run] " : ""}Materialising ${toolLabels} into ${destRoot}\n`
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const byStatus = { created: [], overwritten: [], "skipped-exists": [], "missing-source": [] };
|
|
94
|
+
for (const r of results) {
|
|
95
|
+
(byStatus[r.status] || (byStatus[r.status] = [])).push(r.relPath);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const r of byStatus.created) console.log(` created ${r}`);
|
|
99
|
+
for (const r of byStatus.overwritten) console.log(` overwritten ${r}`);
|
|
100
|
+
for (const r of byStatus["skipped-exists"]) console.log(` skipped ${r} (already exists, use --force to overwrite)`);
|
|
101
|
+
for (const r of byStatus["missing-source"]) console.log(` MISSING ${r} (not bundled in this package)`);
|
|
102
|
+
|
|
103
|
+
console.log(
|
|
104
|
+
`\n${byStatus.created.length} created, ${byStatus.overwritten.length} overwritten, ` +
|
|
105
|
+
`${byStatus["skipped-exists"].length} skipped, ${results.length} total.`
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (!opts.dryRun) {
|
|
109
|
+
console.log(
|
|
110
|
+
`\nDone. Open AGENTS.md / HUMAN.md, then start your AI tool in ${destRoot} to begin.`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
main();
|
package/src/lib/materialize.js
CHANGED
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
|
|
6
|
-
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
7
|
-
|
|
8
|
-
// Canonical, shipped source-of-truth core files live under src/ so they stay
|
|
9
|
-
// separate from this repo's own self-hosting AGENTS.md/HUMAN.md at the root.
|
|
10
|
-
// Each entry maps the package-relative source to the target-relative destination.
|
|
11
|
-
const CORE_FILES = [
|
|
12
|
-
{ src: "src/AGENTS.md", dest: "AGENTS.md" },
|
|
13
|
-
{ src: "src/HUMAN.md", dest: "HUMAN.md" },
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
const TOOLS = {
|
|
17
|
-
claude: {
|
|
18
|
-
label: "Claude Code",
|
|
19
|
-
dirs: [".claude/agents", ".claude/skills"],
|
|
20
|
-
entry: {
|
|
21
|
-
file: "CLAUDE.md",
|
|
22
|
-
content: "@AGENTS.md\n",
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
codex: {
|
|
26
|
-
label: "Codex",
|
|
27
|
-
dirs: [".codex/agents", ".agents/skills"],
|
|
28
|
-
// Codex loads AGENTS.md directly from the workspace root; no separate
|
|
29
|
-
// config entry-point file is required.
|
|
30
|
-
},
|
|
31
|
-
copilot: {
|
|
32
|
-
label: "GitHub Copilot CLI",
|
|
33
|
-
dirs: [".github/agents", ".github/skills"],
|
|
34
|
-
entry: {
|
|
35
|
-
file: path.join(".github", "copilot-instructions.md"),
|
|
36
|
-
content:
|
|
37
|
-
"# Copilot instructions\n\n" +
|
|
38
|
-
"See [AGENTS.md](../AGENTS.md) for the agent rules and the agent/skill " +
|
|
39
|
-
"network that has been materialised into this repository.\n",
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Recursively list files under a directory (relative posix-ish paths).
|
|
46
|
-
*/
|
|
47
|
-
function listFiles(dir) {
|
|
48
|
-
const results = [];
|
|
49
|
-
if (!fs.existsSync(dir)) return results;
|
|
50
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
51
|
-
const full = path.join(dir, entry.name);
|
|
52
|
-
if (entry.isDirectory()) {
|
|
53
|
-
results.push(...listFiles(full));
|
|
54
|
-
} else {
|
|
55
|
-
results.push(full);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return results;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Copy a single file from the package root to the destination root. The source
|
|
63
|
-
* and destination relative paths may differ (e.g. src/AGENTS.md -> AGENTS.md).
|
|
64
|
-
* Returns a status object keyed by the destination path.
|
|
65
|
-
*/
|
|
66
|
-
function copyOne(srcRel, destRel, destRoot, { force, dryRun }) {
|
|
67
|
-
const src = path.join(PACKAGE_ROOT, srcRel);
|
|
68
|
-
const dest = path.join(destRoot, destRel);
|
|
69
|
-
|
|
70
|
-
if (!fs.existsSync(src)) {
|
|
71
|
-
return { relPath: destRel, status: "missing-source" };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const exists = fs.existsSync(dest);
|
|
75
|
-
if (exists && !force) {
|
|
76
|
-
return { relPath: destRel, status: "skipped-exists" };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!dryRun) {
|
|
80
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
81
|
-
fs.copyFileSync(src, dest);
|
|
82
|
-
}
|
|
83
|
-
return { relPath: destRel, status: exists ? "overwritten" : "created" };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Write a small generated entry-point file (e.g. CLAUDE.md,
|
|
88
|
-
* .github/copilot-instructions.md) rather than copying from the package.
|
|
89
|
-
*/
|
|
90
|
-
function writeEntry(entry, destRoot, { force, dryRun }) {
|
|
91
|
-
const dest = path.join(destRoot, entry.file);
|
|
92
|
-
const exists = fs.existsSync(dest);
|
|
93
|
-
if (exists && !force) {
|
|
94
|
-
return { relPath: entry.file, status: "skipped-exists" };
|
|
95
|
-
}
|
|
96
|
-
if (!dryRun) {
|
|
97
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
98
|
-
fs.writeFileSync(dest, entry.content, "utf8");
|
|
99
|
-
}
|
|
100
|
-
return { relPath: entry.file, status: exists ? "overwritten" : "created" };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Materialise the requested tools' agent/skill files, plus the tool-agnostic
|
|
105
|
-
* core files (AGENTS.md, HUMAN.md), into destRoot.
|
|
106
|
-
*
|
|
107
|
-
* @param {string[]} tools - subset of Object.keys(TOOLS)
|
|
108
|
-
* @param {object} opts - { destRoot, force, dryRun }
|
|
109
|
-
* @returns {{results: Array, tools: string[]}}
|
|
110
|
-
*/
|
|
111
|
-
function materialize(tools, opts = {}) {
|
|
112
|
-
const destRoot = path.resolve(opts.destRoot || process.cwd());
|
|
113
|
-
const force = !!opts.force;
|
|
114
|
-
const dryRun = !!opts.dryRun;
|
|
115
|
-
|
|
116
|
-
const results = [];
|
|
117
|
-
|
|
118
|
-
for (const cf of CORE_FILES) {
|
|
119
|
-
results.push(copyOne(cf.src, cf.dest, destRoot, { force, dryRun }));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
for (const toolName of tools) {
|
|
123
|
-
const tool = TOOLS[toolName];
|
|
124
|
-
if (!tool) continue;
|
|
125
|
-
for (const dir of tool.dirs) {
|
|
126
|
-
const files = listFiles(path.join(PACKAGE_ROOT, dir));
|
|
127
|
-
for (const abs of files) {
|
|
128
|
-
const relPath = path.relative(PACKAGE_ROOT, abs);
|
|
129
|
-
results.push(copyOne(relPath, relPath, destRoot, { force, dryRun }));
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (tool.entry) {
|
|
133
|
-
results.push(writeEntry(tool.entry, destRoot, { force, dryRun }));
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return { results, tools, destRoot };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
module.exports = { materialize, TOOLS, CORE_FILES, PACKAGE_ROOT };
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
7
|
+
|
|
8
|
+
// Canonical, shipped source-of-truth core files live under src/ so they stay
|
|
9
|
+
// separate from this repo's own self-hosting AGENTS.md/HUMAN.md at the root.
|
|
10
|
+
// Each entry maps the package-relative source to the target-relative destination.
|
|
11
|
+
const CORE_FILES = [
|
|
12
|
+
{ src: "src/AGENTS.md", dest: "AGENTS.md" },
|
|
13
|
+
{ src: "src/HUMAN.md", dest: "HUMAN.md" },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const TOOLS = {
|
|
17
|
+
claude: {
|
|
18
|
+
label: "Claude Code",
|
|
19
|
+
dirs: [".claude/agents", ".claude/skills"],
|
|
20
|
+
entry: {
|
|
21
|
+
file: "CLAUDE.md",
|
|
22
|
+
content: "@AGENTS.md\n",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
codex: {
|
|
26
|
+
label: "Codex",
|
|
27
|
+
dirs: [".codex/agents", ".agents/skills"],
|
|
28
|
+
// Codex loads AGENTS.md directly from the workspace root; no separate
|
|
29
|
+
// config entry-point file is required.
|
|
30
|
+
},
|
|
31
|
+
copilot: {
|
|
32
|
+
label: "GitHub Copilot CLI",
|
|
33
|
+
dirs: [".github/agents", ".github/skills"],
|
|
34
|
+
entry: {
|
|
35
|
+
file: path.join(".github", "copilot-instructions.md"),
|
|
36
|
+
content:
|
|
37
|
+
"# Copilot instructions\n\n" +
|
|
38
|
+
"See [AGENTS.md](../AGENTS.md) for the agent rules and the agent/skill " +
|
|
39
|
+
"network that has been materialised into this repository.\n",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Recursively list files under a directory (relative posix-ish paths).
|
|
46
|
+
*/
|
|
47
|
+
function listFiles(dir) {
|
|
48
|
+
const results = [];
|
|
49
|
+
if (!fs.existsSync(dir)) return results;
|
|
50
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
51
|
+
const full = path.join(dir, entry.name);
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
results.push(...listFiles(full));
|
|
54
|
+
} else {
|
|
55
|
+
results.push(full);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Copy a single file from the package root to the destination root. The source
|
|
63
|
+
* and destination relative paths may differ (e.g. src/AGENTS.md -> AGENTS.md).
|
|
64
|
+
* Returns a status object keyed by the destination path.
|
|
65
|
+
*/
|
|
66
|
+
function copyOne(srcRel, destRel, destRoot, { force, dryRun }) {
|
|
67
|
+
const src = path.join(PACKAGE_ROOT, srcRel);
|
|
68
|
+
const dest = path.join(destRoot, destRel);
|
|
69
|
+
|
|
70
|
+
if (!fs.existsSync(src)) {
|
|
71
|
+
return { relPath: destRel, status: "missing-source" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const exists = fs.existsSync(dest);
|
|
75
|
+
if (exists && !force) {
|
|
76
|
+
return { relPath: destRel, status: "skipped-exists" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!dryRun) {
|
|
80
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
81
|
+
fs.copyFileSync(src, dest);
|
|
82
|
+
}
|
|
83
|
+
return { relPath: destRel, status: exists ? "overwritten" : "created" };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Write a small generated entry-point file (e.g. CLAUDE.md,
|
|
88
|
+
* .github/copilot-instructions.md) rather than copying from the package.
|
|
89
|
+
*/
|
|
90
|
+
function writeEntry(entry, destRoot, { force, dryRun }) {
|
|
91
|
+
const dest = path.join(destRoot, entry.file);
|
|
92
|
+
const exists = fs.existsSync(dest);
|
|
93
|
+
if (exists && !force) {
|
|
94
|
+
return { relPath: entry.file, status: "skipped-exists" };
|
|
95
|
+
}
|
|
96
|
+
if (!dryRun) {
|
|
97
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
98
|
+
fs.writeFileSync(dest, entry.content, "utf8");
|
|
99
|
+
}
|
|
100
|
+
return { relPath: entry.file, status: exists ? "overwritten" : "created" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Materialise the requested tools' agent/skill files, plus the tool-agnostic
|
|
105
|
+
* core files (AGENTS.md, HUMAN.md), into destRoot.
|
|
106
|
+
*
|
|
107
|
+
* @param {string[]} tools - subset of Object.keys(TOOLS)
|
|
108
|
+
* @param {object} opts - { destRoot, force, dryRun }
|
|
109
|
+
* @returns {{results: Array, tools: string[]}}
|
|
110
|
+
*/
|
|
111
|
+
function materialize(tools, opts = {}) {
|
|
112
|
+
const destRoot = path.resolve(opts.destRoot || process.cwd());
|
|
113
|
+
const force = !!opts.force;
|
|
114
|
+
const dryRun = !!opts.dryRun;
|
|
115
|
+
|
|
116
|
+
const results = [];
|
|
117
|
+
|
|
118
|
+
for (const cf of CORE_FILES) {
|
|
119
|
+
results.push(copyOne(cf.src, cf.dest, destRoot, { force, dryRun }));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (const toolName of tools) {
|
|
123
|
+
const tool = TOOLS[toolName];
|
|
124
|
+
if (!tool) continue;
|
|
125
|
+
for (const dir of tool.dirs) {
|
|
126
|
+
const files = listFiles(path.join(PACKAGE_ROOT, dir));
|
|
127
|
+
for (const abs of files) {
|
|
128
|
+
const relPath = path.relative(PACKAGE_ROOT, abs);
|
|
129
|
+
results.push(copyOne(relPath, relPath, destRoot, { force, dryRun }));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (tool.entry) {
|
|
133
|
+
results.push(writeEntry(tool.entry, destRoot, { force, dryRun }));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { results, tools, destRoot };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { materialize, TOOLS, CORE_FILES, PACKAGE_ROOT };
|