@morphism-systems/cli 0.1.0 → 0.1.3
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 +21 -1
- package/dist/index.js +1080 -18
- package/package.json +38 -36
package/README.md
CHANGED
|
@@ -17,10 +17,30 @@ morphism score # Compute maturity score
|
|
|
17
17
|
morphism doctor # Health check
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
## Example Output
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
$ morphism validate
|
|
24
|
+
κ = 0.0000 | δ = 0.0000
|
|
25
|
+
Maturity: optimizing | Converging: true
|
|
26
|
+
Violations: 0
|
|
27
|
+
Governance validation: PASS
|
|
28
|
+
|
|
29
|
+
$ morphism score
|
|
30
|
+
Maturity score: 125/125
|
|
31
|
+
|
|
32
|
+
$ morphism doctor
|
|
33
|
+
✓ AGENTS.md exists
|
|
34
|
+
✓ SSOT.md exists
|
|
35
|
+
✓ GUIDELINES.md exists
|
|
36
|
+
✓ .morphism/config.json valid
|
|
37
|
+
Health check: PASS
|
|
38
|
+
```
|
|
39
|
+
|
|
20
40
|
## Why Morphism
|
|
21
41
|
|
|
22
42
|
Governance-as-code with mathematical guarantees. [Learn more](https://morphism.systems).
|
|
23
43
|
|
|
24
44
|
## License
|
|
25
45
|
|
|
26
|
-
|
|
46
|
+
Business Source License 1.1 (BUSL-1.1) — Change Date 2030-02-20, Change License Apache 2.0
|
package/dist/index.js
CHANGED
|
@@ -33,30 +33,155 @@ function init(cwd) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// src/commands/validate.ts
|
|
36
|
-
import {
|
|
37
|
-
function validate(cwd) {
|
|
36
|
+
import { execFileSync } from "child_process";
|
|
37
|
+
async function validate(cwd, opts = {}) {
|
|
38
|
+
const format = opts.format ?? "text";
|
|
39
|
+
const proofDir = opts.proofDir ?? ".morphism/proofs";
|
|
40
|
+
const isJson = format === "json";
|
|
41
|
+
let pipelinePassed = false;
|
|
38
42
|
try {
|
|
39
|
-
const result =
|
|
43
|
+
const result = execFileSync("python", ["scripts/verify_pipeline.py"], {
|
|
40
44
|
cwd,
|
|
41
45
|
encoding: "utf-8",
|
|
42
46
|
timeout: 3e4
|
|
43
47
|
});
|
|
44
|
-
console.log(result);
|
|
45
|
-
|
|
48
|
+
if (!isJson) console.log(result);
|
|
49
|
+
pipelinePassed = true;
|
|
46
50
|
} catch (err) {
|
|
47
51
|
const error = err;
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
if (!isJson) {
|
|
53
|
+
if (error.stdout) console.log(error.stdout);
|
|
54
|
+
if (error.stderr) console.error(error.stderr);
|
|
55
|
+
console.error("Governance validation: FAIL (pipeline)");
|
|
56
|
+
}
|
|
57
|
+
if (isJson) {
|
|
58
|
+
console.log(JSON.stringify({
|
|
59
|
+
passed: false,
|
|
60
|
+
kappa: 1,
|
|
61
|
+
delta: 1,
|
|
62
|
+
maturity_level: "initial",
|
|
63
|
+
converging: false,
|
|
64
|
+
violation_count: 1,
|
|
65
|
+
summary: "Pipeline verification failed"
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
51
68
|
process.exit(1);
|
|
52
69
|
}
|
|
70
|
+
let maturityScore = 0;
|
|
71
|
+
try {
|
|
72
|
+
const scoreResult = execFileSync("python", ["scripts/maturity_score.py", "--ci", "--threshold", "0"], {
|
|
73
|
+
cwd,
|
|
74
|
+
encoding: "utf-8",
|
|
75
|
+
timeout: 3e4
|
|
76
|
+
});
|
|
77
|
+
const totalMatch = scoreResult.match(/total:\s*(\d+)\/(\d+)/);
|
|
78
|
+
if (totalMatch) {
|
|
79
|
+
maturityScore = parseInt(totalMatch[1]);
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
let commitSha = opts.commit;
|
|
85
|
+
if (!commitSha) {
|
|
86
|
+
commitSha = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
87
|
+
cwd,
|
|
88
|
+
encoding: "utf-8",
|
|
89
|
+
timeout: 5e3
|
|
90
|
+
}).trim();
|
|
91
|
+
}
|
|
92
|
+
const modPath = "@morphism-systems/mcp-server/governance-loop";
|
|
93
|
+
const { runGovernanceLoop } = await import(
|
|
94
|
+
/* webpackIgnore: true */
|
|
95
|
+
modPath
|
|
96
|
+
).catch(() => {
|
|
97
|
+
return { runGovernanceLoop: null };
|
|
98
|
+
});
|
|
99
|
+
if (!runGovernanceLoop) {
|
|
100
|
+
if (isJson) {
|
|
101
|
+
console.log(JSON.stringify({
|
|
102
|
+
passed: pipelinePassed,
|
|
103
|
+
kappa: 0,
|
|
104
|
+
delta: 0,
|
|
105
|
+
maturity_level: maturityScore >= 100 ? "converged" : "maturing",
|
|
106
|
+
maturity_score: maturityScore,
|
|
107
|
+
converging: true,
|
|
108
|
+
violation_count: 0,
|
|
109
|
+
summary: "Pipeline passed; categorical validation skipped (mcp-server not available)"
|
|
110
|
+
}));
|
|
111
|
+
} else {
|
|
112
|
+
console.log("Categorical validation: skipped (mcp-server not available)");
|
|
113
|
+
console.log("Governance validation: PASS (pipeline only)");
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const result = await runGovernanceLoop({
|
|
118
|
+
commitSha,
|
|
119
|
+
events: [],
|
|
120
|
+
policies: [],
|
|
121
|
+
kappaHistory: [],
|
|
122
|
+
dimensionScores: {
|
|
123
|
+
policy: 1,
|
|
124
|
+
git_hook: 1,
|
|
125
|
+
ci_workflow: 1,
|
|
126
|
+
ssot_atom: 1,
|
|
127
|
+
document: 1,
|
|
128
|
+
security_gate: 1,
|
|
129
|
+
runbook: 1
|
|
130
|
+
},
|
|
131
|
+
writeProof: true,
|
|
132
|
+
proofDir
|
|
133
|
+
});
|
|
134
|
+
if (isJson) {
|
|
135
|
+
console.log(JSON.stringify({
|
|
136
|
+
passed: result.passed,
|
|
137
|
+
kappa: result.kappa,
|
|
138
|
+
delta: result.delta,
|
|
139
|
+
maturity_level: result.maturityLevel,
|
|
140
|
+
maturity_score: maturityScore,
|
|
141
|
+
converging: result.converging,
|
|
142
|
+
violation_count: result.violationCount,
|
|
143
|
+
proof_path: result.proofPath ?? null,
|
|
144
|
+
summary: result.summary
|
|
145
|
+
}));
|
|
146
|
+
} else {
|
|
147
|
+
console.log(`\u03BA = ${result.kappa.toFixed(4)} | \u03B4 = ${result.delta.toFixed(4)}`);
|
|
148
|
+
console.log(`Maturity: ${result.maturityLevel} | Converging: ${result.converging}`);
|
|
149
|
+
console.log(`Violations: ${result.violationCount}`);
|
|
150
|
+
if (result.proofPath) {
|
|
151
|
+
console.log(`Proof: ${result.proofPath}`);
|
|
152
|
+
}
|
|
153
|
+
console.log(result.summary);
|
|
154
|
+
}
|
|
155
|
+
if (!result.passed) {
|
|
156
|
+
if (!isJson) console.error("Governance validation: FAIL (categorical)");
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
if (!isJson) console.log("Governance validation: PASS");
|
|
160
|
+
} catch (err) {
|
|
161
|
+
const error = err;
|
|
162
|
+
if (isJson) {
|
|
163
|
+
console.log(JSON.stringify({
|
|
164
|
+
passed: pipelinePassed,
|
|
165
|
+
kappa: 0,
|
|
166
|
+
delta: 0,
|
|
167
|
+
maturity_level: maturityScore >= 100 ? "converged" : "maturing",
|
|
168
|
+
maturity_score: maturityScore,
|
|
169
|
+
converging: true,
|
|
170
|
+
violation_count: 0,
|
|
171
|
+
summary: `Pipeline passed; categorical validation skipped (${error.message ?? "unknown error"})`
|
|
172
|
+
}));
|
|
173
|
+
} else {
|
|
174
|
+
console.log(`Categorical validation: skipped (${error.message ?? "unknown error"})`);
|
|
175
|
+
console.log("Governance validation: PASS (pipeline only)");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
53
178
|
}
|
|
54
179
|
|
|
55
180
|
// src/commands/score.ts
|
|
56
|
-
import { execSync
|
|
181
|
+
import { execSync } from "child_process";
|
|
57
182
|
function score(cwd, threshold) {
|
|
58
183
|
try {
|
|
59
|
-
const result =
|
|
184
|
+
const result = execSync(
|
|
60
185
|
`python3 scripts/maturity_score.py --ci --threshold ${threshold}`,
|
|
61
186
|
{ cwd, encoding: "utf-8", timeout: 3e4 }
|
|
62
187
|
);
|
|
@@ -72,7 +197,7 @@ function score(cwd, threshold) {
|
|
|
72
197
|
// src/commands/doctor.ts
|
|
73
198
|
import { existsSync as existsSync2 } from "fs";
|
|
74
199
|
import { join as join2 } from "path";
|
|
75
|
-
import { execSync as
|
|
200
|
+
import { execSync as execSync2 } from "child_process";
|
|
76
201
|
function doctor(cwd) {
|
|
77
202
|
const checks = [
|
|
78
203
|
{
|
|
@@ -91,10 +216,15 @@ function doctor(cwd) {
|
|
|
91
216
|
name: "Python available",
|
|
92
217
|
test: () => {
|
|
93
218
|
try {
|
|
94
|
-
|
|
219
|
+
execSync2("python --version", { encoding: "utf-8", timeout: 5e3 });
|
|
95
220
|
return true;
|
|
96
221
|
} catch {
|
|
97
|
-
|
|
222
|
+
try {
|
|
223
|
+
execSync2("python3 --version", { encoding: "utf-8", timeout: 5e3 });
|
|
224
|
+
return true;
|
|
225
|
+
} catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
98
228
|
}
|
|
99
229
|
}
|
|
100
230
|
},
|
|
@@ -118,13 +248,89 @@ function doctor(cwd) {
|
|
|
118
248
|
}
|
|
119
249
|
}
|
|
120
250
|
|
|
251
|
+
// src/commands/status.ts
|
|
252
|
+
import { execSync as execSync3 } from "child_process";
|
|
253
|
+
import { readdirSync } from "fs";
|
|
254
|
+
import { join as join3 } from "path";
|
|
255
|
+
function tryExec(cmd, cwd) {
|
|
256
|
+
try {
|
|
257
|
+
return execSync3(cmd, { cwd, encoding: "utf-8", timeout: 15e3 }).trim();
|
|
258
|
+
} catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function status(cwd) {
|
|
263
|
+
let maturity = "?/?";
|
|
264
|
+
const scoreOut = tryExec("python scripts/maturity_score.py --ci --threshold 0", cwd);
|
|
265
|
+
if (scoreOut) {
|
|
266
|
+
const m = scoreOut.match(/Maturity score:\s*(\d+)\s*\/\s*(\d+)/);
|
|
267
|
+
if (m) maturity = `${m[1]}/${m[2]}`;
|
|
268
|
+
}
|
|
269
|
+
let driftCount = "?";
|
|
270
|
+
const driftOut = tryExec(
|
|
271
|
+
`python -c "from morphism.healing.drift_scanner import DriftScanner; print(len(DriftScanner().scan()))"`,
|
|
272
|
+
cwd
|
|
273
|
+
);
|
|
274
|
+
if (driftOut) driftCount = driftOut;
|
|
275
|
+
let proofCount = 0;
|
|
276
|
+
try {
|
|
277
|
+
const proofsDir = join3(cwd, ".morphism", "proofs");
|
|
278
|
+
proofCount = readdirSync(proofsDir).filter((f) => f.endsWith(".json")).length;
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
console.log(`Maturity: ${maturity} | Drift: ${driftCount} items | Proofs: ${proofCount}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/commands/diff.ts
|
|
285
|
+
import { execSync as execSync4 } from "child_process";
|
|
286
|
+
function tryExec2(cmd, cwd) {
|
|
287
|
+
try {
|
|
288
|
+
return execSync4(cmd, { cwd, encoding: "utf-8", timeout: 15e3 }).trim();
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function diff(cwd, since) {
|
|
294
|
+
const ref = since ?? "HEAD~1";
|
|
295
|
+
const changedFiles = tryExec2(`git diff --name-only ${ref} -- docs/ AGENTS.md SSOT.md GUIDELINES.md .morphism/ scripts/`, cwd);
|
|
296
|
+
if (!changedFiles) {
|
|
297
|
+
console.log("No governance changes detected (or git not available).");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const files = changedFiles.split("\n").filter(Boolean);
|
|
301
|
+
if (files.length === 0) {
|
|
302
|
+
console.log(`No governance files changed since ${ref}.`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
console.log(`Governance changes since ${ref}:`);
|
|
306
|
+
console.log();
|
|
307
|
+
for (const f of files) {
|
|
308
|
+
const stat = tryExec2(`git diff --stat ${ref} -- "${f}"`, cwd);
|
|
309
|
+
console.log(` ${f}`);
|
|
310
|
+
if (stat) {
|
|
311
|
+
const lastLine = stat.split("\n").pop();
|
|
312
|
+
if (lastLine) console.log(` ${lastLine.trim()}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
console.log();
|
|
316
|
+
console.log(`${files.length} governance file(s) changed.`);
|
|
317
|
+
const driftOut = tryExec2(
|
|
318
|
+
`python -c "from morphism.healing.drift_scanner import DriftScanner; items = DriftScanner().scan(); print(f'{len(items)} drift items')"`,
|
|
319
|
+
cwd
|
|
320
|
+
);
|
|
321
|
+
if (driftOut) {
|
|
322
|
+
console.log(`Current drift: ${driftOut}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
121
326
|
// src/commands/scaffold.ts
|
|
122
327
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs";
|
|
123
|
-
import { join as
|
|
328
|
+
import { join as join4, dirname } from "path";
|
|
329
|
+
import { input, select, confirm } from "@inquirer/prompts";
|
|
124
330
|
function writeFiles(cwd, files) {
|
|
125
331
|
let created = 0;
|
|
126
332
|
for (const [relPath, content] of Object.entries(files)) {
|
|
127
|
-
const fullPath =
|
|
333
|
+
const fullPath = join4(cwd, relPath);
|
|
128
334
|
if (existsSync3(fullPath)) {
|
|
129
335
|
console.log(` skip: ${relPath} (already exists)`);
|
|
130
336
|
continue;
|
|
@@ -538,7 +744,7 @@ Scaffolded ${created} file(s) using '${template}' template`);
|
|
|
538
744
|
}
|
|
539
745
|
function scaffoldMonorepo(cwd, name) {
|
|
540
746
|
console.log(`Scaffolding monorepo '${name}'...`);
|
|
541
|
-
const targetDir =
|
|
747
|
+
const targetDir = join4(cwd, name);
|
|
542
748
|
mkdirSync2(targetDir, { recursive: true });
|
|
543
749
|
const files = monorepoFiles(name);
|
|
544
750
|
const created = writeFiles(targetDir, files);
|
|
@@ -619,17 +825,873 @@ Scaffolded ${created} governance file(s)`);
|
|
|
619
825
|
console.log(" 2. Review AGENTS.md and customize invariants");
|
|
620
826
|
console.log(" 3. Push to trigger governance CI checks");
|
|
621
827
|
}
|
|
828
|
+
async function promptTierVariables(tier, defaults) {
|
|
829
|
+
const projectName = await input({
|
|
830
|
+
message: "Project name:",
|
|
831
|
+
default: defaults.projectName
|
|
832
|
+
});
|
|
833
|
+
const description = await input({
|
|
834
|
+
message: "Brief project description:",
|
|
835
|
+
default: `A ${TIER_LABELS[tier]} project`
|
|
836
|
+
});
|
|
837
|
+
const vars = { projectName, description };
|
|
838
|
+
if (tier === 1) {
|
|
839
|
+
vars.framework = await select({
|
|
840
|
+
message: "Frontend framework:",
|
|
841
|
+
choices: [
|
|
842
|
+
{ value: "React 18", name: "React 18" },
|
|
843
|
+
{ value: "Next.js 15", name: "Next.js 15" },
|
|
844
|
+
{ value: "Vue 3", name: "Vue 3" },
|
|
845
|
+
{ value: "Svelte 5", name: "Svelte 5" },
|
|
846
|
+
{ value: "Astro", name: "Astro" }
|
|
847
|
+
],
|
|
848
|
+
default: "React 18"
|
|
849
|
+
});
|
|
850
|
+
vars.styling = await select({
|
|
851
|
+
message: "Styling solution:",
|
|
852
|
+
choices: [
|
|
853
|
+
{ value: "Tailwind CSS", name: "Tailwind CSS" },
|
|
854
|
+
{ value: "CSS Modules", name: "CSS Modules" },
|
|
855
|
+
{ value: "Styled Components", name: "Styled Components" },
|
|
856
|
+
{ value: "Vanilla CSS", name: "Vanilla CSS" }
|
|
857
|
+
],
|
|
858
|
+
default: "Tailwind CSS"
|
|
859
|
+
});
|
|
860
|
+
vars.nodeVersion = await input({ message: "Node.js version:", default: "20" });
|
|
861
|
+
}
|
|
862
|
+
if (tier === 2) {
|
|
863
|
+
vars.domain = await input({
|
|
864
|
+
message: "Research domain (e.g., quantum mechanics, ML, physics):",
|
|
865
|
+
default: "scientific computing"
|
|
866
|
+
});
|
|
867
|
+
vars.moduleName = await input({
|
|
868
|
+
message: "Main Python module name:",
|
|
869
|
+
default: projectName.toLowerCase().replace(/[^a-z0-9_]/g, "_")
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
if (tier === 3) {
|
|
873
|
+
vars.buildSystem = await select({
|
|
874
|
+
message: "Build system:",
|
|
875
|
+
choices: [
|
|
876
|
+
{ value: "Turborepo", name: "Turborepo" },
|
|
877
|
+
{ value: "Nx", name: "Nx" },
|
|
878
|
+
{ value: "Lerna", name: "Lerna" },
|
|
879
|
+
{ value: "Make", name: "Make" }
|
|
880
|
+
],
|
|
881
|
+
default: "Turborepo"
|
|
882
|
+
});
|
|
883
|
+
vars.buildCommand = await input({
|
|
884
|
+
message: "Build command:",
|
|
885
|
+
default: vars.buildSystem === "Turborepo" ? "npx turbo build" : "npm run build"
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
const addOptional = await confirm({ message: "Add org/maintainer info?", default: false });
|
|
889
|
+
if (addOptional) {
|
|
890
|
+
vars.githubOrg = await input({ message: "GitHub org/user:", default: "alawein" });
|
|
891
|
+
vars.maintainer = await input({ message: "Maintainer name:", default: "" });
|
|
892
|
+
}
|
|
893
|
+
return vars;
|
|
894
|
+
}
|
|
895
|
+
var TIER_LABELS = {
|
|
896
|
+
1: "Web Application (Tier 1)",
|
|
897
|
+
2: "Research / Python (Tier 2)",
|
|
898
|
+
3: "Governance / Monorepo (Tier 3)"
|
|
899
|
+
};
|
|
900
|
+
function tierClaudeTemplate(tier, projectName, date, vars) {
|
|
901
|
+
if (tier === 1) {
|
|
902
|
+
const framework = vars?.framework ?? "React 18";
|
|
903
|
+
const styling = vars?.styling ?? "Tailwind CSS";
|
|
904
|
+
const desc2 = vars?.description ?? `a web application built with TypeScript`;
|
|
905
|
+
return `# Claude Code Configuration \u2014 ${projectName}
|
|
906
|
+
|
|
907
|
+
**Project Type:** Web Application (Product)
|
|
908
|
+
**Last Updated:** ${date}
|
|
909
|
+
|
|
910
|
+
---
|
|
911
|
+
|
|
912
|
+
## Project Context
|
|
913
|
+
|
|
914
|
+
This is **${projectName}** \u2014 ${desc2}.
|
|
915
|
+
|
|
916
|
+
**Quick Reference:**
|
|
917
|
+
- **Tech Stack:** ${framework}, TypeScript (strict mode), ${styling}
|
|
918
|
+
- **Build:** \`npm run build\`
|
|
919
|
+
- **Dev:** \`npm run dev\`
|
|
920
|
+
- **Type Check:** \`npm run typecheck\`
|
|
921
|
+
- **Lint:** \`npm run lint\`
|
|
922
|
+
|
|
923
|
+
---
|
|
924
|
+
|
|
925
|
+
## Session Bootstrap
|
|
926
|
+
|
|
927
|
+
1. \`git log --oneline -10\` \u2014 recent work
|
|
928
|
+
2. \`git status\` \u2014 current state
|
|
929
|
+
3. Read GUIDELINES.md for code style
|
|
930
|
+
4. \`npm install && npm run dev\`
|
|
931
|
+
|
|
932
|
+
## Core Rules
|
|
933
|
+
|
|
934
|
+
1. TypeScript \`strict: true\` at all times
|
|
935
|
+
2. Functional components only (no class components)
|
|
936
|
+
3. No \`any\` types without justification
|
|
937
|
+
4. Run \`npm run typecheck\` before committing
|
|
938
|
+
5. Follow GUIDELINES.md
|
|
939
|
+
|
|
940
|
+
## Work Style
|
|
941
|
+
|
|
942
|
+
- Execute, do not plan
|
|
943
|
+
- One change at a time
|
|
944
|
+
- If stuck >2 tool calls, ask
|
|
945
|
+
|
|
946
|
+
## Test Gates
|
|
947
|
+
|
|
948
|
+
\`\`\`bash
|
|
949
|
+
npm run typecheck && npm run lint && npm run build
|
|
950
|
+
\`\`\`
|
|
951
|
+
|
|
952
|
+
---
|
|
953
|
+
|
|
954
|
+
**Template:** morphism-systems/.morphism/templates/projects/product-web/
|
|
955
|
+
**Generated by:** \`morphism scaffold tier 1\`
|
|
956
|
+
`;
|
|
957
|
+
}
|
|
958
|
+
if (tier === 2) {
|
|
959
|
+
const domain = vars?.domain ?? "scientific computing";
|
|
960
|
+
const moduleName = vars?.moduleName ?? projectName.toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
961
|
+
const desc2 = vars?.description ?? `a Python research project`;
|
|
962
|
+
return `# Claude Code Configuration \u2014 ${projectName}
|
|
963
|
+
|
|
964
|
+
**Project Type:** Research / Scientific Computing
|
|
965
|
+
**Last Updated:** ${date}
|
|
966
|
+
|
|
967
|
+
---
|
|
968
|
+
|
|
969
|
+
## Project Context
|
|
970
|
+
|
|
971
|
+
This is **${projectName}** \u2014 ${desc2}.
|
|
972
|
+
**Domain:** ${domain}
|
|
973
|
+
**Module:** \`${moduleName}\`
|
|
974
|
+
|
|
975
|
+
**Quick Reference:**
|
|
976
|
+
- **Tech Stack:** Python 3.11+, pytest, mypy (strict), Ruff
|
|
977
|
+
- **Install:** \`pip install -e ".[dev]"\`
|
|
978
|
+
- **Type Check:** \`mypy src/\`
|
|
979
|
+
- **Lint:** \`ruff check src/ && ruff format src/\`
|
|
980
|
+
- **Test:** \`pytest tests/ -v\`
|
|
981
|
+
|
|
982
|
+
---
|
|
983
|
+
|
|
984
|
+
## Session Bootstrap
|
|
985
|
+
|
|
986
|
+
1. \`git log --oneline -10\` \u2014 recent work
|
|
987
|
+
2. \`git status\` \u2014 current state
|
|
988
|
+
3. Read AGENTS.md for mission and scope
|
|
989
|
+
4. Examine \`pyproject.toml\` for dependencies
|
|
990
|
+
|
|
991
|
+
## Core Rules
|
|
992
|
+
|
|
993
|
+
1. Complete type hints on every function (mypy strict)
|
|
994
|
+
2. Google-style docstrings with examples
|
|
995
|
+
3. >90% test coverage
|
|
996
|
+
4. No \`Any\` without justification
|
|
997
|
+
5. Dependencies pinned with version bounds
|
|
998
|
+
|
|
999
|
+
## Work Style
|
|
1000
|
+
|
|
1001
|
+
- Execute, do not plan
|
|
1002
|
+
- One change at a time
|
|
1003
|
+
- If stuck >2 tool calls, ask
|
|
1004
|
+
|
|
1005
|
+
## Test Gates
|
|
1006
|
+
|
|
1007
|
+
\`\`\`bash
|
|
1008
|
+
mypy src/ && ruff check src/ && pytest tests/ -v
|
|
1009
|
+
\`\`\`
|
|
1010
|
+
|
|
1011
|
+
---
|
|
1012
|
+
|
|
1013
|
+
**Template:** morphism-systems/.morphism/templates/projects/research-math/
|
|
1014
|
+
**Generated by:** \`morphism scaffold tier 2\`
|
|
1015
|
+
`;
|
|
1016
|
+
}
|
|
1017
|
+
const buildSystem = vars?.buildSystem ?? "Turborepo";
|
|
1018
|
+
const buildCommand = vars?.buildCommand ?? "npx turbo build";
|
|
1019
|
+
const desc = vars?.description ?? `a governance framework or monorepo project`;
|
|
1020
|
+
return `# Claude Code Configuration \u2014 ${projectName}
|
|
1021
|
+
|
|
1022
|
+
**Project Type:** Governance / Monorepo
|
|
1023
|
+
**Last Updated:** ${date}
|
|
1024
|
+
|
|
1025
|
+
---
|
|
1026
|
+
|
|
1027
|
+
## Project Context
|
|
1028
|
+
|
|
1029
|
+
This is **${projectName}** \u2014 ${desc}.
|
|
1030
|
+
|
|
1031
|
+
**Quick Reference:**
|
|
1032
|
+
- **Tech Stack:** Multi-language (TypeScript + Python)
|
|
1033
|
+
- **Build System:** ${buildSystem} (\`${buildCommand}\`)
|
|
1034
|
+
- **Type Check:** TypeScript strict + mypy strict
|
|
1035
|
+
- **Testing:** >95% coverage for governance paths
|
|
1036
|
+
|
|
1037
|
+
---
|
|
1038
|
+
|
|
1039
|
+
## Session Bootstrap
|
|
1040
|
+
|
|
1041
|
+
1. \`git log --oneline -15\` \u2014 recent work
|
|
1042
|
+
2. \`git status\` \u2014 current state
|
|
1043
|
+
3. Read SSOT.md \u2014 single source of truth
|
|
1044
|
+
4. Read AGENTS.md \u2014 kernel invariants
|
|
1045
|
+
5. Read GUIDELINES.md \u2014 code style
|
|
1046
|
+
|
|
1047
|
+
## Core Rules
|
|
1048
|
+
|
|
1049
|
+
1. NEVER weaken kernel invariants
|
|
1050
|
+
2. Every code change updates SSOT/AGENTS/GUIDELINES as needed
|
|
1051
|
+
3. Cross-package changes require full test verification
|
|
1052
|
+
4. Governance verification must pass before commit
|
|
1053
|
+
5. Drift detection must report zero violations
|
|
1054
|
+
|
|
1055
|
+
## Work Style
|
|
1056
|
+
|
|
1057
|
+
- Execute, do not plan
|
|
1058
|
+
- One change at a time
|
|
1059
|
+
- If stuck >2 tool calls, ask
|
|
1060
|
+
|
|
1061
|
+
## Test Gates
|
|
1062
|
+
|
|
1063
|
+
\`\`\`bash
|
|
1064
|
+
# TypeScript
|
|
1065
|
+
npx turbo typecheck && npx turbo lint && npx turbo test
|
|
1066
|
+
# Python
|
|
1067
|
+
ruff check src/ && mypy src/ && pytest tests/
|
|
1068
|
+
# Governance
|
|
1069
|
+
python scripts/verify_pipeline.py
|
|
1070
|
+
\`\`\`
|
|
1071
|
+
|
|
1072
|
+
---
|
|
1073
|
+
|
|
1074
|
+
**Template:** morphism-systems/.morphism/templates/projects/tool-cli/
|
|
1075
|
+
**Generated by:** \`morphism scaffold tier 3\`
|
|
1076
|
+
`;
|
|
1077
|
+
}
|
|
1078
|
+
function tierAgentsTemplate(tier, projectName, date) {
|
|
1079
|
+
const tierLabel = tier === 1 ? "Web" : tier === 2 ? "Research" : "Governance";
|
|
1080
|
+
return `# Project Agents: Mission, Scope & Governance \u2014 ${projectName}
|
|
1081
|
+
|
|
1082
|
+
**Status:** Active
|
|
1083
|
+
**Tier:** ${tier} (${tierLabel})
|
|
1084
|
+
**Last Updated:** ${date}
|
|
1085
|
+
|
|
1086
|
+
---
|
|
1087
|
+
|
|
1088
|
+
## 1. Mission Statement
|
|
1089
|
+
|
|
1090
|
+
[Describe what ${projectName} does and who it serves]
|
|
1091
|
+
|
|
1092
|
+
## 2. Scope
|
|
1093
|
+
|
|
1094
|
+
### What This Project Is
|
|
1095
|
+
- [In-scope item 1]
|
|
1096
|
+
- [In-scope item 2]
|
|
1097
|
+
|
|
1098
|
+
### What This Project Is NOT
|
|
1099
|
+
- [Out-of-scope item 1]
|
|
1100
|
+
|
|
1101
|
+
## 3. Core Rules
|
|
1102
|
+
|
|
1103
|
+
${tier === 1 ? `- R1: TypeScript strict mode
|
|
1104
|
+
- R2: Functional components only
|
|
1105
|
+
- R3: No breaking changes without version bump
|
|
1106
|
+
- R4: ESLint + Prettier before merge
|
|
1107
|
+
- R5: Auto-deploy to Vercel on main` : tier === 2 ? `- R1: mypy strict on all code
|
|
1108
|
+
- R2: Google-style docstrings on all functions
|
|
1109
|
+
- R3: >90% test coverage
|
|
1110
|
+
- R4: Dependencies pinned with bounds
|
|
1111
|
+
- R5: Reproducible results (seeds, dates, parameters)` : `- R1: All kernel invariants (I-1 through I-7) hold
|
|
1112
|
+
- R2: SSOT.md and AGENTS.md updated with every change
|
|
1113
|
+
- R3: Drift detection passes before merge
|
|
1114
|
+
- R4: Maturity score maintained
|
|
1115
|
+
- R5: Cross-package changes require full test suite`}
|
|
1116
|
+
|
|
1117
|
+
## 4. Dependencies
|
|
1118
|
+
|
|
1119
|
+
[List external and internal dependencies]
|
|
1120
|
+
|
|
1121
|
+
## 5. Reading Order
|
|
1122
|
+
|
|
1123
|
+
1. This file (AGENTS.md)
|
|
1124
|
+
2. README.md
|
|
1125
|
+
3. GUIDELINES.md
|
|
1126
|
+
4. CONTRIBUTING.md
|
|
1127
|
+
5. CLAUDE.md
|
|
1128
|
+
|
|
1129
|
+
---
|
|
1130
|
+
|
|
1131
|
+
**Generated by:** \`morphism scaffold tier ${tier}\`
|
|
1132
|
+
`;
|
|
1133
|
+
}
|
|
1134
|
+
function tierGuidelinesTemplate(projectName, tier, date) {
|
|
1135
|
+
const isTS = tier === 1 || tier === 3;
|
|
1136
|
+
const isPY = tier === 2 || tier === 3;
|
|
1137
|
+
let content = `# Development Guidelines \u2014 ${projectName}
|
|
1138
|
+
|
|
1139
|
+
**Last Updated:** ${date}
|
|
1140
|
+
|
|
1141
|
+
---
|
|
1142
|
+
|
|
1143
|
+
## Branching Strategy
|
|
1144
|
+
|
|
1145
|
+
Format: \`type/short-description\`
|
|
1146
|
+
Types: feat/, fix/, docs/, refactor/, test/, chore/, perf/
|
|
1147
|
+
|
|
1148
|
+
## Commit Conventions
|
|
1149
|
+
|
|
1150
|
+
Follow [Conventional Commits](https://www.conventionalcommits.org/).
|
|
1151
|
+
Format: \`type(scope): subject\`
|
|
1152
|
+
|
|
1153
|
+
`;
|
|
1154
|
+
if (isTS) {
|
|
1155
|
+
content += `## TypeScript Style
|
|
1156
|
+
|
|
1157
|
+
- Naming: PascalCase (components), camelCase (utils), UPPER_SNAKE (constants)
|
|
1158
|
+
- Line length: 100 max, 2-space indent, semicolons required
|
|
1159
|
+
- Types: strict mode, no \`any\` without justification
|
|
1160
|
+
|
|
1161
|
+
\`\`\`bash
|
|
1162
|
+
npm run typecheck && npm run lint -- --fix && npm run format
|
|
1163
|
+
\`\`\`
|
|
1164
|
+
|
|
1165
|
+
`;
|
|
1166
|
+
}
|
|
1167
|
+
if (isPY) {
|
|
1168
|
+
content += `## Python Style
|
|
1169
|
+
|
|
1170
|
+
- Naming: snake_case (functions), PascalCase (classes), UPPER_SNAKE (constants)
|
|
1171
|
+
- Line length: 100 max, 4-space indent (PEP 8)
|
|
1172
|
+
- Types: mypy strict, Google-style docstrings
|
|
1173
|
+
|
|
1174
|
+
\`\`\`bash
|
|
1175
|
+
mypy src/ && ruff check src/ --fix && ruff format src/
|
|
1176
|
+
\`\`\`
|
|
1177
|
+
|
|
1178
|
+
`;
|
|
1179
|
+
}
|
|
1180
|
+
content += `## Code Review Checklist
|
|
1181
|
+
|
|
1182
|
+
- [ ] Types correct (no \`any\`)
|
|
1183
|
+
- [ ] Tests included
|
|
1184
|
+
- [ ] Lint passes
|
|
1185
|
+
- [ ] Docs updated
|
|
1186
|
+
- [ ] Scope aligns with AGENTS.md
|
|
1187
|
+
|
|
1188
|
+
---
|
|
1189
|
+
|
|
1190
|
+
**Generated by:** \`morphism scaffold tier ${tier}\`
|
|
1191
|
+
`;
|
|
1192
|
+
return content;
|
|
1193
|
+
}
|
|
1194
|
+
function tierContributingTemplate(projectName, tier) {
|
|
1195
|
+
return `# Contributing to ${projectName}
|
|
1196
|
+
|
|
1197
|
+
## Getting Started
|
|
1198
|
+
|
|
1199
|
+
1. Fork and clone the repository
|
|
1200
|
+
2. Set up local environment:
|
|
1201
|
+
${tier === 1 ? ` \`\`\`bash
|
|
1202
|
+
npm install
|
|
1203
|
+
npm run dev
|
|
1204
|
+
\`\`\`` : tier === 2 ? ` \`\`\`bash
|
|
1205
|
+
python -m venv venv
|
|
1206
|
+
source venv/bin/activate # Windows: venv\\Scripts\\activate
|
|
1207
|
+
pip install -e ".[dev]"
|
|
1208
|
+
\`\`\`` : ` \`\`\`bash
|
|
1209
|
+
npm install
|
|
1210
|
+
pip install -e ".[dev]"
|
|
1211
|
+
\`\`\``}
|
|
1212
|
+
3. Verify setup passes all checks
|
|
1213
|
+
|
|
1214
|
+
## Workflow
|
|
1215
|
+
|
|
1216
|
+
1. Create branch: \`git checkout -b feat/your-feature\`
|
|
1217
|
+
2. Make changes following GUIDELINES.md
|
|
1218
|
+
3. Run quality checks
|
|
1219
|
+
4. Commit with conventional commits
|
|
1220
|
+
5. Push and create PR
|
|
1221
|
+
|
|
1222
|
+
## Code of Conduct
|
|
1223
|
+
|
|
1224
|
+
Be respectful, constructive, and inclusive.
|
|
1225
|
+
|
|
1226
|
+
---
|
|
1227
|
+
|
|
1228
|
+
**Generated by:** \`morphism scaffold tier ${tier}\`
|
|
1229
|
+
`;
|
|
1230
|
+
}
|
|
1231
|
+
function tierCITemplate(tier, projectName) {
|
|
1232
|
+
if (tier === 1) {
|
|
1233
|
+
return `name: CI \u2014 ${projectName}
|
|
1234
|
+
|
|
1235
|
+
on:
|
|
1236
|
+
push:
|
|
1237
|
+
branches: [main]
|
|
1238
|
+
pull_request:
|
|
1239
|
+
branches: [main]
|
|
1240
|
+
|
|
1241
|
+
jobs:
|
|
1242
|
+
ci:
|
|
1243
|
+
runs-on: ubuntu-latest
|
|
1244
|
+
timeout-minutes: 15
|
|
1245
|
+
steps:
|
|
1246
|
+
- uses: actions/checkout@v4
|
|
1247
|
+
- uses: actions/setup-node@v4
|
|
1248
|
+
with:
|
|
1249
|
+
node-version: 20
|
|
1250
|
+
cache: 'npm'
|
|
1251
|
+
- run: npm ci
|
|
1252
|
+
- run: npm run typecheck
|
|
1253
|
+
- run: npm run lint
|
|
1254
|
+
- run: npm run build
|
|
1255
|
+
- run: npm run test -- --run
|
|
1256
|
+
if: always()
|
|
1257
|
+
`;
|
|
1258
|
+
}
|
|
1259
|
+
if (tier === 2) {
|
|
1260
|
+
return `name: CI \u2014 ${projectName}
|
|
1261
|
+
|
|
1262
|
+
on:
|
|
1263
|
+
push:
|
|
1264
|
+
branches: [main]
|
|
1265
|
+
pull_request:
|
|
1266
|
+
branches: [main]
|
|
1267
|
+
|
|
1268
|
+
jobs:
|
|
1269
|
+
ci:
|
|
1270
|
+
runs-on: ubuntu-latest
|
|
1271
|
+
strategy:
|
|
1272
|
+
matrix:
|
|
1273
|
+
python-version: ['3.11', '3.12', '3.13']
|
|
1274
|
+
timeout-minutes: 20
|
|
1275
|
+
steps:
|
|
1276
|
+
- uses: actions/checkout@v4
|
|
1277
|
+
- uses: actions/setup-python@v4
|
|
1278
|
+
with:
|
|
1279
|
+
python-version: \${{ matrix.python-version }}
|
|
1280
|
+
cache: 'pip'
|
|
1281
|
+
- run: pip install -e ".[dev]"
|
|
1282
|
+
- run: mypy src/
|
|
1283
|
+
- run: ruff check src/ && ruff format --check src/
|
|
1284
|
+
- run: pytest tests/ -v --cov=src/ --cov-report=xml
|
|
1285
|
+
`;
|
|
1286
|
+
}
|
|
1287
|
+
return `name: CI \u2014 ${projectName}
|
|
1288
|
+
|
|
1289
|
+
on:
|
|
1290
|
+
push:
|
|
1291
|
+
branches: [main]
|
|
1292
|
+
pull_request:
|
|
1293
|
+
branches: [main]
|
|
1294
|
+
|
|
1295
|
+
jobs:
|
|
1296
|
+
build:
|
|
1297
|
+
runs-on: ubuntu-latest
|
|
1298
|
+
timeout-minutes: 15
|
|
1299
|
+
steps:
|
|
1300
|
+
- uses: actions/checkout@v4
|
|
1301
|
+
- uses: actions/setup-node@v4
|
|
1302
|
+
with:
|
|
1303
|
+
node-version: 20
|
|
1304
|
+
cache: 'npm'
|
|
1305
|
+
- uses: actions/setup-python@v4
|
|
1306
|
+
with:
|
|
1307
|
+
python-version: '3.12'
|
|
1308
|
+
- run: npm ci
|
|
1309
|
+
- run: npx turbo build
|
|
1310
|
+
- run: npx turbo typecheck
|
|
1311
|
+
- run: npx turbo lint
|
|
1312
|
+
- run: npx turbo test
|
|
1313
|
+
- run: pip install -e ".[dev]"
|
|
1314
|
+
- run: ruff check src/ && mypy src/ && pytest tests/
|
|
1315
|
+
`;
|
|
1316
|
+
}
|
|
1317
|
+
function githubTemplateFiles(projectName) {
|
|
1318
|
+
return {
|
|
1319
|
+
".github/ISSUE_TEMPLATE/bug_report.md": `---
|
|
1320
|
+
name: Bug Report
|
|
1321
|
+
about: Report a bug or unexpected behavior
|
|
1322
|
+
title: "[BUG] "
|
|
1323
|
+
labels: bug
|
|
1324
|
+
---
|
|
1325
|
+
|
|
1326
|
+
## Description
|
|
1327
|
+
<!-- Brief description of the bug -->
|
|
1328
|
+
|
|
1329
|
+
## Steps to Reproduce
|
|
1330
|
+
1.
|
|
1331
|
+
2.
|
|
1332
|
+
3.
|
|
1333
|
+
|
|
1334
|
+
## Expected Behavior
|
|
1335
|
+
<!-- What should happen -->
|
|
1336
|
+
|
|
1337
|
+
## Actual Behavior
|
|
1338
|
+
<!-- What actually happens -->
|
|
1339
|
+
|
|
1340
|
+
## Environment
|
|
1341
|
+
- **Project:** ${projectName}
|
|
1342
|
+
- **OS:**
|
|
1343
|
+
- **Version:**
|
|
1344
|
+
`,
|
|
1345
|
+
".github/ISSUE_TEMPLATE/feature_request.md": `---
|
|
1346
|
+
name: Feature Request
|
|
1347
|
+
about: Suggest an idea
|
|
1348
|
+
title: "[FEATURE] "
|
|
1349
|
+
labels: enhancement
|
|
1350
|
+
---
|
|
1351
|
+
|
|
1352
|
+
## Description
|
|
1353
|
+
<!-- Clear description of the feature -->
|
|
1354
|
+
|
|
1355
|
+
## Motivation
|
|
1356
|
+
<!-- Why is this needed? -->
|
|
1357
|
+
|
|
1358
|
+
## Proposed Solution
|
|
1359
|
+
<!-- How should it work? -->
|
|
1360
|
+
|
|
1361
|
+
## Acceptance Criteria
|
|
1362
|
+
- [ ] Criterion 1
|
|
1363
|
+
- [ ] Criterion 2
|
|
1364
|
+
`,
|
|
1365
|
+
".github/pull_request_template.md": `## Description
|
|
1366
|
+
<!-- What does this PR do? -->
|
|
1367
|
+
|
|
1368
|
+
## Type of Change
|
|
1369
|
+
- [ ] Bug fix
|
|
1370
|
+
- [ ] New feature
|
|
1371
|
+
- [ ] Documentation
|
|
1372
|
+
- [ ] Refactoring
|
|
1373
|
+
|
|
1374
|
+
## Checklist
|
|
1375
|
+
- [ ] Follows GUIDELINES.md
|
|
1376
|
+
- [ ] Tests included
|
|
1377
|
+
- [ ] Lint passes
|
|
1378
|
+
- [ ] Types correct
|
|
1379
|
+
- [ ] Docs updated
|
|
1380
|
+
`
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
async function scaffoldTier(cwd, tier, options = {}) {
|
|
1384
|
+
if (tier < 1 || tier > 3) {
|
|
1385
|
+
console.error(`Invalid tier: ${tier}. Must be 1, 2, or 3.`);
|
|
1386
|
+
console.error(" 1 = Web Application (React/Vite/TypeScript)");
|
|
1387
|
+
console.error(" 2 = Research / Python (pytest/mypy/Ruff)");
|
|
1388
|
+
console.error(" 3 = Governance / Monorepo (multi-language)");
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1391
|
+
const defaultName = options.name || cwd.split(/[\\/]/).pop() || "project";
|
|
1392
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1393
|
+
let vars;
|
|
1394
|
+
if (options.interactive) {
|
|
1395
|
+
console.log(`
|
|
1396
|
+
Morphism Scaffold \u2014 Interactive Mode (Tier ${tier})
|
|
1397
|
+
`);
|
|
1398
|
+
vars = await promptTierVariables(tier, { projectName: defaultName });
|
|
1399
|
+
console.log();
|
|
1400
|
+
}
|
|
1401
|
+
const projectName = vars?.projectName ?? defaultName;
|
|
1402
|
+
const tierLabel = TIER_LABELS[tier];
|
|
1403
|
+
console.log(`Scaffolding ${tierLabel} governance for '${projectName}'...`);
|
|
1404
|
+
const files = {
|
|
1405
|
+
"CLAUDE.md": tierClaudeTemplate(tier, projectName, date, vars),
|
|
1406
|
+
"GUIDELINES.md": tierGuidelinesTemplate(projectName, tier, date)
|
|
1407
|
+
};
|
|
1408
|
+
if (tier >= 2) {
|
|
1409
|
+
files["AGENTS.md"] = tierAgentsTemplate(tier, projectName, date);
|
|
1410
|
+
files["CONTRIBUTING.md"] = tierContributingTemplate(projectName, tier);
|
|
1411
|
+
}
|
|
1412
|
+
if (options.ci !== false) {
|
|
1413
|
+
files[".github/workflows/ci.yml"] = tierCITemplate(tier, projectName);
|
|
1414
|
+
}
|
|
1415
|
+
if (options.github !== false) {
|
|
1416
|
+
Object.assign(files, githubTemplateFiles(projectName));
|
|
1417
|
+
}
|
|
1418
|
+
if (tier === 3) {
|
|
1419
|
+
files[".morphism/config.json"] = JSON.stringify(
|
|
1420
|
+
{
|
|
1421
|
+
version: "0.1.0",
|
|
1422
|
+
governance: { kernel: "morphism-kernel", invariants: 7, tenets: 10 },
|
|
1423
|
+
metrics: { convergence_threshold: 1, drift_threshold: 15 }
|
|
1424
|
+
},
|
|
1425
|
+
null,
|
|
1426
|
+
2
|
|
1427
|
+
) + "\n";
|
|
1428
|
+
}
|
|
1429
|
+
const created = writeFiles(cwd, files);
|
|
1430
|
+
console.log(`
|
|
1431
|
+
Scaffolded ${created} file(s) for ${tierLabel}`);
|
|
1432
|
+
console.log(`
|
|
1433
|
+
Files generated:`);
|
|
1434
|
+
console.log(` - CLAUDE.md (AI agent configuration for tier ${tier})`);
|
|
1435
|
+
console.log(` - GUIDELINES.md (code style & conventions)`);
|
|
1436
|
+
if (tier >= 2) {
|
|
1437
|
+
console.log(` - AGENTS.md (mission, scope, governance)`);
|
|
1438
|
+
console.log(` - CONTRIBUTING.md (contribution workflow)`);
|
|
1439
|
+
}
|
|
1440
|
+
if (options.ci !== false) console.log(` - .github/workflows/ci.yml`);
|
|
1441
|
+
if (options.github !== false) {
|
|
1442
|
+
console.log(` - .github/ISSUE_TEMPLATE/bug_report.md`);
|
|
1443
|
+
console.log(` - .github/ISSUE_TEMPLATE/feature_request.md`);
|
|
1444
|
+
console.log(` - .github/pull_request_template.md`);
|
|
1445
|
+
}
|
|
1446
|
+
console.log(`
|
|
1447
|
+
Next steps:`);
|
|
1448
|
+
console.log(` 1. Review and customize each generated file`);
|
|
1449
|
+
console.log(` 2. Fill in [bracketed sections] and project-specific details`);
|
|
1450
|
+
console.log(` 3. Run your project's test suite to verify nothing breaks`);
|
|
1451
|
+
console.log(` 4. Commit: git add . && git commit -m "chore: onboard to morphism governance (tier ${tier})"`);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// src/commands/entropy.ts
|
|
1455
|
+
import { execSync as execSync5 } from "child_process";
|
|
1456
|
+
import fs from "fs";
|
|
1457
|
+
import path from "path";
|
|
1458
|
+
function measureEntropy(text, base = 2) {
|
|
1459
|
+
const escapedText = text.replace(/'/g, `'"'"'`);
|
|
1460
|
+
try {
|
|
1461
|
+
const out = execSync5(
|
|
1462
|
+
`python3 -c "
|
|
1463
|
+
import sys
|
|
1464
|
+
sys.path.insert(0, 'src')
|
|
1465
|
+
from morphism.entropy.llm_entropy import measure_text_entropy, classify_entropy_level
|
|
1466
|
+
e = measure_text_entropy('${escapedText}', base=${base})
|
|
1467
|
+
c = classify_entropy_level(e)
|
|
1468
|
+
print(f'{e}|{c.value}')
|
|
1469
|
+
"`,
|
|
1470
|
+
{ encoding: "utf-8", timeout: 1e4 }
|
|
1471
|
+
);
|
|
1472
|
+
const [entropyStr, classification] = out.trim().split("|");
|
|
1473
|
+
const entropy = parseFloat(entropyStr);
|
|
1474
|
+
return {
|
|
1475
|
+
entropy,
|
|
1476
|
+
classification,
|
|
1477
|
+
interpretation: interpretEntropy(entropy)
|
|
1478
|
+
};
|
|
1479
|
+
} catch {
|
|
1480
|
+
return measureEntropyJS(text, base);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
function compareEntropy(text1, text2) {
|
|
1484
|
+
const result1 = measureEntropy(text1);
|
|
1485
|
+
const result2 = measureEntropy(text2);
|
|
1486
|
+
const delta = result2.entropy - result1.entropy;
|
|
1487
|
+
const percentChange = result1.entropy > 0 ? delta / result1.entropy * 100 : 0;
|
|
1488
|
+
return {
|
|
1489
|
+
text1: { entropy: result1.entropy, classification: result1.classification },
|
|
1490
|
+
text2: { entropy: result2.entropy, classification: result2.classification },
|
|
1491
|
+
comparison: {
|
|
1492
|
+
delta,
|
|
1493
|
+
direction: delta > 0 ? "increased" : delta < 0 ? "decreased" : "unchanged",
|
|
1494
|
+
percent_change: percentChange
|
|
1495
|
+
},
|
|
1496
|
+
recommendation: getRecommendation(delta, result1.entropy, result2.entropy)
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
function entropyReport(filePaths, options = {}) {
|
|
1500
|
+
const artifacts = [];
|
|
1501
|
+
for (const filePath of filePaths) {
|
|
1502
|
+
try {
|
|
1503
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
1504
|
+
const ext = path.extname(filePath);
|
|
1505
|
+
const type = getFileType(ext);
|
|
1506
|
+
artifacts.push({ name: filePath, content, type });
|
|
1507
|
+
} catch {
|
|
1508
|
+
console.error(`Warning: Could not read file ${filePath}`);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
const measurements = artifacts.map((a) => ({
|
|
1512
|
+
name: a.name,
|
|
1513
|
+
type: a.type,
|
|
1514
|
+
...measureEntropy(a.content)
|
|
1515
|
+
}));
|
|
1516
|
+
const avgEntropy = measurements.reduce((sum, m) => sum + m.entropy, 0) / measurements.length;
|
|
1517
|
+
const maxEntropy = Math.max(...measurements.map((m) => m.entropy));
|
|
1518
|
+
const minEntropy = Math.min(...measurements.map((m) => m.entropy));
|
|
1519
|
+
if (options.json) {
|
|
1520
|
+
console.log(
|
|
1521
|
+
JSON.stringify(
|
|
1522
|
+
{
|
|
1523
|
+
summary: {
|
|
1524
|
+
num_artifacts: measurements.length,
|
|
1525
|
+
average_entropy: avgEntropy,
|
|
1526
|
+
max_entropy: maxEntropy,
|
|
1527
|
+
min_entropy: minEntropy,
|
|
1528
|
+
entropy_range: maxEntropy - minEntropy
|
|
1529
|
+
},
|
|
1530
|
+
artifacts: measurements
|
|
1531
|
+
},
|
|
1532
|
+
null,
|
|
1533
|
+
2
|
|
1534
|
+
)
|
|
1535
|
+
);
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
console.log("\n=== Entropy Report ===\n");
|
|
1539
|
+
console.log(`Analyzed ${measurements.length} artifacts
|
|
1540
|
+
`);
|
|
1541
|
+
console.log("Summary:");
|
|
1542
|
+
console.log(` Average entropy: ${avgEntropy.toFixed(3)}`);
|
|
1543
|
+
console.log(` Range: ${minEntropy.toFixed(3)} - ${maxEntropy.toFixed(3)}`);
|
|
1544
|
+
console.log("\nArtifacts:");
|
|
1545
|
+
const nameWidth = Math.max(20, ...measurements.map((m) => m.name.length));
|
|
1546
|
+
for (const m of measurements) {
|
|
1547
|
+
const bar = getEntropyBar(m.entropy);
|
|
1548
|
+
console.log(
|
|
1549
|
+
` ${m.name.padEnd(nameWidth)} ${m.entropy.toFixed(3)} ${m.classification.padEnd(10)} ${bar}`
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
if (options.verbose) {
|
|
1553
|
+
console.log("\nInterpretations:");
|
|
1554
|
+
for (const m of measurements) {
|
|
1555
|
+
console.log(` ${m.name}:`);
|
|
1556
|
+
console.log(` ${m.interpretation}`);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function entropyMeasureCli(text, options) {
|
|
1561
|
+
const base = options.base ?? 2;
|
|
1562
|
+
const result = measureEntropy(text, base);
|
|
1563
|
+
if (options.json) {
|
|
1564
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
console.log("\n=== Entropy Measurement ===\n");
|
|
1568
|
+
console.log(`Entropy: ${result.entropy.toFixed(3)} bits`);
|
|
1569
|
+
console.log(`Classification: ${result.classification}`);
|
|
1570
|
+
console.log(`
|
|
1571
|
+
${result.interpretation}`);
|
|
1572
|
+
const bar = getEntropyBar(result.entropy);
|
|
1573
|
+
console.log(`
|
|
1574
|
+
Visual: ${bar}`);
|
|
1575
|
+
}
|
|
1576
|
+
function entropyCompareCli(text1, text2, options) {
|
|
1577
|
+
const result = compareEntropy(text1, text2);
|
|
1578
|
+
if (options.json) {
|
|
1579
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
console.log("\n=== Entropy Comparison ===\n");
|
|
1583
|
+
console.log(`Text 1: ${result.text1.entropy.toFixed(3)} bits (${result.text1.classification})`);
|
|
1584
|
+
console.log(`Text 2: ${result.text2.entropy.toFixed(3)} bits (${result.text2.classification})`);
|
|
1585
|
+
console.log(`
|
|
1586
|
+
Delta: ${result.comparison.delta > 0 ? "+" : ""}${result.comparison.delta.toFixed(3)} bits`);
|
|
1587
|
+
console.log(`Change: ${result.comparison.percent_change.toFixed(1)}% ${result.comparison.direction}`);
|
|
1588
|
+
console.log(`
|
|
1589
|
+
${result.recommendation}`);
|
|
1590
|
+
}
|
|
1591
|
+
function entropyReportCli(files, options) {
|
|
1592
|
+
entropyReport(files, options);
|
|
1593
|
+
}
|
|
1594
|
+
function interpretEntropy(entropy) {
|
|
1595
|
+
if (entropy < 1) {
|
|
1596
|
+
return "Very low entropy: highly predictable/deterministic content";
|
|
1597
|
+
}
|
|
1598
|
+
if (entropy < 2) {
|
|
1599
|
+
return "Low entropy: mostly predictable with some variation";
|
|
1600
|
+
}
|
|
1601
|
+
if (entropy < 3) {
|
|
1602
|
+
return "Moderate entropy: balanced predictability and variation";
|
|
1603
|
+
}
|
|
1604
|
+
if (entropy < 4) {
|
|
1605
|
+
return "High entropy: significant variation/uncertainty";
|
|
1606
|
+
}
|
|
1607
|
+
return "Very high entropy: highly unpredictable content";
|
|
1608
|
+
}
|
|
1609
|
+
function getRecommendation(delta, _e1, _e2) {
|
|
1610
|
+
if (Math.abs(delta) < 0.1) {
|
|
1611
|
+
return "Entropy levels are similar; content complexity is comparable";
|
|
1612
|
+
}
|
|
1613
|
+
if (delta > 1) {
|
|
1614
|
+
return "Significant entropy increase: second text is much more complex/uncertain";
|
|
1615
|
+
}
|
|
1616
|
+
if (delta > 0) {
|
|
1617
|
+
return "Moderate entropy increase: second text has more variation";
|
|
1618
|
+
}
|
|
1619
|
+
if (delta < -1) {
|
|
1620
|
+
return "Significant entropy decrease: second text is much more deterministic";
|
|
1621
|
+
}
|
|
1622
|
+
return "Moderate entropy decrease: second text is more predictable";
|
|
1623
|
+
}
|
|
1624
|
+
function measureEntropyJS(text, base) {
|
|
1625
|
+
const freq = {};
|
|
1626
|
+
for (const char of text) {
|
|
1627
|
+
freq[char] = (freq[char] || 0) + 1;
|
|
1628
|
+
}
|
|
1629
|
+
const len = text.length;
|
|
1630
|
+
let entropy = 0;
|
|
1631
|
+
for (const count of Object.values(freq)) {
|
|
1632
|
+
const p = count / len;
|
|
1633
|
+
entropy -= p * Math.log(p);
|
|
1634
|
+
}
|
|
1635
|
+
entropy = entropy / Math.log(base);
|
|
1636
|
+
return {
|
|
1637
|
+
entropy,
|
|
1638
|
+
classification: classifyEntropy(entropy),
|
|
1639
|
+
interpretation: interpretEntropy(entropy)
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
function classifyEntropy(entropy) {
|
|
1643
|
+
if (entropy < 1) return "VERY_LOW";
|
|
1644
|
+
if (entropy < 2) return "LOW";
|
|
1645
|
+
if (entropy < 3) return "MODERATE";
|
|
1646
|
+
if (entropy < 4) return "HIGH";
|
|
1647
|
+
return "VERY_HIGH";
|
|
1648
|
+
}
|
|
1649
|
+
function getEntropyBar(entropy, width = 20) {
|
|
1650
|
+
const normalized = Math.min(entropy / 5, 1);
|
|
1651
|
+
const filled = Math.round(normalized * width);
|
|
1652
|
+
const empty = width - filled;
|
|
1653
|
+
return "[" + "#".repeat(filled) + "-".repeat(empty) + "]";
|
|
1654
|
+
}
|
|
1655
|
+
function getFileType(ext) {
|
|
1656
|
+
const types = {
|
|
1657
|
+
".ts": "typescript",
|
|
1658
|
+
".tsx": "typescript",
|
|
1659
|
+
".js": "javascript",
|
|
1660
|
+
".jsx": "javascript",
|
|
1661
|
+
".py": "python",
|
|
1662
|
+
".md": "markdown",
|
|
1663
|
+
".json": "json",
|
|
1664
|
+
".yaml": "yaml",
|
|
1665
|
+
".yml": "yaml",
|
|
1666
|
+
".txt": "text"
|
|
1667
|
+
};
|
|
1668
|
+
return types[ext.toLowerCase()] || "unknown";
|
|
1669
|
+
}
|
|
622
1670
|
|
|
623
1671
|
// src/index.ts
|
|
624
1672
|
var program = new Command();
|
|
625
|
-
program.name("morphism").description("Morphism governance CLI").version("0.1.
|
|
1673
|
+
program.name("morphism").description("Morphism governance CLI").version("0.1.3");
|
|
626
1674
|
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()));
|
|
1675
|
+
program.command("validate").description("Run the governance validation pipeline").option("--full", "Run full validation including categorical loop").option("--commit <sha>", "Git commit SHA to validate").option("--proof-dir <dir>", "Directory for proof witnesses", ".morphism/proofs").option("--format <fmt>", "Output format: text or json", "text").action(async (opts) => validate(process.cwd(), opts));
|
|
628
1676
|
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
1677
|
program.command("doctor").description("Health check \u2014 verify governance setup").action(() => doctor(process.cwd()));
|
|
1678
|
+
program.command("status").description("One-line governance summary: maturity, drift, proofs").action(() => status(process.cwd()));
|
|
1679
|
+
program.command("diff").description("Show governance delta since last commit").option("--since <ref>", "Git ref to compare against", "HEAD~1").action((opts) => diff(process.cwd(), opts.since));
|
|
630
1680
|
var scaffoldCmd = program.command("scaffold").description("Scaffold projects, packages, and governance files");
|
|
631
1681
|
scaffoldCmd.command("monorepo <name>").description("Generate a full Morphism-governed monorepo").action((name) => scaffoldMonorepo(process.cwd(), name));
|
|
632
1682
|
scaffoldCmd.command("package <name>").description("Add a new package to an existing monorepo").action((name) => scaffoldPackage(process.cwd(), name));
|
|
633
1683
|
scaffoldCmd.command("governance").description("Add governance files (AGENTS.md, SSOT.md, GUIDELINES.md, .morphism/) to an existing project").action(() => scaffoldGovernance(process.cwd()));
|
|
1684
|
+
scaffoldCmd.command("tier <tier>").description("Generate tier-specific governance files (1=web, 2=research, 3=governance)").option("-n, --name <name>", "Project name (defaults to directory name)").option("-i, --interactive", "Prompt for project-specific values").option("--no-ci", "Skip CI workflow generation").option("--no-github", "Skip GitHub issue/PR templates").action(
|
|
1685
|
+
async (tier, opts) => scaffoldTier(process.cwd(), parseInt(tier), {
|
|
1686
|
+
name: opts.name,
|
|
1687
|
+
interactive: opts.interactive === true,
|
|
1688
|
+
ci: opts.ci !== false,
|
|
1689
|
+
github: opts.github !== false
|
|
1690
|
+
})
|
|
1691
|
+
);
|
|
634
1692
|
scaffoldCmd.command("legacy <template>").description("Legacy scaffold templates (ts-project, py-project)").action((template) => scaffold(process.cwd(), template));
|
|
1693
|
+
var entropyCmd = program.command("entropy").description("Measure and analyze entropy of text/artifacts");
|
|
1694
|
+
entropyCmd.command("measure <text>").description("Measure entropy of text content").option("--base <n>", "Logarithm base (2=bits, e=nats)", "2").option("--json", "Output as JSON").action((text, opts) => entropyMeasureCli(text, { base: parseInt(opts.base), json: opts.json }));
|
|
1695
|
+
entropyCmd.command("compare <text1> <text2>").description("Compare entropy between two texts").option("--json", "Output as JSON").action((text1, text2, opts) => entropyCompareCli(text1, text2, { json: opts.json }));
|
|
1696
|
+
entropyCmd.command("report <files...>").description("Generate entropy report for files").option("--json", "Output as JSON").option("-v, --verbose", "Include detailed interpretations").action((files, opts) => entropyReportCli(files, { json: opts.json, verbose: opts.verbose }));
|
|
635
1697
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,36 +1,38 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@morphism-systems/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Morphism governance CLI — init, validate, score, doctor",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@morphism-systems/cli",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Morphism governance CLI — init, validate, score, doctor",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"morphism": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"lint": "echo 'no lint configured'"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"morphism",
|
|
19
|
+
"governance",
|
|
20
|
+
"cli",
|
|
21
|
+
"ai-governance",
|
|
22
|
+
"category-theory"
|
|
23
|
+
],
|
|
24
|
+
"license": "BUSL-1.1",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@inquirer/prompts": "^8.3.0",
|
|
30
|
+
"chalk": "^5.4.0",
|
|
31
|
+
"commander": "^13.1.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"tsup": "^8.4.0",
|
|
35
|
+
"typescript": "^5.8.3",
|
|
36
|
+
"vitest": "^3.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|