@ogygie/cli 0.8.0 → 0.9.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.
Files changed (2) hide show
  1. package/dist/cli.js +113 -29
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -14679,7 +14679,21 @@ var FrameworkManifestSchema = external_exports.object({
14679
14679
  external_exports.object({
14680
14680
  name: external_exports.string().describe("Published npm name, e.g. @ogygie/core"),
14681
14681
  path: external_exports.string().describe("Folder relative to the repo root"),
14682
- intent: external_exports.string().min(20)
14682
+ intent: external_exports.string().min(20),
14683
+ /**
14684
+ * The spec describing this package, when one does. Named EXPLICITLY
14685
+ * because a published npm name (`@ogygie/mcp`) is not a spec id
14686
+ * (`ogygie-mcp`) — the other two natures of entry match by name, this
14687
+ * one cannot. Several packages may point at the same spec: a spec of
14688
+ * the framework often spans the core and the CLI.
14689
+ */
14690
+ spec: external_exports.string().nullable(),
14691
+ /**
14692
+ * How far along, in the SAME words as the other natures. Required, and
14693
+ * deliberately without default: a lifecycle gate that guesses a status
14694
+ * confirms specs nobody delivered.
14695
+ */
14696
+ status: external_exports.enum(["skeleton", "in-progress", "delivered"])
14683
14697
  })
14684
14698
  ),
14685
14699
  templates: external_exports.object({
@@ -14774,6 +14788,22 @@ var OgygieManifestSchema = external_exports.object({
14774
14788
  }
14775
14789
  packageNames.add(packageEntry.name);
14776
14790
  });
14791
+ const specIdOf = (specPath) => (specPath.split("/").pop() ?? specPath).replace(/\.spec\.md$/, "");
14792
+ const ownedSpecIds = /* @__PURE__ */ new Set([
14793
+ ...manifest.features.map((feature) => feature.name),
14794
+ ...packageNames
14795
+ ]);
14796
+ (manifest.framework?.packages ?? []).forEach((packageEntry, index) => {
14797
+ if (packageEntry.spec === null) return;
14798
+ const specId = specIdOf(packageEntry.spec);
14799
+ if (ownedSpecIds.has(specId)) {
14800
+ context.addIssue({
14801
+ code: "custom",
14802
+ path: ["framework", "packages", index, "spec"],
14803
+ message: `spec "${specId}" is already owned by a feature or a product package \u2014 one spec, one owner`
14804
+ });
14805
+ }
14806
+ });
14777
14807
  const checkPackageDependencies = (dependsOn, path) => {
14778
14808
  dependsOn.forEach((dependency, dependencyIndex) => {
14779
14809
  if (!dependency.startsWith("package:")) return;
@@ -15115,9 +15145,57 @@ async function runStudio(deps) {
15115
15145
  });
15116
15146
  }
15117
15147
 
15148
+ // src/commands/mcp.ts
15149
+ import { readFileSync as readFileSync4 } from "node:fs";
15150
+ import { createRequire as createRequire2 } from "node:module";
15151
+ import { join as join5 } from "node:path";
15152
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
15153
+ var PUBLISHED_MCP_SERVER = "@ogygie/mcp/server";
15154
+ async function loadMcpFrom(projectRoot) {
15155
+ const resolveFromProject = createRequire2(join5(projectRoot, "package.json"));
15156
+ const entryPoint = resolveFromProject.resolve(PUBLISHED_MCP_SERVER);
15157
+ return await import(pathToFileURL2(entryPoint).href);
15158
+ }
15159
+ function isModuleNotFound2(thrown) {
15160
+ const code = thrown?.code;
15161
+ return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || code === "ERR_PACKAGE_PATH_NOT_EXPORTED" || thrown instanceof Error && /Cannot find (package|module)/i.test(thrown.message);
15162
+ }
15163
+ async function runMcp(deps) {
15164
+ const importMcp = deps.importMcp ?? (() => loadMcpFrom(deps.projectRoot));
15165
+ let mcpServer;
15166
+ try {
15167
+ mcpServer = await importMcp();
15168
+ } catch (thrown) {
15169
+ const reason = thrown instanceof Error ? thrown.message : String(thrown);
15170
+ if (isModuleNotFound2(thrown)) {
15171
+ throwOgygieError({
15172
+ code: "MCP_NOT_INSTALLED",
15173
+ message: "The Ogygie MCP server is not installed in this project.",
15174
+ file: "package.json",
15175
+ cause: `${PUBLISHED_MCP_SERVER} cannot be resolved: ${reason}`,
15176
+ suggestedFix: `Install it at the framework's own version (lockstep D2): pnpm add -Dw @ogygie/mcp@${deps.cliVersion}`
15177
+ });
15178
+ }
15179
+ throwOgygieError({
15180
+ code: "MCP_LOAD_FAILED",
15181
+ message: "The Ogygie MCP server is installed but could not be loaded.",
15182
+ file: "package.json",
15183
+ cause: reason,
15184
+ suggestedFix: "Reinstalling will not help: this is a framework defect. Report it with this cause, and check that @ogygie/mcp matches @ogygie/cli (lockstep D2)."
15185
+ });
15186
+ }
15187
+ await mcpServer.startOgygieMcp({
15188
+ argv: deps.argv,
15189
+ cwd: deps.projectRoot,
15190
+ version: deps.cliVersion,
15191
+ token: process.env["OGYGIE_MCP_TOKEN"],
15192
+ readConfigFile: (absolutePath) => readFileSync4(absolutePath, "utf8")
15193
+ });
15194
+ }
15195
+
15118
15196
  // src/commands/upgrade.ts
15119
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
15120
- import { dirname as dirname2, join as join5 } from "node:path";
15197
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
15198
+ import { dirname as dirname2, join as join6 } from "node:path";
15121
15199
 
15122
15200
  // src/diff.ts
15123
15201
  function diffLines(oldText, newText) {
@@ -15185,11 +15263,11 @@ function compareVersions(a, b) {
15185
15263
 
15186
15264
  // src/commands/upgrade.ts
15187
15265
  function reportMissingGates(deps) {
15188
- const packageJsonPath = join5(deps.projectRoot, "package.json");
15266
+ const packageJsonPath = join6(deps.projectRoot, "package.json");
15189
15267
  if (!existsSync4(packageJsonPath)) return;
15190
15268
  let scripts;
15191
15269
  try {
15192
- const parsed = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
15270
+ const parsed = JSON.parse(readFileSync5(packageJsonPath, "utf8"));
15193
15271
  const scriptsValue = typeof parsed === "object" && parsed !== null ? parsed.scripts : void 0;
15194
15272
  if (typeof scriptsValue === "object" && scriptsValue !== null) {
15195
15273
  scripts = scriptsValue;
@@ -15240,8 +15318,8 @@ async function upgradeProject(deps) {
15240
15318
  for (const governed of governedFiles) {
15241
15319
  const expected = installedChecksums[governed.targetPath];
15242
15320
  if (expected === void 0) continue;
15243
- const absolutePath = join5(deps.projectRoot, governed.targetPath);
15244
- const actual = existsSync4(absolutePath) ? sha256Hex(readFileSync4(absolutePath, "utf8")) : "<absent>";
15321
+ const absolutePath = join6(deps.projectRoot, governed.targetPath);
15322
+ const actual = existsSync4(absolutePath) ? sha256Hex(readFileSync5(absolutePath, "utf8")) : "<absent>";
15245
15323
  if (actual !== expected) {
15246
15324
  throwOgygieError({
15247
15325
  code: "OGYGIE_GOVERNED_FILE_MODIFIED",
@@ -15255,12 +15333,12 @@ async function upgradeProject(deps) {
15255
15333
  const plannedWrites = [];
15256
15334
  const diffBlocks = [];
15257
15335
  for (const governed of governedFiles) {
15258
- const content = readFileSync4(
15259
- join5(deps.templatesDir, governed.templatePath),
15336
+ const content = readFileSync5(
15337
+ join6(deps.templatesDir, governed.templatePath),
15260
15338
  "utf8"
15261
15339
  );
15262
- const absolutePath = join5(deps.projectRoot, governed.targetPath);
15263
- const current = existsSync4(absolutePath) ? readFileSync4(absolutePath, "utf8") : "";
15340
+ const absolutePath = join6(deps.projectRoot, governed.targetPath);
15341
+ const current = existsSync4(absolutePath) ? readFileSync5(absolutePath, "utf8") : "";
15264
15342
  const diff = diffLines(current, content);
15265
15343
  if (diff !== "") {
15266
15344
  plannedWrites.push({ targetPath: governed.targetPath, content });
@@ -15280,7 +15358,7 @@ ${diff}`);
15280
15358
  };
15281
15359
  }
15282
15360
  for (const write of plannedWrites) {
15283
- const absolutePath = join5(deps.projectRoot, write.targetPath);
15361
+ const absolutePath = join6(deps.projectRoot, write.targetPath);
15284
15362
  mkdirSync2(dirname2(absolutePath), { recursive: true });
15285
15363
  writeFileSync3(absolutePath, write.content);
15286
15364
  }
@@ -15301,8 +15379,8 @@ ${diff}`);
15301
15379
  }
15302
15380
 
15303
15381
  // src/commands/verify-vocabulary.ts
15304
- import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
15305
- import { join as join6, relative, sep } from "node:path";
15382
+ import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
15383
+ import { join as join7, relative, sep } from "node:path";
15306
15384
 
15307
15385
  // ../ogygie-core/src/vocabulary.ts
15308
15386
  var TOKEN_PATTERN = new RegExp("\\p{L}[\\p{L}\\p{N}]*(?:[-_$][\\p{L}\\p{N}]+)*", "gu");
@@ -15409,7 +15487,7 @@ function auditProductVocabulary(input) {
15409
15487
  var CODE_EXTENSIONS = [".ts", ".tsx", ".mts", ".mjs", ".js", ".jsx"];
15410
15488
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", ".git", "coverage"]);
15411
15489
  function readManifest(projectRoot) {
15412
- const manifestPath = join6(projectRoot, MANIFEST_FILE_NAME);
15490
+ const manifestPath = join7(projectRoot, MANIFEST_FILE_NAME);
15413
15491
  if (!existsSync5(manifestPath)) {
15414
15492
  throwOgygieError({
15415
15493
  code: "OGYGIE_CLI_USAGE",
@@ -15420,7 +15498,7 @@ function readManifest(projectRoot) {
15420
15498
  });
15421
15499
  }
15422
15500
  const parsed = OgygieManifestSchema.safeParse(
15423
- JSON.parse(readFileSync5(manifestPath, "utf8"))
15501
+ JSON.parse(readFileSync6(manifestPath, "utf8"))
15424
15502
  );
15425
15503
  if (!parsed.success) {
15426
15504
  throwOgygieError({
@@ -15434,13 +15512,13 @@ function readManifest(projectRoot) {
15434
15512
  return parsed.data;
15435
15513
  }
15436
15514
  function collectCodeFiles(projectRoot, folder) {
15437
- const absoluteFolder = join6(projectRoot, folder);
15515
+ const absoluteFolder = join7(projectRoot, folder);
15438
15516
  if (!existsSync5(absoluteFolder)) return [];
15439
15517
  const files = [];
15440
15518
  const walk = (directory) => {
15441
15519
  for (const entry of readdirSync2(directory)) {
15442
15520
  if (IGNORED_DIRS.has(entry)) continue;
15443
- const entryPath = join6(directory, entry);
15521
+ const entryPath = join7(directory, entry);
15444
15522
  if (statSync2(entryPath).isDirectory()) {
15445
15523
  walk(entryPath);
15446
15524
  continue;
@@ -15448,7 +15526,7 @@ function collectCodeFiles(projectRoot, folder) {
15448
15526
  if (!CODE_EXTENSIONS.some((extension) => entry.endsWith(extension))) continue;
15449
15527
  files.push({
15450
15528
  path: relative(projectRoot, entryPath).split(sep).join("/"),
15451
- content: readFileSync5(entryPath, "utf8")
15529
+ content: readFileSync6(entryPath, "utf8")
15452
15530
  });
15453
15531
  }
15454
15532
  };
@@ -15476,9 +15554,9 @@ function verifyVocabulary(deps) {
15476
15554
  const sourceFiles = productCodeFoldersOf(manifest).flatMap(
15477
15555
  (folder) => collectCodeFiles(deps.projectRoot, folder)
15478
15556
  );
15479
- const governed = governedFiles.filter((file2) => existsSync5(join6(deps.projectRoot, file2.targetPath))).map((file2) => ({
15557
+ const governed = governedFiles.filter((file2) => existsSync5(join7(deps.projectRoot, file2.targetPath))).map((file2) => ({
15480
15558
  path: file2.targetPath,
15481
- content: readFileSync5(join6(deps.projectRoot, file2.targetPath), "utf8")
15559
+ content: readFileSync6(join7(deps.projectRoot, file2.targetPath), "utf8")
15482
15560
  }));
15483
15561
  return auditProductVocabulary({
15484
15562
  vocabulary: manifest.vocabulary,
@@ -15590,23 +15668,23 @@ var governedChecksumsByVersion = {
15590
15668
  };
15591
15669
 
15592
15670
  // src/paths.ts
15593
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
15594
- import { dirname as dirname3, join as join7 } from "node:path";
15671
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
15672
+ import { dirname as dirname3, join as join8 } from "node:path";
15595
15673
  import { fileURLToPath } from "node:url";
15596
- var PACKAGE_ROOT = join7(dirname3(fileURLToPath(import.meta.url)), "..");
15674
+ var PACKAGE_ROOT = join8(dirname3(fileURLToPath(import.meta.url)), "..");
15597
15675
  var PackageVersionSchema2 = external_exports.object({ version: external_exports.string() });
15598
15676
  function cliVersion() {
15599
15677
  return PackageVersionSchema2.parse(
15600
- JSON.parse(readFileSync6(join7(PACKAGE_ROOT, "package.json"), "utf8"))
15678
+ JSON.parse(readFileSync7(join8(PACKAGE_ROOT, "package.json"), "utf8"))
15601
15679
  ).version;
15602
15680
  }
15603
15681
  function templatesDir() {
15604
- const embedded = join7(PACKAGE_ROOT, "templates");
15682
+ const embedded = join8(PACKAGE_ROOT, "templates");
15605
15683
  if (existsSync6(embedded)) return embedded;
15606
15684
  return repoTemplatesDir();
15607
15685
  }
15608
15686
  function repoTemplatesDir() {
15609
- return join7(PACKAGE_ROOT, "..", "..", "templates");
15687
+ return join8(PACKAGE_ROOT, "..", "..", "templates");
15610
15688
  }
15611
15689
 
15612
15690
  // src/cli.ts
@@ -15685,14 +15763,20 @@ ogygie verify-vocabulary \u2014 ${problems.length} probl\xE8me(s).`);
15685
15763
  console.log("ogygie verify-vocabulary \u2014 OK, le vocabulaire d\xE9clar\xE9 est respect\xE9.");
15686
15764
  } else if (command === "studio") {
15687
15765
  await runStudio({ projectRoot: process.cwd(), cliVersion: cliVersion() });
15766
+ } else if (command === "mcp") {
15767
+ await runMcp({
15768
+ projectRoot: process.cwd(),
15769
+ cliVersion: cliVersion(),
15770
+ argv: process.argv.slice(3)
15771
+ });
15688
15772
  } else {
15689
15773
  printError(
15690
15774
  ogygieError({
15691
15775
  code: "OGYGIE_CLI_USAGE",
15692
15776
  message: `Unknown command ${JSON.stringify(command ?? "")}.`,
15693
15777
  file: null,
15694
- cause: `expected one of: init <product-name>, upgrade, check, verify-vocabulary, studio (flags: --json, --yes)`,
15695
- suggestedFix: "Run `ogygie init <product-name>`, `ogygie upgrade`, `ogygie check`, `ogygie verify-vocabulary` or `ogygie studio`."
15778
+ cause: `expected one of: init <product-name>, upgrade, check, verify-vocabulary, studio, mcp (flags: --json, --yes)`,
15779
+ suggestedFix: "Run `ogygie init <product-name>`, `ogygie upgrade`, `ogygie check`, `ogygie verify-vocabulary`, `ogygie studio` or `ogygie mcp`."
15696
15780
  })
15697
15781
  );
15698
15782
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogygie/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Ogygie framework CLI: deterministic scaffolding (init), governed-file upgrades (upgrade) and lockstep verification (check) for Ogygie products.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "tsx": "^4.20.0",
23
23
  "typescript": "^5.8.0",
24
24
  "zod": "^4.0.0",
25
- "@ogygie/core": "0.8.0"
25
+ "@ogygie/core": "0.9.0"
26
26
  },
27
27
  "scripts": {
28
28
  "typecheck": "tsc -p tsconfig.json",