@venizia/vulcan 0.2.0-4 → 0.2.0-6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venizia/vulcan",
3
- "version": "0.2.0-4",
3
+ "version": "0.2.0-6",
4
4
  "description": "VULCAN - one setup, one workflow for every project: init, sync, doctor, upgrade, and `vulcan x` for the shipped tools. Bun-only.",
5
5
  "homepage": "https://github.com/VENIZIA-AI/vulcan/tree/main/packages/cli",
6
6
  "repository": {
@@ -44,8 +44,8 @@
44
44
  "test": "NODE_ENV=test bun test --env-file=.env.test"
45
45
  },
46
46
  "dependencies": {
47
- "@venizia/vulcan-atlas": "0.2.0-4",
48
- "@venizia/vulcan-okf": "0.2.0-4",
47
+ "@venizia/vulcan-atlas": "0.2.0-6",
48
+ "@venizia/vulcan-okf": "0.2.0-6",
49
49
  "zod": "^4.5.4"
50
50
  },
51
51
  "devDependencies": {
@@ -17,6 +17,46 @@ const PROJECT_PACKAGE = z.strictObject({
17
17
  retired: z.boolean().default(false),
18
18
  });
19
19
 
20
+ /** A path as a key: no leading `./`, no trailing slash. */
21
+ const pathKeyOf = (opts: { path: string }): string =>
22
+ opts.path.replace(/^\.\//, '').replace(/\/+$/, '');
23
+
24
+ /**
25
+ * `dir` is a unique KEY (it need not be the directory name - set `path` for that), so two packages
26
+ * whose directories are both called `core` are `umbras-core` and `kits-core`. A repeated key, name or
27
+ * live path would make every table keyed by it collide silently.
28
+ */
29
+ const checkPackagesUnique = (
30
+ packages: z.infer<typeof PROJECT_PACKAGE>[],
31
+ context: z.RefinementCtx,
32
+ ): void => {
33
+ const seen = { dir: new Set<string>(), name: new Set<string>(), path: new Set<string>() };
34
+
35
+ for (const [index, entry] of packages.entries()) {
36
+ const path = pathKeyOf({ path: entry.path ?? `packages/${entry.dir}` });
37
+ const repeated = [
38
+ seen.dir.has(entry.dir)
39
+ ? `packages: dir '${entry.dir}' is used twice - dir is a unique key, not the directory name; give each package its own dir and set path`
40
+ : undefined,
41
+ seen.name.has(entry.name) ? `packages: name '${entry.name}' is used twice` : undefined,
42
+ !entry.retired && seen.path.has(path) ? `packages: path '${path}' is used twice` : undefined,
43
+ ];
44
+
45
+ for (const message of repeated) {
46
+ if (message !== undefined) {
47
+ context.addIssue({ code: 'custom', message, path: [index] });
48
+ }
49
+ }
50
+
51
+ seen.dir.add(entry.dir);
52
+ seen.name.add(entry.name);
53
+
54
+ if (!entry.retired) {
55
+ seen.path.add(path);
56
+ }
57
+ }
58
+ };
59
+
20
60
  /**
21
61
  * A release template: `<dir>` (synonym `<package>`) is the package DIRECTORY - the `packages` key,
22
62
  * never the npm name (`packages/core-server` published as `@acme/server` tags `core-server-v1.2.0`); `<version>` is the
@@ -50,7 +90,7 @@ export const VULCAN_CONFIG_SCHEMA = z.strictObject({
50
90
  agents: z.array(CODING_AGENT).default([CodingAgents.CLAUDE]),
51
91
  rules: z.record(z.string().regex(ConfigConstants.RULE_ID_PATTERN), z.string()).default({}),
52
92
  /** The project's packages in release order; okf, atlas, make and release read this one list. */
53
- packages: z.array(PROJECT_PACKAGE).default([]),
93
+ packages: z.array(PROJECT_PACKAGE).superRefine(checkPackagesUnique).default([]),
54
94
  knowledge: z
55
95
  .strictObject({
56
96
  bundle: z.string().default(ConfigConstants.DEFAULT_KNOWLEDGE_BUNDLE),