@okfit/engine 0.4.0 → 0.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okfit/engine",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "The okfit engine: platform layer, config discovery, and the validate, verify, sync, init and context programs shared by the okfit CLI and MCP server.",
6
6
  "keywords": [
@@ -35,8 +35,8 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@effect/platform-node": "4.0.0-rc.115",
38
- "@effected/app": "^0.16.1",
39
- "@effected/config-file": "^0.9.0",
38
+ "@effected/app": "^0.16.2",
39
+ "@effected/config-file": "^0.10.0",
40
40
  "@effected/git": "^0.15.1",
41
41
  "@effected/glob": "^0.6.0",
42
42
  "@effected/jsonc": "^0.11.0",
@@ -44,10 +44,10 @@
44
44
  "@effected/store": "^0.8.0",
45
45
  "@effected/toml": "^0.7.0",
46
46
  "@effected/walker": "^0.9.0",
47
- "@effected/xdg": "^0.5.1",
47
+ "@effected/xdg": "^0.5.2",
48
48
  "@effected/yaml": "^0.15.1",
49
- "@okfit/core": "0.4.1",
50
- "@okfit/profiles": "0.5.0",
49
+ "@okfit/core": "0.5.0",
50
+ "@okfit/profiles": "0.7.0",
51
51
  "effect": "4.0.0-rc.115"
52
52
  },
53
53
  "engines": {
@@ -0,0 +1,73 @@
1
+ import { Diagnostic, OkfitConfig } from "@okfit/core";
2
+ import { Effect, FileSystem, Path } from "effect";
3
+
4
+ //#region src/validate/resources.ts
5
+ /** A `resource`/`sources[].resource` value that names a URL, not a bundle-relative path (D-24). */
6
+ const SCHEME_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
7
+ /** Glob metacharacters that mark a value as a descriptor pattern, not a literal path. */
8
+ const GLOB_RE = /[*?[{]/;
9
+ /**
10
+ * True when `value` is a descriptor (a URL, a scope description, or a glob
11
+ * pattern) rather than a literal bundle-relative path — D-24 leaves
12
+ * `Source.resource` untyped for exactly this reason: "a URL, a bundle path or
13
+ * a scope descriptor; never resolved here". Anything with a URL scheme
14
+ * (`https:`, `git:`, …) or a bare `://`, anything containing whitespace (a
15
+ * prose description like "the git history"), and anything containing a glob
16
+ * metacharacter (`src/**`) is a descriptor and is skipped, never resolved
17
+ * against the filesystem.
18
+ */
19
+ const isDescriptor = (value) => SCHEME_RE.test(value) || value.includes("://") || /\s/.test(value) || GLOB_RE.test(value);
20
+ /** Strips a trailing `#fragment` or `?query` suffix before filesystem resolution. */
21
+ const stripSuffix = (value) => {
22
+ const at = value.search(/[?#]/);
23
+ return at === -1 ? value : value.slice(0, at);
24
+ };
25
+ /** Every `resource` value on a concept: the top-level field, then each source's. */
26
+ const resourceValuesOf = (concept) => {
27
+ const values = [];
28
+ if (concept.frontmatter.resource !== void 0) values.push(concept.frontmatter.resource);
29
+ for (const source of concept.frontmatter.sources ?? []) values.push(source.resource);
30
+ return values;
31
+ };
32
+ /**
33
+ * D-24's `resource`/`sources[].resource` value resolves to nothing in the
34
+ * bundle for every non-descriptor value on every concept (issue #106). A
35
+ * `resource` is a descriptor (a URL, a scope description, or a glob
36
+ * pattern) or a literal path resolved in D-23 order: relative to the
37
+ * concept's own directory, then (for a bare path) relative to the bundle
38
+ * root, or with a leading `/` relative to the bundle root only; only the
39
+ * latter is checked against the filesystem. Total over severity (mirrors
40
+ * `Provenance.lint`'s S-28 posture): returns `[]`, touching neither
41
+ * `FileSystem` nor `Path`, the moment
42
+ * `OkfitConfig.severityFor(config, "source-resource-missing")` is `"off"`.
43
+ *
44
+ * @public
45
+ */
46
+ const lintResources = (bundle, config) => Effect.gen(function* () {
47
+ const severity = OkfitConfig.severityFor(config, "source-resource-missing");
48
+ if (severity === "off") return [];
49
+ const fs = yield* FileSystem.FileSystem;
50
+ const path = yield* Path.Path;
51
+ const diagnostics = [];
52
+ for (const [, concept] of bundle.concepts) for (const value of resourceValuesOf(concept)) {
53
+ if (isDescriptor(value)) continue;
54
+ const stripped = stripSuffix(value);
55
+ const candidates = stripped.startsWith("/") ? [path.join(bundle.root, stripped.slice(1))] : [path.resolve(bundle.root, path.dirname(concept.path), stripped), ...stripped.startsWith("./") || stripped.startsWith("../") ? [] : [path.resolve(bundle.root, stripped)]];
56
+ let exists = false;
57
+ for (const candidate of candidates) if (yield* fs.exists(candidate)) {
58
+ exists = true;
59
+ break;
60
+ }
61
+ if (exists) continue;
62
+ diagnostics.push(Diagnostic.make({
63
+ file: concept.path,
64
+ code: "source-resource-missing",
65
+ severity,
66
+ message: `Resource "${value}" does not exist relative to ${concept.path}`
67
+ }));
68
+ }
69
+ return diagnostics;
70
+ });
71
+
72
+ //#endregion
73
+ export { lintResources };
package/validate/run.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { lintResources } from "./resources.js";
1
2
  import { Bundle, OkfitConfig, Validate } from "@okfit/core";
2
3
  import { Context, Effect, Option } from "effect";
3
4
  import { Provenance } from "@okfit/profiles";
@@ -53,11 +54,16 @@ const run = (options) => Effect.gen(function* () {
53
54
  onSome: (profile) => profile.check(bundle)
54
55
  });
55
56
  const provenance = OkfitConfig.severityFor(options.config, "generated-at-drift") === "off" ? [] : yield* Provenance.lint(bundle, options.config, { skipGitTier: options.skipProvenance === true });
57
+ const resources = yield* lintResources(bundle, options.config);
56
58
  return {
57
59
  bundle,
58
60
  report: {
59
61
  ...report,
60
- lint: [...report.lint, ...provenance]
62
+ lint: [
63
+ ...report.lint,
64
+ ...provenance,
65
+ ...resources
66
+ ]
61
67
  },
62
68
  profileDiagnostics
63
69
  };