@morphism-systems/cli 0.1.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 +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +635 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @morphism-systems/cli
|
|
2
|
+
|
|
3
|
+
Morphism governance CLI. Initialize config, validate governance, check maturity scores, run health checks.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @morphism-systems/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
morphism init # Initialize .morphism/ config
|
|
15
|
+
morphism validate # Run governance validation
|
|
16
|
+
morphism score # Compute maturity score
|
|
17
|
+
morphism doctor # Health check
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Why Morphism
|
|
21
|
+
|
|
22
|
+
Governance-as-code with mathematical guarantees. [Learn more](https://morphism.systems).
|
|
23
|
+
|
|
24
|
+
## License
|
|
25
|
+
|
|
26
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
var DEFAULT_CONFIG = {
|
|
10
|
+
version: "0.1.0",
|
|
11
|
+
governance: {
|
|
12
|
+
kernel: "morphism-kernel",
|
|
13
|
+
invariants: 7,
|
|
14
|
+
tenets: 10
|
|
15
|
+
},
|
|
16
|
+
metrics: {
|
|
17
|
+
convergence_threshold: 1,
|
|
18
|
+
drift_threshold: 15
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function init(cwd) {
|
|
22
|
+
const morphismDir = join(cwd, ".morphism");
|
|
23
|
+
if (existsSync(morphismDir)) {
|
|
24
|
+
console.log(".morphism/ already exists \u2014 skipping");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
mkdirSync(morphismDir, { recursive: true });
|
|
28
|
+
writeFileSync(
|
|
29
|
+
join(morphismDir, "config.json"),
|
|
30
|
+
JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n"
|
|
31
|
+
);
|
|
32
|
+
console.log("Initialized .morphism/ with default config");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/commands/validate.ts
|
|
36
|
+
import { execSync } from "child_process";
|
|
37
|
+
function validate(cwd) {
|
|
38
|
+
try {
|
|
39
|
+
const result = execSync("python3 scripts/verify_pipeline.py", {
|
|
40
|
+
cwd,
|
|
41
|
+
encoding: "utf-8",
|
|
42
|
+
timeout: 3e4
|
|
43
|
+
});
|
|
44
|
+
console.log(result);
|
|
45
|
+
console.log("Governance validation: PASS");
|
|
46
|
+
} catch (err) {
|
|
47
|
+
const error = err;
|
|
48
|
+
if (error.stdout) console.log(error.stdout);
|
|
49
|
+
if (error.stderr) console.error(error.stderr);
|
|
50
|
+
console.error("Governance validation: FAIL");
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/commands/score.ts
|
|
56
|
+
import { execSync as execSync2 } from "child_process";
|
|
57
|
+
function score(cwd, threshold) {
|
|
58
|
+
try {
|
|
59
|
+
const result = execSync2(
|
|
60
|
+
`python3 scripts/maturity_score.py --ci --threshold ${threshold}`,
|
|
61
|
+
{ cwd, encoding: "utf-8", timeout: 3e4 }
|
|
62
|
+
);
|
|
63
|
+
console.log(result);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
const error = err;
|
|
66
|
+
if (error.stdout) console.log(error.stdout);
|
|
67
|
+
if (error.stderr) console.error(error.stderr);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/commands/doctor.ts
|
|
73
|
+
import { existsSync as existsSync2 } from "fs";
|
|
74
|
+
import { join as join2 } from "path";
|
|
75
|
+
import { execSync as execSync3 } from "child_process";
|
|
76
|
+
function doctor(cwd) {
|
|
77
|
+
const checks = [
|
|
78
|
+
{
|
|
79
|
+
name: ".morphism/ config exists",
|
|
80
|
+
test: () => existsSync2(join2(cwd, ".morphism", "config.json"))
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "AGENTS.md exists",
|
|
84
|
+
test: () => existsSync2(join2(cwd, "AGENTS.md"))
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "SSOT.md exists",
|
|
88
|
+
test: () => existsSync2(join2(cwd, "SSOT.md"))
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "Python available",
|
|
92
|
+
test: () => {
|
|
93
|
+
try {
|
|
94
|
+
execSync3("python3 --version", { encoding: "utf-8", timeout: 5e3 });
|
|
95
|
+
return true;
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "Governance scripts exist",
|
|
103
|
+
test: () => existsSync2(join2(cwd, "scripts", "verify_pipeline.py"))
|
|
104
|
+
}
|
|
105
|
+
];
|
|
106
|
+
let allPassed = true;
|
|
107
|
+
for (const check of checks) {
|
|
108
|
+
const passed = check.test();
|
|
109
|
+
const icon = passed ? "+" : "-";
|
|
110
|
+
console.log(` [${icon}] ${check.name}`);
|
|
111
|
+
if (!passed) allPassed = false;
|
|
112
|
+
}
|
|
113
|
+
if (allPassed) {
|
|
114
|
+
console.log("\nAll checks passed");
|
|
115
|
+
} else {
|
|
116
|
+
console.log("\nSome checks failed \u2014 run `morphism init` to set up");
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/commands/scaffold.ts
|
|
122
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs";
|
|
123
|
+
import { join as join3, dirname } from "path";
|
|
124
|
+
function writeFiles(cwd, files) {
|
|
125
|
+
let created = 0;
|
|
126
|
+
for (const [relPath, content] of Object.entries(files)) {
|
|
127
|
+
const fullPath = join3(cwd, relPath);
|
|
128
|
+
if (existsSync3(fullPath)) {
|
|
129
|
+
console.log(` skip: ${relPath} (already exists)`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
mkdirSync2(dirname(fullPath), { recursive: true });
|
|
133
|
+
writeFileSync2(fullPath, content);
|
|
134
|
+
console.log(` created: ${relPath}`);
|
|
135
|
+
created++;
|
|
136
|
+
}
|
|
137
|
+
return created;
|
|
138
|
+
}
|
|
139
|
+
function governanceFiles() {
|
|
140
|
+
return {
|
|
141
|
+
"AGENTS.md": `# Agents
|
|
142
|
+
|
|
143
|
+
## Governance
|
|
144
|
+
|
|
145
|
+
This project follows the Morphism governance framework.
|
|
146
|
+
|
|
147
|
+
## Invariants
|
|
148
|
+
|
|
149
|
+
7 normative invariants (I-1 through I-7) from the morphism kernel.
|
|
150
|
+
|
|
151
|
+
## Scope
|
|
152
|
+
|
|
153
|
+
See .morphism/config.json for governance configuration.
|
|
154
|
+
`,
|
|
155
|
+
"SSOT.md": `# SSOT
|
|
156
|
+
|
|
157
|
+
## Canonical Sources
|
|
158
|
+
|
|
159
|
+
| Domain | Source |
|
|
160
|
+
|--------|--------|
|
|
161
|
+
| Governance | .morphism/config.json |
|
|
162
|
+
| Agents | AGENTS.md |
|
|
163
|
+
| Guidelines | GUIDELINES.md |
|
|
164
|
+
`,
|
|
165
|
+
"GUIDELINES.md": `# Guidelines
|
|
166
|
+
|
|
167
|
+
## Commit Messages
|
|
168
|
+
|
|
169
|
+
Use conventional commits: \`type(scope): subject\`
|
|
170
|
+
|
|
171
|
+
Types: feat, fix, chore, ci, docs, refactor, test, perf, hotfix
|
|
172
|
+
|
|
173
|
+
## Code Style
|
|
174
|
+
|
|
175
|
+
- TypeScript: strict mode, ESM
|
|
176
|
+
- Lint before commit
|
|
177
|
+
- Tests required for new features
|
|
178
|
+
`,
|
|
179
|
+
".morphism/config.json": JSON.stringify(
|
|
180
|
+
{
|
|
181
|
+
version: "0.1.0",
|
|
182
|
+
governance: { kernel: "morphism-kernel", invariants: 7, tenets: 10 },
|
|
183
|
+
metrics: { convergence_threshold: 1, drift_threshold: 15 }
|
|
184
|
+
},
|
|
185
|
+
null,
|
|
186
|
+
2
|
|
187
|
+
) + "\n"
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function monorepoFiles(name) {
|
|
191
|
+
return {
|
|
192
|
+
// Root config
|
|
193
|
+
"package.json": JSON.stringify(
|
|
194
|
+
{
|
|
195
|
+
name,
|
|
196
|
+
private: true,
|
|
197
|
+
workspaces: [`apps/*`, `packages/*`],
|
|
198
|
+
scripts: {
|
|
199
|
+
build: "turbo build",
|
|
200
|
+
dev: "turbo dev",
|
|
201
|
+
lint: "turbo lint",
|
|
202
|
+
typecheck: "turbo typecheck",
|
|
203
|
+
test: "turbo test"
|
|
204
|
+
},
|
|
205
|
+
devDependencies: {
|
|
206
|
+
turbo: "^2.0.0",
|
|
207
|
+
typescript: "^5.8.0"
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
null,
|
|
211
|
+
2
|
|
212
|
+
) + "\n",
|
|
213
|
+
"turbo.json": JSON.stringify(
|
|
214
|
+
{
|
|
215
|
+
$schema: "https://turbo.build/schema.json",
|
|
216
|
+
tasks: {
|
|
217
|
+
build: { dependsOn: ["^build"], outputs: ["dist/**", ".next/**"] },
|
|
218
|
+
dev: { cache: false, persistent: true },
|
|
219
|
+
lint: { dependsOn: ["^build"] },
|
|
220
|
+
typecheck: { dependsOn: ["^build"] },
|
|
221
|
+
test: { dependsOn: ["build"] }
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
null,
|
|
225
|
+
2
|
|
226
|
+
) + "\n",
|
|
227
|
+
"tsconfig.json": JSON.stringify(
|
|
228
|
+
{
|
|
229
|
+
compilerOptions: {
|
|
230
|
+
target: "ES2022",
|
|
231
|
+
module: "ESNext",
|
|
232
|
+
moduleResolution: "bundler",
|
|
233
|
+
strict: true,
|
|
234
|
+
esModuleInterop: true,
|
|
235
|
+
skipLibCheck: true,
|
|
236
|
+
declaration: true,
|
|
237
|
+
resolveJsonModule: true
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
null,
|
|
241
|
+
2
|
|
242
|
+
) + "\n",
|
|
243
|
+
".gitignore": `node_modules/
|
|
244
|
+
dist/
|
|
245
|
+
.next/
|
|
246
|
+
.turbo/
|
|
247
|
+
*.tsbuildinfo
|
|
248
|
+
.env
|
|
249
|
+
.env.local
|
|
250
|
+
`,
|
|
251
|
+
// Governance
|
|
252
|
+
...governanceFiles(),
|
|
253
|
+
// App stub
|
|
254
|
+
[`apps/${name}/package.json`]: JSON.stringify(
|
|
255
|
+
{
|
|
256
|
+
name: `@${name}/app`,
|
|
257
|
+
version: "0.1.0",
|
|
258
|
+
private: true,
|
|
259
|
+
scripts: {
|
|
260
|
+
dev: "next dev",
|
|
261
|
+
build: "next build",
|
|
262
|
+
start: "next start",
|
|
263
|
+
lint: "next lint",
|
|
264
|
+
typecheck: "tsc --noEmit"
|
|
265
|
+
},
|
|
266
|
+
dependencies: {
|
|
267
|
+
next: "^15.0.0",
|
|
268
|
+
react: "^19.0.0",
|
|
269
|
+
"react-dom": "^19.0.0",
|
|
270
|
+
[`@${name}/shared`]: "workspace:*"
|
|
271
|
+
},
|
|
272
|
+
devDependencies: {
|
|
273
|
+
typescript: "^5.8.0",
|
|
274
|
+
"@types/react": "^19.0.0"
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
null,
|
|
278
|
+
2
|
|
279
|
+
) + "\n",
|
|
280
|
+
[`apps/${name}/tsconfig.json`]: JSON.stringify(
|
|
281
|
+
{
|
|
282
|
+
extends: "../../tsconfig.json",
|
|
283
|
+
compilerOptions: {
|
|
284
|
+
jsx: "preserve",
|
|
285
|
+
outDir: "dist",
|
|
286
|
+
rootDir: "src",
|
|
287
|
+
plugins: [{ name: "next" }]
|
|
288
|
+
},
|
|
289
|
+
include: ["src", "next-env.d.ts"],
|
|
290
|
+
exclude: ["node_modules", "dist", ".next"]
|
|
291
|
+
},
|
|
292
|
+
null,
|
|
293
|
+
2
|
|
294
|
+
) + "\n",
|
|
295
|
+
[`apps/${name}/src/app/layout.tsx`]: `export const metadata = {
|
|
296
|
+
title: '${name}',
|
|
297
|
+
description: 'A Morphism-governed application',
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
301
|
+
return (
|
|
302
|
+
<html lang="en">
|
|
303
|
+
<body>{children}</body>
|
|
304
|
+
</html>
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
`,
|
|
308
|
+
[`apps/${name}/src/app/page.tsx`]: `export default function Home() {
|
|
309
|
+
return (
|
|
310
|
+
<main>
|
|
311
|
+
<h1>${name}</h1>
|
|
312
|
+
<p>Scaffolded with Morphism governance.</p>
|
|
313
|
+
</main>
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
`,
|
|
317
|
+
// Shared package stub
|
|
318
|
+
"packages/shared/package.json": JSON.stringify(
|
|
319
|
+
{
|
|
320
|
+
name: `@${name}/shared`,
|
|
321
|
+
version: "0.1.0",
|
|
322
|
+
private: true,
|
|
323
|
+
type: "module",
|
|
324
|
+
main: "dist/index.js",
|
|
325
|
+
types: "dist/index.d.ts",
|
|
326
|
+
scripts: {
|
|
327
|
+
build: "tsup src/index.ts --format esm --dts --clean",
|
|
328
|
+
typecheck: "tsc --noEmit",
|
|
329
|
+
test: "vitest run"
|
|
330
|
+
},
|
|
331
|
+
devDependencies: {
|
|
332
|
+
tsup: "^8.0.0",
|
|
333
|
+
typescript: "^5.8.0",
|
|
334
|
+
vitest: "^3.0.0"
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
null,
|
|
338
|
+
2
|
|
339
|
+
) + "\n",
|
|
340
|
+
"packages/shared/tsconfig.json": JSON.stringify(
|
|
341
|
+
{
|
|
342
|
+
extends: "../../tsconfig.json",
|
|
343
|
+
compilerOptions: {
|
|
344
|
+
outDir: "dist",
|
|
345
|
+
rootDir: "src"
|
|
346
|
+
},
|
|
347
|
+
include: ["src"],
|
|
348
|
+
exclude: ["node_modules", "dist"]
|
|
349
|
+
},
|
|
350
|
+
null,
|
|
351
|
+
2
|
|
352
|
+
) + "\n",
|
|
353
|
+
"packages/shared/src/index.ts": `/**
|
|
354
|
+
* @${name}/shared \u2014 shared utilities
|
|
355
|
+
*/
|
|
356
|
+
export function greeting(name: string): string {
|
|
357
|
+
return \`Hello from \${name}!\`
|
|
358
|
+
}
|
|
359
|
+
`,
|
|
360
|
+
"packages/shared/__tests__/index.test.ts": `import { describe, it, expect } from 'vitest'
|
|
361
|
+
import { greeting } from '../src/index.js'
|
|
362
|
+
|
|
363
|
+
describe('shared', () => {
|
|
364
|
+
it('returns a greeting', () => {
|
|
365
|
+
expect(greeting('world')).toBe('Hello from world!')
|
|
366
|
+
})
|
|
367
|
+
})
|
|
368
|
+
`,
|
|
369
|
+
// Scripts
|
|
370
|
+
"scripts/verify_pipeline.py": `#!/usr/bin/env python3
|
|
371
|
+
"""Basic governance verification pipeline."""
|
|
372
|
+
|
|
373
|
+
import sys
|
|
374
|
+
from pathlib import Path
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def main() -> int:
|
|
378
|
+
root = Path(__file__).resolve().parent.parent
|
|
379
|
+
checks = [
|
|
380
|
+
("AGENTS.md", root / "AGENTS.md"),
|
|
381
|
+
("SSOT.md", root / "SSOT.md"),
|
|
382
|
+
("GUIDELINES.md", root / "GUIDELINES.md"),
|
|
383
|
+
(".morphism/config.json", root / ".morphism" / "config.json"),
|
|
384
|
+
]
|
|
385
|
+
failed = 0
|
|
386
|
+
for label, path in checks:
|
|
387
|
+
if path.exists():
|
|
388
|
+
print(f" [+] {label}")
|
|
389
|
+
else:
|
|
390
|
+
print(f" [-] {label} MISSING")
|
|
391
|
+
failed += 1
|
|
392
|
+
if failed:
|
|
393
|
+
print(f"\\n{failed} check(s) failed")
|
|
394
|
+
return 1
|
|
395
|
+
print("\\nAll checks passed")
|
|
396
|
+
return 0
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
if __name__ == "__main__":
|
|
400
|
+
sys.exit(main())
|
|
401
|
+
`,
|
|
402
|
+
// Docs
|
|
403
|
+
"docs/README.md": `# Documentation
|
|
404
|
+
|
|
405
|
+
This directory contains project documentation.
|
|
406
|
+
|
|
407
|
+
## Structure
|
|
408
|
+
|
|
409
|
+
- \`adr/\` \u2014 Architecture Decision Records
|
|
410
|
+
- \`governance/\` \u2014 Governance framework docs
|
|
411
|
+
- \`runbooks/\` \u2014 Operational runbooks
|
|
412
|
+
`,
|
|
413
|
+
"docs/adr/000-template.md": `# ADR-000: Template
|
|
414
|
+
|
|
415
|
+
## Status
|
|
416
|
+
|
|
417
|
+
Accepted
|
|
418
|
+
|
|
419
|
+
## Context
|
|
420
|
+
|
|
421
|
+
Template for Architecture Decision Records.
|
|
422
|
+
|
|
423
|
+
## Decision
|
|
424
|
+
|
|
425
|
+
Use this format for all ADRs.
|
|
426
|
+
|
|
427
|
+
## Consequences
|
|
428
|
+
|
|
429
|
+
Consistent decision documentation.
|
|
430
|
+
`,
|
|
431
|
+
// CI
|
|
432
|
+
".github/workflows/ci.yml": `name: CI
|
|
433
|
+
|
|
434
|
+
on:
|
|
435
|
+
push:
|
|
436
|
+
branches: [main]
|
|
437
|
+
pull_request:
|
|
438
|
+
branches: [main]
|
|
439
|
+
|
|
440
|
+
jobs:
|
|
441
|
+
build:
|
|
442
|
+
runs-on: ubuntu-latest
|
|
443
|
+
steps:
|
|
444
|
+
- uses: actions/checkout@v4
|
|
445
|
+
- uses: actions/setup-node@v4
|
|
446
|
+
with:
|
|
447
|
+
node-version: 22
|
|
448
|
+
cache: npm
|
|
449
|
+
- run: npm ci
|
|
450
|
+
- run: npx turbo build
|
|
451
|
+
- run: npx turbo typecheck
|
|
452
|
+
- run: npx turbo lint
|
|
453
|
+
- run: npx turbo test
|
|
454
|
+
`
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function packageFiles(name) {
|
|
458
|
+
return {
|
|
459
|
+
[`packages/${name}/package.json`]: JSON.stringify(
|
|
460
|
+
{
|
|
461
|
+
name: `@morphism-systems/${name}`,
|
|
462
|
+
version: "0.1.0",
|
|
463
|
+
type: "module",
|
|
464
|
+
main: "dist/index.js",
|
|
465
|
+
types: "dist/index.d.ts",
|
|
466
|
+
scripts: {
|
|
467
|
+
build: "tsup src/index.ts --format esm --dts --clean",
|
|
468
|
+
typecheck: "tsc --noEmit",
|
|
469
|
+
test: "vitest run",
|
|
470
|
+
lint: 'echo "no lint configured"'
|
|
471
|
+
},
|
|
472
|
+
devDependencies: {
|
|
473
|
+
tsup: "^8.0.0",
|
|
474
|
+
typescript: "^5.8.0",
|
|
475
|
+
vitest: "^3.0.0"
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
null,
|
|
479
|
+
2
|
|
480
|
+
) + "\n",
|
|
481
|
+
[`packages/${name}/tsconfig.json`]: JSON.stringify(
|
|
482
|
+
{
|
|
483
|
+
extends: "../../tsconfig.json",
|
|
484
|
+
compilerOptions: {
|
|
485
|
+
outDir: "dist",
|
|
486
|
+
rootDir: "src"
|
|
487
|
+
},
|
|
488
|
+
include: ["src"],
|
|
489
|
+
exclude: ["node_modules", "dist"]
|
|
490
|
+
},
|
|
491
|
+
null,
|
|
492
|
+
2
|
|
493
|
+
) + "\n",
|
|
494
|
+
[`packages/${name}/src/index.ts`]: `/**
|
|
495
|
+
* @morphism-systems/${name}
|
|
496
|
+
*/
|
|
497
|
+
export function hello(): string {
|
|
498
|
+
return 'Hello from @morphism-systems/${name}!'
|
|
499
|
+
}
|
|
500
|
+
`,
|
|
501
|
+
[`packages/${name}/__tests__/index.test.ts`]: `import { describe, it, expect } from 'vitest'
|
|
502
|
+
import { hello } from '../src/index.js'
|
|
503
|
+
|
|
504
|
+
describe('${name}', () => {
|
|
505
|
+
it('exports hello', () => {
|
|
506
|
+
expect(hello()).toContain('${name}')
|
|
507
|
+
})
|
|
508
|
+
})
|
|
509
|
+
`
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
var LEGACY_TEMPLATES = {
|
|
513
|
+
"ts-project": () => ({
|
|
514
|
+
...governanceFiles(),
|
|
515
|
+
".githooks/commit-msg": '#!/usr/bin/env bash\n# Validate commit message format: type(scope): subject\nMSG=$(head -1 "$1")\nif ! echo "$MSG" | grep -qE "^(feat|fix|chore|ci|docs|refactor|test|perf|hotfix)\\([^)]+\\): .+"; then\n echo "Invalid commit message format. Use: type(scope): subject"\n exit 1\nfi\n'
|
|
516
|
+
}),
|
|
517
|
+
"py-project": () => ({
|
|
518
|
+
...governanceFiles(),
|
|
519
|
+
"pyproject.toml": '[tool.ruff]\nline-length = 100\n\n[tool.ruff.lint]\nselect = ["E", "F", "I", "N", "UP", "B", "SIM"]\n\n[tool.mypy]\nstrict = true\n'
|
|
520
|
+
})
|
|
521
|
+
};
|
|
522
|
+
function scaffold(cwd, template) {
|
|
523
|
+
const templateFn = LEGACY_TEMPLATES[template];
|
|
524
|
+
if (!templateFn) {
|
|
525
|
+
const available = [
|
|
526
|
+
...Object.keys(LEGACY_TEMPLATES),
|
|
527
|
+
"monorepo <name>",
|
|
528
|
+
"package <name>",
|
|
529
|
+
"governance"
|
|
530
|
+
].join(", ");
|
|
531
|
+
console.error(`Unknown template: ${template}. Available: ${available}`);
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
const files = templateFn();
|
|
535
|
+
const created = writeFiles(cwd, files);
|
|
536
|
+
console.log(`
|
|
537
|
+
Scaffolded ${created} file(s) using '${template}' template`);
|
|
538
|
+
}
|
|
539
|
+
function scaffoldMonorepo(cwd, name) {
|
|
540
|
+
console.log(`Scaffolding monorepo '${name}'...`);
|
|
541
|
+
const targetDir = join3(cwd, name);
|
|
542
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
543
|
+
const files = monorepoFiles(name);
|
|
544
|
+
const created = writeFiles(targetDir, files);
|
|
545
|
+
console.log(`
|
|
546
|
+
Scaffolded monorepo '${name}' with ${created} file(s) in ${name}/`);
|
|
547
|
+
}
|
|
548
|
+
function scaffoldPackage(cwd, name) {
|
|
549
|
+
console.log(`Scaffolding package '${name}'...`);
|
|
550
|
+
const files = packageFiles(name);
|
|
551
|
+
const created = writeFiles(cwd, files);
|
|
552
|
+
console.log(`
|
|
553
|
+
Scaffolded package '${name}' with ${created} file(s)`);
|
|
554
|
+
}
|
|
555
|
+
function scaffoldGovernance(cwd) {
|
|
556
|
+
console.log("Scaffolding governance files...");
|
|
557
|
+
const files = {
|
|
558
|
+
...governanceFiles(),
|
|
559
|
+
// CI workflow for governance checks
|
|
560
|
+
".github/workflows/governance.yml": `name: Governance
|
|
561
|
+
|
|
562
|
+
on:
|
|
563
|
+
push:
|
|
564
|
+
branches: [main]
|
|
565
|
+
pull_request:
|
|
566
|
+
branches: [main]
|
|
567
|
+
|
|
568
|
+
jobs:
|
|
569
|
+
governance:
|
|
570
|
+
name: Governance checks
|
|
571
|
+
runs-on: ubuntu-latest
|
|
572
|
+
steps:
|
|
573
|
+
- uses: actions/checkout@v4
|
|
574
|
+
- uses: actions/setup-python@v6
|
|
575
|
+
with:
|
|
576
|
+
python-version: "3.11"
|
|
577
|
+
- name: Install morphism
|
|
578
|
+
run: pip install morphism-systems
|
|
579
|
+
- name: Maturity score
|
|
580
|
+
run: python3 scripts/maturity_score.py --ci --threshold 60
|
|
581
|
+
- name: Verify pipeline
|
|
582
|
+
run: python3 scripts/verify_pipeline.py
|
|
583
|
+
`,
|
|
584
|
+
// Pre-commit hook
|
|
585
|
+
".githooks/pre-commit": `#!/usr/bin/env bash
|
|
586
|
+
# Morphism governance pre-commit hook
|
|
587
|
+
set -e
|
|
588
|
+
|
|
589
|
+
# Run policy check if available
|
|
590
|
+
if [ -f scripts/policy_check.py ]; then
|
|
591
|
+
python3 scripts/policy_check.py --mode ci --explain || {
|
|
592
|
+
echo "Policy check failed. Fix issues before committing."
|
|
593
|
+
exit 1
|
|
594
|
+
}
|
|
595
|
+
fi
|
|
596
|
+
|
|
597
|
+
# Run lint on staged files
|
|
598
|
+
STAGED_PY=$(git diff --cached --name-only --diff-filter=ACM | grep '\\.py$' || true)
|
|
599
|
+
if [ -n "$STAGED_PY" ]; then
|
|
600
|
+
echo "Checking Python files..."
|
|
601
|
+
ruff check $STAGED_PY 2>/dev/null || true
|
|
602
|
+
fi
|
|
603
|
+
`,
|
|
604
|
+
// Commit message hook
|
|
605
|
+
".githooks/commit-msg": `#!/usr/bin/env bash
|
|
606
|
+
# Validate commit message: type(scope): subject
|
|
607
|
+
MSG=$(head -1 "$1")
|
|
608
|
+
if ! echo "$MSG" | grep -qE "^(feat|fix|chore|ci|docs|refactor|test|perf|hotfix)\\([^)]+\\): .+"; then
|
|
609
|
+
echo "Invalid commit message format. Use: type(scope): subject"
|
|
610
|
+
exit 1
|
|
611
|
+
fi
|
|
612
|
+
`
|
|
613
|
+
};
|
|
614
|
+
const created = writeFiles(cwd, files);
|
|
615
|
+
console.log(`
|
|
616
|
+
Scaffolded ${created} governance file(s)`);
|
|
617
|
+
console.log("\nNext steps:");
|
|
618
|
+
console.log(" 1. Run: git config core.hooksPath .githooks");
|
|
619
|
+
console.log(" 2. Review AGENTS.md and customize invariants");
|
|
620
|
+
console.log(" 3. Push to trigger governance CI checks");
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/index.ts
|
|
624
|
+
var program = new Command();
|
|
625
|
+
program.name("morphism").description("Morphism governance CLI").version("0.1.0");
|
|
626
|
+
program.command("init").description("Initialize .morphism/ config in the current project").action(() => init(process.cwd()));
|
|
627
|
+
program.command("validate").description("Run the governance validation pipeline").action(() => validate(process.cwd()));
|
|
628
|
+
program.command("score").description("Compute the governance maturity score").option("-t, --threshold <n>", "Minimum passing score", "60").action((opts) => score(process.cwd(), parseInt(opts.threshold)));
|
|
629
|
+
program.command("doctor").description("Health check \u2014 verify governance setup").action(() => doctor(process.cwd()));
|
|
630
|
+
var scaffoldCmd = program.command("scaffold").description("Scaffold projects, packages, and governance files");
|
|
631
|
+
scaffoldCmd.command("monorepo <name>").description("Generate a full Morphism-governed monorepo").action((name) => scaffoldMonorepo(process.cwd(), name));
|
|
632
|
+
scaffoldCmd.command("package <name>").description("Add a new package to an existing monorepo").action((name) => scaffoldPackage(process.cwd(), name));
|
|
633
|
+
scaffoldCmd.command("governance").description("Add governance files (AGENTS.md, SSOT.md, GUIDELINES.md, .morphism/) to an existing project").action(() => scaffoldGovernance(process.cwd()));
|
|
634
|
+
scaffoldCmd.command("legacy <template>").description("Legacy scaffold templates (ts-project, py-project)").action((template) => scaffold(process.cwd(), template));
|
|
635
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@morphism-systems/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Morphism governance CLI — init, validate, score, doctor",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"morphism": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"lint": "echo 'no lint configured'"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"morphism",
|
|
18
|
+
"governance",
|
|
19
|
+
"cli",
|
|
20
|
+
"ai-governance",
|
|
21
|
+
"category-theory"
|
|
22
|
+
],
|
|
23
|
+
"license": "BUSL-1.1",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"commander": "^13.1.0",
|
|
29
|
+
"chalk": "^5.4.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"tsup": "^8.4.0",
|
|
33
|
+
"typescript": "^5.8.3",
|
|
34
|
+
"vitest": "^3.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|