@okfit/engine 0.4.1 → 0.6.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/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import { AppDirs, Xdg } from "@effected/xdg";
4
4
  import { ConfigReadError } from "@effected/config-file";
5
5
  import { GeneratedAtError, GitHistory, GitHistoryError, Layout, Profile, ProfileDiagnostic } from "@okfit/profiles";
6
6
  import { FrontmatterWriteError, MarkdownParseError } from "@effected/markdown";
7
+ import { HostedSchema } from "@effected/schemastore";
7
8
  import * as NodeServices from "@effect/platform-node/NodeServices";
8
9
  import { Git, GitCommandError, UnknownRefError } from "@effected/git";
9
10
  //#region src/config/anchor.d.ts
@@ -353,8 +354,17 @@ export declare const runGraph: (options: GraphRunOptions) => Effect.Effect<Graph
353
354
  //#region src/init/scaffold.d.ts
354
355
  /** `.config/okfit.toml`, relative to the project root (C-10). @public */
355
356
  export declare const CONFIG_RELATIVE_PATH = ".config/okfit.toml";
357
+ /**
358
+ * Where the published `config` JSON Schema document lives and which version
359
+ * is current — the one identity `schemastore.config.ts` and `okfit init`'s
360
+ * `#:schema` directive both derive from, so the URL a fresh config points at
361
+ * and the one the generated document declares can never disagree (C-18).
362
+ *
363
+ * @public
364
+ */
365
+ export declare const okfitConfigSchemaHost: HostedSchema;
356
366
  /** C-22's directive, plus the blank line Tombi requires. @public */
357
- export declare const SCHEMA_DIRECTIVE = "#:schema https://raw.githubusercontent.com/spencerbeggs/okfit/main/schemas/config/okfit-1.0.0.json\n\n";
367
+ export declare const SCHEMA_DIRECTIVE: string;
358
368
  /** @public */
359
369
  interface ScaffoldOptions {
360
370
  /** Absolute. */
package/index.js CHANGED
@@ -4,7 +4,7 @@ import { buildConfigLayer, provideConfig } from "./config/layer.js";
4
4
  import { DEFAULT_PROFILE_NAME, resolveProjectConfig } from "./config/resolve.js";
5
5
  import { runContext } from "./context/run.js";
6
6
  import { runGraph } from "./graph/run.js";
7
- import { CONFIG_RELATIVE_PATH, SCHEMA_DIRECTIVE, configValue, files, targetPaths } from "./init/scaffold.js";
7
+ import { CONFIG_RELATIVE_PATH, SCHEMA_DIRECTIVE, configValue, files, okfitConfigSchemaHost, targetPaths } from "./init/scaffold.js";
8
8
  import { OkfitPlatform } from "./platform.js";
9
9
  import { ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, contextEnvelope } from "./render/context.js";
10
10
  import { forDiagnostics, tally } from "./render/exit.js";
@@ -19,4 +19,4 @@ import { runStale } from "./stale/run.js";
19
19
  import { Now, run } from "./validate/run.js";
20
20
  import { runVerify } from "./verify/run.js";
21
21
 
22
- export { CONFIG_RELATIVE_PATH, ConfigMalformedError, ConfigPathNotFoundError, ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, DEFAULT_PROFILE_NAME, GraphEdgeEnvelope, GraphEnvelope, GraphNodeEnvelope, GraphSummary, InitOverwriteError, JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, Now, OkfitPlatform, SCHEMA_DIRECTIVE, SkipReason, StaleEnvelope, StaleItem, StaleSummary, SyncEnvelope, SyncModeEnvelope, VerifyConceptNotFoundError, VerifyEnvelope, VerifyUnsupportedFrontmatterError, buildConfigLayer, collect, configValue, contextEnvelope, files, forDiagnostics, graphEnvelope, json, jsonError, provideConfig, resolveBundleRoot, resolveProjectConfig, resolveProjectRoot, run, runContext, runGraph, runStale, runSync, runVerify, sort, staleEnvelope, syncEnvelope, tally, targetPaths, verifyEnvelope };
22
+ export { CONFIG_RELATIVE_PATH, ConfigMalformedError, ConfigPathNotFoundError, ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, DEFAULT_PROFILE_NAME, GraphEdgeEnvelope, GraphEnvelope, GraphNodeEnvelope, GraphSummary, InitOverwriteError, JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, Now, OkfitPlatform, SCHEMA_DIRECTIVE, SkipReason, StaleEnvelope, StaleItem, StaleSummary, SyncEnvelope, SyncModeEnvelope, VerifyConceptNotFoundError, VerifyEnvelope, VerifyUnsupportedFrontmatterError, buildConfigLayer, collect, configValue, contextEnvelope, files, forDiagnostics, graphEnvelope, json, jsonError, okfitConfigSchemaHost, provideConfig, resolveBundleRoot, resolveProjectConfig, resolveProjectRoot, run, runContext, runGraph, runStale, runSync, runVerify, sort, staleEnvelope, syncEnvelope, tally, targetPaths, verifyEnvelope };
package/init/scaffold.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Concept, ConceptId, Derive, LoadedConcept, OKF_SPEC_VERSION } from "@okfit/core";
2
2
  import { Effect, Option, Schema } from "effect";
