@ogygie/core 0.13.0 → 0.14.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/package.json +18 -17
- package/src/manifest-owners.test.ts +29 -1
- package/src/manifest-owners.ts +22 -0
- package/src/scan.test.ts +31 -0
- package/src/scan.ts +35 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ogygie/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Ogygie framework core: manifest/spec/error schemas, generic tenancy and gate tooling. Ships TypeScript sources — Ogygie products run tsx/vitest by construction (D1).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,28 +13,29 @@
|
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
15
|
"exports": {
|
|
16
|
-
"./manifest": "./src/manifest.schema.ts",
|
|
17
|
-
"./manifest-owners": "./src/manifest-owners.ts",
|
|
18
|
-
"./vocabulary": "./src/vocabulary.ts",
|
|
19
|
-
"./vocabulary-audit": "./src/vocabulary-audit.ts",
|
|
20
|
-
"./web-lint": "./src/web-lint.ts",
|
|
21
16
|
"./bundle-budget": "./src/bundle-budget.ts",
|
|
22
|
-
"./
|
|
23
|
-
"./
|
|
24
|
-
"./
|
|
17
|
+
"./client": "./src/manifest-client.ts",
|
|
18
|
+
"./client-react": "./src/manifest-client-react.ts",
|
|
19
|
+
"./clock": "./src/clock.ts",
|
|
20
|
+
"./contract-purity": "./src/contract-purity.ts",
|
|
21
|
+
"./db": "./src/db.ts",
|
|
25
22
|
"./decision-refs": "./src/decision-refs.ts",
|
|
26
23
|
"./design-brief": "./src/design-brief.ts",
|
|
27
|
-
"./module-purity": "./src/module-purity.ts",
|
|
28
|
-
"./contract-purity": "./src/contract-purity.ts",
|
|
29
24
|
"./errors": "./src/error.schema.ts",
|
|
30
|
-
"./
|
|
31
|
-
"./
|
|
32
|
-
"./
|
|
25
|
+
"./gateway": "./src/manifest-gateway.ts",
|
|
26
|
+
"./impact": "./src/impact.ts",
|
|
27
|
+
"./manifest": "./src/manifest.schema.ts",
|
|
28
|
+
"./manifest-owners": "./src/manifest-owners.ts",
|
|
29
|
+
"./module-purity": "./src/module-purity.ts",
|
|
30
|
+
"./repo-adapter": "./src/repo-adapter.ts",
|
|
31
|
+
"./scan": "./src/scan.ts",
|
|
33
32
|
"./shared-tables": "./src/shared-tables.ts",
|
|
33
|
+
"./spec": "./src/spec.schema.ts",
|
|
34
|
+
"./tenant": "./src/tenant.ts",
|
|
34
35
|
"./test-kit": "./src/test-kit.ts",
|
|
35
|
-
"./
|
|
36
|
-
"./
|
|
37
|
-
"./
|
|
36
|
+
"./vocabulary": "./src/vocabulary.ts",
|
|
37
|
+
"./vocabulary-audit": "./src/vocabulary-audit.ts",
|
|
38
|
+
"./web-lint": "./src/web-lint.ts"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
41
|
"@electric-sql/pglite": "^0.3.0",
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* confirm, and that neither shadows the other.
|
|
6
6
|
*/
|
|
7
7
|
import { describe, expect, it } from "vitest";
|
|
8
|
-
import { declaredEntries, findSpecOwner } from "./manifest-owners.js";
|
|
8
|
+
import { declaredEntries, findSpecOwner, hasImplementation } from "./manifest-owners.js";
|
|
9
|
+
import type { SpecOwner, SpecOwnerKind } from "./manifest-owners.js";
|
|
9
10
|
import { OgygieManifestSchema } from "./manifest.schema.js";
|
|
10
11
|
|
|
11
12
|
function manifestWith(extra: Record<string, unknown>) {
|
|
@@ -250,3 +251,30 @@ describe("declaredEntries — tout ce que la carte déclare, à un seul endroit"
|
|
|
250
251
|
expect(declaredEntries(withApps({}))).toEqual([]);
|
|
251
252
|
});
|
|
252
253
|
});
|
|
254
|
+
|
|
255
|
+
describe("hasImplementation — ce qui prouve qu'une spec draft a déjà du code", () => {
|
|
256
|
+
const owner = (
|
|
257
|
+
kind: SpecOwnerKind,
|
|
258
|
+
status: "skeleton" | "in-progress" | "delivered",
|
|
259
|
+
): SpecOwner => ({ kind, name: "peu-importe", status, folder: "un/chemin" });
|
|
260
|
+
|
|
261
|
+
it("ÉTANT DONNÉ un statut au-delà de skeleton ALORS l'implémentation existe, dossier ou pas", () => {
|
|
262
|
+
for (const kind of ["feature", "package", "framework-package"] as SpecOwnerKind[]) {
|
|
263
|
+
expect(hasImplementation(owner(kind, "in-progress"), false)).toBe(true);
|
|
264
|
+
expect(hasImplementation(owner(kind, "delivered"), false)).toBe(true);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("une FEATURE skeleton dont le dossier existe a bien du code — c'est la preuve historique", () => {
|
|
269
|
+
expect(hasImplementation(owner("feature", "skeleton"), true)).toBe(true);
|
|
270
|
+
expect(hasImplementation(owner("feature", "skeleton"), false)).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("MUTATION : un PACKAGE skeleton dont le dossier existe n'a PAS de code — le manifeste EXIGE ce dossier", () => {
|
|
274
|
+
// Sans cette règle, une spec draft et un package skeleton ne peuvent pas
|
|
275
|
+
// coexister : verify-manifest réclame le dossier, verify-specs y lit du
|
|
276
|
+
// code, et le projet est coincé entre deux fichiers gouvernés.
|
|
277
|
+
expect(hasImplementation(owner("package", "skeleton"), true)).toBe(false);
|
|
278
|
+
expect(hasImplementation(owner("framework-package", "skeleton"), true)).toBe(false);
|
|
279
|
+
});
|
|
280
|
+
});
|
package/src/manifest-owners.ts
CHANGED
|
@@ -158,6 +158,28 @@ export function findSpecOwner(
|
|
|
158
158
|
return { kind: owner.kind, name: owner.name, status: owner.status, folder: owner.folder };
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Does an implementation of this spec exist? The question the `draft` rule of
|
|
163
|
+
* the spec lifecycle rests on ("draft = no code yet").
|
|
164
|
+
*
|
|
165
|
+
* A declared status above `skeleton` always answers yes. A FOLDER on disk only
|
|
166
|
+
* answers yes for a FEATURE, whose folder is born with its code and is
|
|
167
|
+
* therefore evidence of it. A package's folder is NOT evidence: the manifest
|
|
168
|
+
* gate REQUIRES it to exist (MANIFEST_PACKAGE_NOT_FOUND), so a package
|
|
169
|
+
* declared `skeleton` whose spec is still `draft` could satisfy neither gate —
|
|
170
|
+
* one demands the folder, the other reads it as code, and the project is stuck
|
|
171
|
+
* between two governed files it may not edit. Observed on a consumer project
|
|
172
|
+
* the day it declared its first package before writing it.
|
|
173
|
+
*
|
|
174
|
+
* Lives here rather than in the gate script for the reason the whole module
|
|
175
|
+
* exists: the rule travels with the versioned package, not with a copy each
|
|
176
|
+
* project keeps.
|
|
177
|
+
*/
|
|
178
|
+
export function hasImplementation(owner: SpecOwner, folderExists: boolean): boolean {
|
|
179
|
+
if (owner.status !== "skeleton") return true;
|
|
180
|
+
return owner.kind === "feature" && folderExists;
|
|
181
|
+
}
|
|
182
|
+
|
|
161
183
|
/** What a refusal should suggest — both natures, never one that cannot exist. */
|
|
162
184
|
export const SPEC_OWNER_FIX =
|
|
163
185
|
"Set the owning entry to delivered in the manifest — a feature under `features`, a library under `packages`, or a published package under `framework.packages` (which names its spec explicitly) — or keep the spec in validated.";
|
package/src/scan.test.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @intent Prouve la règle de parcours des gates, avec le cas qui l'a imposée :
|
|
3
|
+
* un projet Nuxt dont `typecheck` génère `.nuxt/` que `lint` refusait ensuite.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { isScannableDirectory, NEVER_SCANNED_DIRECTORIES } from "./scan.js";
|
|
7
|
+
|
|
8
|
+
describe("isScannableDirectory", () => {
|
|
9
|
+
it("parcourt le code du projet", () => {
|
|
10
|
+
for (const name of ["apps", "packages", "src", "scripts", "features", "specs"]) {
|
|
11
|
+
expect(isScannableDirectory(name)).toBe(true);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("ne parcourt AUCUN dossier en point — c'est la règle qui ne vieillit pas", () => {
|
|
16
|
+
for (const name of [".git", ".nuxt", ".output", ".next", ".svelte-kit", ".turbo", ".venv"]) {
|
|
17
|
+
expect(isScannableDirectory(name)).toBe(false);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("ne parcourt pas les artefacts au nom ordinaire", () => {
|
|
22
|
+
for (const name of NEVER_SCANNED_DIRECTORIES) expect(isScannableDirectory(name)).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("MUTATION : un dossier dont le nom COMMENCE par celui d'un artefact reste parcouru", () => {
|
|
26
|
+
// `distribution/` n'est pas `dist/`, et `buildings/` n'est pas `build/`.
|
|
27
|
+
expect(isScannableDirectory("distribution")).toBe(true);
|
|
28
|
+
expect(isScannableDirectory("buildings")).toBe(true);
|
|
29
|
+
expect(isScannableDirectory("node_modules_backup")).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
});
|
package/src/scan.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @intent Which directories a gate WALKS — the one rule, for every gate.
|
|
3
|
+
*
|
|
4
|
+
* It existed five times, slightly differently, in as many scripts. That is how
|
|
5
|
+
* it fell behind: the lists were written when a build produced `dist/`, and a
|
|
6
|
+
* modern framework generates `.nuxt/`, `.output/`, `.next/`, `.svelte-kit/`,
|
|
7
|
+
* while a Python package carries `.venv/`. A gate that walks into them reports
|
|
8
|
+
* bans and forbidden words found in code NOBODY WROTE — and worse, it does so
|
|
9
|
+
* only after a build, so the loop passes in CI and fails on the machine of
|
|
10
|
+
* whoever just ran it. A consumer project hit exactly that: `typecheck`
|
|
11
|
+
* generated `.nuxt/`, and `lint` then refused a type-suppression directive the
|
|
12
|
+
* framework had written into its own generated types.
|
|
13
|
+
*
|
|
14
|
+
* The rule is deliberately blunt, because a subtle one would fall behind again:
|
|
15
|
+
* a DOT-directory is never source. Everything a project actually writes lives
|
|
16
|
+
* in a plainly-named folder; tooling, caches, virtual environments and build
|
|
17
|
+
* output all hide behind a dot, by a convention older than any of these tools.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Plainly-named directories that are still artefacts, not source. */
|
|
21
|
+
export const NEVER_SCANNED_DIRECTORIES = [
|
|
22
|
+
"node_modules",
|
|
23
|
+
"dist",
|
|
24
|
+
"dist-ui",
|
|
25
|
+
"build",
|
|
26
|
+
"coverage",
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
/** Does a gate walk into this directory? Its NAME is the whole question. */
|
|
30
|
+
export function isScannableDirectory(name: string): boolean {
|
|
31
|
+
// `.git`, `.nuxt`, `.output`, `.next`, `.svelte-kit`, `.turbo`, `.venv`,
|
|
32
|
+
// `.pytest_cache`… — one rule instead of a list that ages.
|
|
33
|
+
if (name.startsWith(".")) return false;
|
|
34
|
+
return !(NEVER_SCANNED_DIRECTORIES as readonly string[]).includes(name);
|
|
35
|
+
}
|