methodology-m 0.3.1
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/bin/m.mjs +76 -0
- package/dist-m/CHANGELOG.md +45 -0
- package/dist-m/capabilities/bootstrap-root-repo/SKILL.md +138 -0
- package/dist-m/capabilities/decompose-story/SKILL.md +299 -0
- package/dist-m/capabilities/generate-acceptance-tests/SKILL.md +305 -0
- package/dist-m/capabilities/generate-pats/SKILL.md +131 -0
- package/dist-m/capabilities/scaffold-repo/SKILL.md +641 -0
- package/dist-m/capabilities/setup-workspace/SKILL.md +70 -0
- package/dist-m/capabilities/tag-release/SKILL.md +121 -0
- package/dist-m/capabilities/wire-orchestration/SKILL.md +351 -0
- package/dist-m/m.md +126 -0
- package/dist-m/providers/provider-interface.md +191 -0
- package/dist-m/providers/scm/gitlab.md +377 -0
- package/dist-m/schemas/pat.schema.json +161 -0
- package/dist-m/schemas/project.schema.json +177 -0
- package/package.json +27 -0
- package/src/commands/changelog.mjs +58 -0
- package/src/commands/clone.mjs +199 -0
- package/src/commands/diff.mjs +29 -0
- package/src/commands/init.mjs +51 -0
- package/src/commands/update.mjs +41 -0
- package/src/commands/version.mjs +43 -0
- package/src/lib/copy.mjs +20 -0
- package/src/lib/detect-agent.mjs +25 -0
- package/src/lib/diff-trees.mjs +95 -0
- package/src/lib/topology.mjs +62 -0
- package/src/lib/version-file.mjs +25 -0
- package/src/lib/workspace.mjs +40 -0
- package/src/lib/wrappers/claude.mjs +54 -0
- package/templates/claude/skills/bootstrap-root-repo/SKILL.md +13 -0
- package/templates/claude/skills/decompose-story/SKILL.md +13 -0
- package/templates/claude/skills/generate-acceptance-tests/SKILL.md +13 -0
- package/templates/claude/skills/generate-pats/SKILL.md +13 -0
- package/templates/claude/skills/scaffold-repo/SKILL.md +13 -0
- package/templates/claude/skills/setup-workspace/SKILL.md +13 -0
- package/templates/claude/skills/tag-release/SKILL.md +13 -0
- package/templates/claude/skills/wire-orchestration/SKILL.md +13 -0
- package/templates/claude/steering/m-steering.md +3 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { copyDistM } from '../lib/copy.mjs';
|
|
4
|
+
import { readInstalledVersion, writeInstalledVersion, readAvailableVersion } from '../lib/version-file.mjs';
|
|
5
|
+
import { detectAgents } from '../lib/detect-agent.mjs';
|
|
6
|
+
import { generateClaudeWrappers } from '../lib/wrappers/claude.mjs';
|
|
7
|
+
|
|
8
|
+
export async function init(args) {
|
|
9
|
+
const target = resolve(args[0] || '.');
|
|
10
|
+
const version = readAvailableVersion();
|
|
11
|
+
const installed = readInstalledVersion(target);
|
|
12
|
+
|
|
13
|
+
if (installed) {
|
|
14
|
+
console.log(`M v${installed} is already installed. Use "m update" to upgrade.`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log(`Installing Methodology M v${version} into ${target}`);
|
|
19
|
+
console.log('');
|
|
20
|
+
|
|
21
|
+
// 1. Copy .m/ directory
|
|
22
|
+
const destDir = copyDistM(target);
|
|
23
|
+
console.log(` ✓ .m/ directory created`);
|
|
24
|
+
|
|
25
|
+
// 2. Write version file
|
|
26
|
+
writeInstalledVersion(target, version);
|
|
27
|
+
console.log(` ✓ .m-version written (v${version})`);
|
|
28
|
+
|
|
29
|
+
// 3. Detect agent runtimes and generate wrappers
|
|
30
|
+
const agents = detectAgents(target);
|
|
31
|
+
|
|
32
|
+
if (agents.includes('claude')) {
|
|
33
|
+
const { created, skipped } = generateClaudeWrappers(target);
|
|
34
|
+
for (const f of created) console.log(` ✓ ${f} created`);
|
|
35
|
+
for (const f of skipped) console.log(` · ${f} already exists (skipped)`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!agents.length) {
|
|
39
|
+
console.log(' · No agent runtime detected (.claude/ or .kiro/)');
|
|
40
|
+
console.log(' Create .claude/ or .kiro/ first, then re-run "m init" to generate wrappers.');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 4. Summary
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log(`Methodology M v${version} installed.`);
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log('Next steps:');
|
|
48
|
+
console.log(' 1. Tell your AI agent: "Read .m/m.md and bootstrap this project"');
|
|
49
|
+
console.log(' 2. The agent will discover capabilities and guide you through setup');
|
|
50
|
+
console.log(' 3. Run "m version" to check your installed version');
|
|
51
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { copyDistM } from '../lib/copy.mjs';
|
|
4
|
+
import { readInstalledVersion, writeInstalledVersion, readAvailableVersion } from '../lib/version-file.mjs';
|
|
5
|
+
import { diffTrees, formatDiff } from '../lib/diff-trees.mjs';
|
|
6
|
+
|
|
7
|
+
export async function update(args) {
|
|
8
|
+
const target = resolve(args[0] || '.');
|
|
9
|
+
const installed = readInstalledVersion(target);
|
|
10
|
+
const available = readAvailableVersion();
|
|
11
|
+
|
|
12
|
+
if (!installed) {
|
|
13
|
+
console.error('M is not installed in this project. Run "m init" first.');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (installed === available) {
|
|
18
|
+
console.log(`Already at v${installed}. Nothing to update.`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const installedDir = join(target, '.m');
|
|
23
|
+
const availableDir = join(import.meta.dirname, '..', '..', 'dist-m');
|
|
24
|
+
|
|
25
|
+
// Show what will change
|
|
26
|
+
console.log(`Updating Methodology M: v${installed} → v${available}`);
|
|
27
|
+
console.log('');
|
|
28
|
+
|
|
29
|
+
const result = diffTrees(installedDir, availableDir);
|
|
30
|
+
console.log(formatDiff(result));
|
|
31
|
+
console.log('');
|
|
32
|
+
|
|
33
|
+
// Apply the update
|
|
34
|
+
copyDistM(target);
|
|
35
|
+
writeInstalledVersion(target, available);
|
|
36
|
+
|
|
37
|
+
console.log(`Updated to v${available}.`);
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log('Agent wrappers (.claude/, .kiro/) were NOT touched.');
|
|
40
|
+
console.log('Review the changes above and update your agent wrappers if needed.');
|
|
41
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { readInstalledVersion, readAvailableVersion } from '../lib/version-file.mjs';
|
|
4
|
+
|
|
5
|
+
export async function version(args) {
|
|
6
|
+
const target = resolve(args[0] || '.');
|
|
7
|
+
const bundled = readAvailableVersion();
|
|
8
|
+
const installed = readInstalledVersion(target);
|
|
9
|
+
const latest = checkRegistry();
|
|
10
|
+
|
|
11
|
+
console.log(`Bundled: v${bundled} (shipped with this CLI)`);
|
|
12
|
+
|
|
13
|
+
if (installed) {
|
|
14
|
+
console.log(`Installed: v${installed}`);
|
|
15
|
+
} else {
|
|
16
|
+
console.log('Installed: not installed (run "m init")');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (latest) {
|
|
20
|
+
console.log(`Latest: v${latest} (npm registry)`);
|
|
21
|
+
if (installed && installed !== latest) {
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(`Update available: v${installed} → v${latest}`);
|
|
24
|
+
console.log('Run "npm i -g methodology-m@latest" then "m update"');
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
console.log('Latest: (not published yet / offline)');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Check npm registry for the latest published version. Fails silently. */
|
|
32
|
+
function checkRegistry() {
|
|
33
|
+
try {
|
|
34
|
+
return execSync('npm view methodology-m version 2>/dev/null', {
|
|
35
|
+
stdio: 'pipe',
|
|
36
|
+
timeout: 5000,
|
|
37
|
+
})
|
|
38
|
+
.toString()
|
|
39
|
+
.trim();
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/lib/copy.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cpSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Copy the dist-m/ snapshot into the target project's .m/ directory.
|
|
6
|
+
* Overwrites existing files. Creates directories as needed.
|
|
7
|
+
*/
|
|
8
|
+
export function copyDistM(target) {
|
|
9
|
+
const distDir = join(import.meta.dirname, '..', '..', 'dist-m');
|
|
10
|
+
const destDir = join(target, '.m');
|
|
11
|
+
|
|
12
|
+
if (!existsSync(distDir)) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
'dist-m/ not found. Run "npm run snapshot" first, or install from npm.'
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
cpSync(distDir, destDir, { recursive: true, force: true });
|
|
19
|
+
return destDir;
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Detect which AI agent runtimes are present in the target directory.
|
|
6
|
+
* Returns an array of detected runtime names.
|
|
7
|
+
*/
|
|
8
|
+
export function detectAgents(target) {
|
|
9
|
+
const agents = [];
|
|
10
|
+
|
|
11
|
+
// Claude Code — .claude/ directory or CLAUDE.md
|
|
12
|
+
if (
|
|
13
|
+
existsSync(join(target, '.claude')) ||
|
|
14
|
+
existsSync(join(target, 'CLAUDE.md'))
|
|
15
|
+
) {
|
|
16
|
+
agents.push('claude');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Kiro — .kiro/ directory
|
|
20
|
+
if (existsSync(join(target, '.kiro'))) {
|
|
21
|
+
agents.push('kiro');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return agents;
|
|
25
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Walk a directory tree and return all file paths relative to root.
|
|
6
|
+
*/
|
|
7
|
+
function walk(dir, root = dir) {
|
|
8
|
+
const results = [];
|
|
9
|
+
if (!existsSync(dir)) return results;
|
|
10
|
+
|
|
11
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
12
|
+
const full = join(dir, entry.name);
|
|
13
|
+
if (entry.isDirectory()) {
|
|
14
|
+
results.push(...walk(full, root));
|
|
15
|
+
} else {
|
|
16
|
+
results.push(relative(root, full));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return results.sort();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Compare two directory trees and return a structured diff.
|
|
24
|
+
* Returns { added: [], removed: [], changed: [], unchanged: [] }
|
|
25
|
+
* Each entry in changed has { file, installedContent, availableContent }.
|
|
26
|
+
*/
|
|
27
|
+
export function diffTrees(installedDir, availableDir) {
|
|
28
|
+
const installedFiles = new Set(walk(installedDir));
|
|
29
|
+
const availableFiles = new Set(walk(availableDir));
|
|
30
|
+
|
|
31
|
+
const added = [];
|
|
32
|
+
const removed = [];
|
|
33
|
+
const changed = [];
|
|
34
|
+
const unchanged = [];
|
|
35
|
+
|
|
36
|
+
// Files in available but not installed
|
|
37
|
+
for (const file of availableFiles) {
|
|
38
|
+
if (!installedFiles.has(file)) {
|
|
39
|
+
added.push(file);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Files in installed but not available
|
|
44
|
+
for (const file of installedFiles) {
|
|
45
|
+
if (!availableFiles.has(file)) {
|
|
46
|
+
removed.push(file);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Files in both — compare content
|
|
51
|
+
for (const file of availableFiles) {
|
|
52
|
+
if (!installedFiles.has(file)) continue;
|
|
53
|
+
const a = readFileSync(join(installedDir, file), 'utf8');
|
|
54
|
+
const b = readFileSync(join(availableDir, file), 'utf8');
|
|
55
|
+
if (a === b) {
|
|
56
|
+
unchanged.push(file);
|
|
57
|
+
} else {
|
|
58
|
+
changed.push(file);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { added, removed, changed, unchanged };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Format a diff result as a human-readable string.
|
|
67
|
+
*/
|
|
68
|
+
export function formatDiff(result) {
|
|
69
|
+
const lines = [];
|
|
70
|
+
|
|
71
|
+
if (result.added.length) {
|
|
72
|
+
lines.push('Added:');
|
|
73
|
+
for (const f of result.added) lines.push(` + ${f}`);
|
|
74
|
+
}
|
|
75
|
+
if (result.removed.length) {
|
|
76
|
+
lines.push('Removed:');
|
|
77
|
+
for (const f of result.removed) lines.push(` - ${f}`);
|
|
78
|
+
}
|
|
79
|
+
if (result.changed.length) {
|
|
80
|
+
lines.push('Changed:');
|
|
81
|
+
for (const f of result.changed) lines.push(` ~ ${f}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!lines.length) {
|
|
85
|
+
lines.push('No differences.');
|
|
86
|
+
} else {
|
|
87
|
+
const total = result.added.length + result.removed.length + result.changed.length;
|
|
88
|
+
lines.push('');
|
|
89
|
+
lines.push(
|
|
90
|
+
`${total} file(s) differ (${result.added.length} added, ${result.removed.length} removed, ${result.changed.length} changed, ${result.unchanged.length} unchanged)`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Read project.yaml and extract topology information.
|
|
6
|
+
* Uses a simple line parser — no YAML library dependency.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} rootRepoDir — path to the cloned root repo
|
|
9
|
+
* @returns {{ project: string, group: string, components: Array<{name, type, location}> }}
|
|
10
|
+
*/
|
|
11
|
+
export function readTopology(rootRepoDir) {
|
|
12
|
+
const filePath = join(rootRepoDir, 'project.yaml');
|
|
13
|
+
const content = readFileSync(filePath, 'utf8');
|
|
14
|
+
|
|
15
|
+
const result = { project: '', group: '', components: [] };
|
|
16
|
+
let currentComponent = null;
|
|
17
|
+
|
|
18
|
+
for (const line of content.split('\n')) {
|
|
19
|
+
const trimmed = line.trim();
|
|
20
|
+
|
|
21
|
+
// Top-level scalars
|
|
22
|
+
if (trimmed.startsWith('project:')) {
|
|
23
|
+
result.project = extractValue(trimmed);
|
|
24
|
+
} else if (trimmed.startsWith('group:')) {
|
|
25
|
+
result.group = extractValue(trimmed);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Component list entries
|
|
29
|
+
if (trimmed.startsWith('- name:')) {
|
|
30
|
+
if (currentComponent) result.components.push(currentComponent);
|
|
31
|
+
currentComponent = { name: extractValue(trimmed.replace('- ', '')), type: '', location: '' };
|
|
32
|
+
} else if (currentComponent && trimmed.startsWith('type:')) {
|
|
33
|
+
currentComponent.type = extractValue(trimmed);
|
|
34
|
+
} else if (currentComponent && trimmed.startsWith('location:')) {
|
|
35
|
+
currentComponent.location = extractValue(trimmed);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (currentComponent) result.components.push(currentComponent);
|
|
40
|
+
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function extractValue(line) {
|
|
45
|
+
const parts = line.split(':');
|
|
46
|
+
parts.shift();
|
|
47
|
+
return parts.join(':').trim().replace(/^["']|["']$/g, '');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the list of referenced (external) repos from the topology.
|
|
52
|
+
* Returns the repo name (last segment of location) and full location.
|
|
53
|
+
*/
|
|
54
|
+
export function getReferencedRepos(topology) {
|
|
55
|
+
return topology.components
|
|
56
|
+
.filter((c) => c.type === 'referenced')
|
|
57
|
+
.map((c) => ({
|
|
58
|
+
name: c.name,
|
|
59
|
+
location: c.location,
|
|
60
|
+
repoName: c.location.split('/').pop(),
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
|
|
5
|
+
const VERSION_FILE = '.m-version';
|
|
6
|
+
|
|
7
|
+
/** Read the installed M version from .m-version in the target dir. */
|
|
8
|
+
export function readInstalledVersion(target) {
|
|
9
|
+
const file = join(target, VERSION_FILE);
|
|
10
|
+
if (!existsSync(file)) return null;
|
|
11
|
+
return readFileSync(file, 'utf8').trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Write the M version to .m-version in the target dir. */
|
|
15
|
+
export function writeInstalledVersion(target, version) {
|
|
16
|
+
const file = join(target, VERSION_FILE);
|
|
17
|
+
writeFileSync(file, version + '\n', 'utf8');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Read the available (bundled) M version from the CLI's package.json. */
|
|
21
|
+
export function readAvailableVersion() {
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
const pkg = require('../../package.json');
|
|
24
|
+
return pkg.version;
|
|
25
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { join, basename } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generate an IDE workspace file for the project.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} parentDir — directory containing all cloned repos
|
|
8
|
+
* @param {string} projectName — project name (used for the file name)
|
|
9
|
+
* @param {string[]} repoDirs — directory names of all repos (root + managed)
|
|
10
|
+
* @param {object} options
|
|
11
|
+
* @param {string} options.ide — IDE to generate for (default: 'vscode')
|
|
12
|
+
* @returns {string} path to the generated workspace file
|
|
13
|
+
*/
|
|
14
|
+
export function generateWorkspace(parentDir, projectName, repoDirs, options = {}) {
|
|
15
|
+
const ide = options.ide || 'vscode';
|
|
16
|
+
|
|
17
|
+
switch (ide) {
|
|
18
|
+
case 'vscode':
|
|
19
|
+
return generateVSCodeWorkspace(parentDir, projectName, repoDirs);
|
|
20
|
+
default:
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Unsupported IDE: "${ide}". Supported: vscode. ` +
|
|
23
|
+
`(More IDEs can be added — PRs welcome.)`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function generateVSCodeWorkspace(parentDir, projectName, repoDirs) {
|
|
29
|
+
const workspace = {
|
|
30
|
+
folders: repoDirs.map((dir) => ({
|
|
31
|
+
name: dir,
|
|
32
|
+
path: dir,
|
|
33
|
+
})),
|
|
34
|
+
settings: {},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const filePath = join(parentDir, `${projectName}.code-workspace`);
|
|
38
|
+
writeFileSync(filePath, JSON.stringify(workspace, null, 2) + '\n', 'utf8');
|
|
39
|
+
return filePath;
|
|
40
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generate Claude Code wrapper files:
|
|
6
|
+
* .claude/steering/m-steering.md
|
|
7
|
+
* .claude/skills/<name>/SKILL.md (one per M capability)
|
|
8
|
+
*
|
|
9
|
+
* Only creates files that don't already exist — respects project ownership.
|
|
10
|
+
*/
|
|
11
|
+
export function generateClaudeWrappers(target) {
|
|
12
|
+
const created = [];
|
|
13
|
+
const skipped = [];
|
|
14
|
+
const templatesDir = join(import.meta.dirname, '..', '..', '..', 'templates', 'claude');
|
|
15
|
+
|
|
16
|
+
// Steering wrapper (single file)
|
|
17
|
+
const steeringPair = {
|
|
18
|
+
src: join(templatesDir, 'steering', 'm-steering.md'),
|
|
19
|
+
dest: join(target, '.claude', 'steering', 'm-steering.md'),
|
|
20
|
+
label: '.claude/steering/m-steering.md',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (existsSync(steeringPair.dest)) {
|
|
24
|
+
skipped.push(steeringPair.label);
|
|
25
|
+
} else {
|
|
26
|
+
mkdirSync(join(steeringPair.dest, '..'), { recursive: true });
|
|
27
|
+
cpSync(steeringPair.src, steeringPair.dest);
|
|
28
|
+
created.push(steeringPair.label);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Skill wrappers (one directory per capability)
|
|
32
|
+
const skillsTemplateDir = join(templatesDir, 'skills');
|
|
33
|
+
if (existsSync(skillsTemplateDir)) {
|
|
34
|
+
for (const name of readdirSync(skillsTemplateDir, { withFileTypes: true })) {
|
|
35
|
+
if (!name.isDirectory()) continue;
|
|
36
|
+
|
|
37
|
+
const src = join(skillsTemplateDir, name.name, 'SKILL.md');
|
|
38
|
+
const dest = join(target, '.claude', 'skills', name.name, 'SKILL.md');
|
|
39
|
+
const label = `.claude/skills/${name.name}/SKILL.md`;
|
|
40
|
+
|
|
41
|
+
if (!existsSync(src)) continue;
|
|
42
|
+
|
|
43
|
+
if (existsSync(dest)) {
|
|
44
|
+
skipped.push(label);
|
|
45
|
+
} else {
|
|
46
|
+
mkdirSync(join(dest, '..'), { recursive: true });
|
|
47
|
+
cpSync(src, dest);
|
|
48
|
+
created.push(label);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { created, skipped };
|
|
54
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# bootstrap-root-repo
|
|
2
|
+
|
|
3
|
+
Create and seed the root repo with topology manifest and Story Zero
|
|
4
|
+
|
|
5
|
+
## Triggers
|
|
6
|
+
|
|
7
|
+
bootstrap root repo, create root repo
|
|
8
|
+
|
|
9
|
+
## Execution
|
|
10
|
+
|
|
11
|
+
Read and execute the full capability at `.m/capabilities/bootstrap-root-repo/SKILL.md`.
|
|
12
|
+
|
|
13
|
+
Follow the M execution protocol defined in `.m/m.md`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# decompose-story
|
|
2
|
+
|
|
3
|
+
Map story-level PATs to components, generate sub-tasks and readiness tracker
|
|
4
|
+
|
|
5
|
+
## Triggers
|
|
6
|
+
|
|
7
|
+
decompose story, break down story
|
|
8
|
+
|
|
9
|
+
## Execution
|
|
10
|
+
|
|
11
|
+
Read and execute the full capability at `.m/capabilities/decompose-story/SKILL.md`.
|
|
12
|
+
|
|
13
|
+
Follow the M execution protocol defined in `.m/m.md`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# generate-acceptance-tests
|
|
2
|
+
|
|
3
|
+
Compile PAT stubs into executable acceptance tests (CATs)
|
|
4
|
+
|
|
5
|
+
## Triggers
|
|
6
|
+
|
|
7
|
+
generate acceptance tests, compile cats
|
|
8
|
+
|
|
9
|
+
## Execution
|
|
10
|
+
|
|
11
|
+
Read and execute the full capability at `.m/capabilities/generate-acceptance-tests/SKILL.md`.
|
|
12
|
+
|
|
13
|
+
Follow the M execution protocol defined in `.m/m.md`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# generate-pats
|
|
2
|
+
|
|
3
|
+
Transform story acceptance criteria into PAT.yaml format
|
|
4
|
+
|
|
5
|
+
## Triggers
|
|
6
|
+
|
|
7
|
+
generate pats, create acceptance tests
|
|
8
|
+
|
|
9
|
+
## Execution
|
|
10
|
+
|
|
11
|
+
Read and execute the full capability at `.m/capabilities/generate-pats/SKILL.md`.
|
|
12
|
+
|
|
13
|
+
Follow the M execution protocol defined in `.m/m.md`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# scaffold-repo
|
|
2
|
+
|
|
3
|
+
Create and configure a managed repo from a sub-task file
|
|
4
|
+
|
|
5
|
+
## Triggers
|
|
6
|
+
|
|
7
|
+
scaffold repo, create managed repo
|
|
8
|
+
|
|
9
|
+
## Execution
|
|
10
|
+
|
|
11
|
+
Read and execute the full capability at `.m/capabilities/scaffold-repo/SKILL.md`.
|
|
12
|
+
|
|
13
|
+
Follow the M execution protocol defined in `.m/m.md`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# setup-workspace
|
|
2
|
+
|
|
3
|
+
Create a project workspace (group/org) on the SCM platform
|
|
4
|
+
|
|
5
|
+
## Triggers
|
|
6
|
+
|
|
7
|
+
setup workspace, create project workspace
|
|
8
|
+
|
|
9
|
+
## Execution
|
|
10
|
+
|
|
11
|
+
Read and execute the full capability at `.m/capabilities/setup-workspace/SKILL.md`.
|
|
12
|
+
|
|
13
|
+
Follow the M execution protocol defined in `.m/m.md`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# tag-release
|
|
2
|
+
|
|
3
|
+
Tag a managed repo at a version and update root repo topology
|
|
4
|
+
|
|
5
|
+
## Triggers
|
|
6
|
+
|
|
7
|
+
tag release, version component
|
|
8
|
+
|
|
9
|
+
## Execution
|
|
10
|
+
|
|
11
|
+
Read and execute the full capability at `.m/capabilities/tag-release/SKILL.md`.
|
|
12
|
+
|
|
13
|
+
Follow the M execution protocol defined in `.m/m.md`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# wire-orchestration
|
|
2
|
+
|
|
3
|
+
Connect managed repos to root repo orchestration (webhooks, CI, tokens)
|
|
4
|
+
|
|
5
|
+
## Triggers
|
|
6
|
+
|
|
7
|
+
wire orchestration, connect repos
|
|
8
|
+
|
|
9
|
+
## Execution
|
|
10
|
+
|
|
11
|
+
Read and execute the full capability at `.m/capabilities/wire-orchestration/SKILL.md`.
|
|
12
|
+
|
|
13
|
+
Follow the M execution protocol defined in `.m/m.md`.
|