3
3
  import { MarkdownDocument, MarkdownFrontmatter, MarkdownParseOptions, YamlFrontmatter } from "@effected/markdown";
4
+ import { HostedSchema } from "@effected/schemastore";
4
5
 
5
6
  //#region src/init/scaffold.ts
6
7
  /** `.config/okfit.toml`, relative to the project root (C-10). @public */
@@ -21,8 +22,23 @@ const PROJECT_CONFIG_NAMES = [
21
22
  "okfit.toml",
22
23
  ".config/okfit.toml"
23
24
  ];
25
+ /**
26
+ * Where the published `config` JSON Schema document lives and which version
27
+ * is current — the one identity `schemastore.config.ts` and `okfit init`'s
28
+ * `#:schema` directive both derive from, so the URL a fresh config points at
29
+ * and the one the generated document declares can never disagree (C-18).
30
+ *
31
+ * @public
32
+ */
33
+ const okfitConfigSchemaHost = HostedSchema.github({
34
+ repo: "spencerbeggs/okfit",
35
+ path: "schemas",
36
+ name: "config",
37
+ versions: ["1.0"],
38
+ appendVersion: false
39
+ });
24
40
  /** C-22's directive, plus the blank line Tombi requires. @public */
25
- const SCHEMA_DIRECTIVE = "#:schema https://raw.githubusercontent.com/spencerbeggs/okfit/main/schemas/config/okfit-1.0.0.json\n\n";
41
+ const SCHEMA_DIRECTIVE = `#:schema ${okfitConfigSchemaHost.$id}\n\n`;
26
42
  /**
27
43
  * K-23: the THIN override only — never the merged config. `bundle.path` is
28
44
  * the bundle root relative to the project root; `extensions: {}` satisfies
@@ -171,4 +187,4 @@ const files = (options) => Effect.gen(function* () {
171
187
  });
172
188
 
173
189
  //#endregion
174
- export { CONFIG_RELATIVE_PATH, PROJECT_CONFIG_NAMES, SCHEMA_DIRECTIVE, configValue, files, targetPaths };
190
+ export { CONFIG_RELATIVE_PATH, PROJECT_CONFIG_NAMES, SCHEMA_DIRECTIVE, configValue, files, okfitConfigSchemaHost, targetPaths };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okfit/engine",
3
- "version": "0.4.1",
3
+ "version": "0.6.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,19 +35,20 @@
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",
43
43
  "@effected/markdown": "^0.10.0",
44
+ "@effected/schemastore": "^0.12.0",
44
45
  "@effected/store": "^0.8.0",
45
46
  "@effected/toml": "^0.7.0",
46
47
  "@effected/walker": "^0.9.0",
47
- "@effected/xdg": "^0.5.1",
48
+ "@effected/xdg": "^0.5.2",
48
49
  "@effected/yaml": "^0.15.1",
49
- "@okfit/core": "0.4.1",
50
- "@okfit/profiles": "0.6.0",
50
+ "@okfit/core": "0.6.0",
51
+ "@okfit/profiles": "0.7.1",
51
52
  "effect": "4.0.0-rc.115"
52
53
  },
53
54
  "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
  };