@okfit/engine 0.1.0 → 0.2.1
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/config/anchor.js +1 -1
- package/index.d.ts +24 -16
- package/package.json +16 -16
- package/sync/generated.js +11 -9
- package/tsdoc-metadata.json +1 -1
- package/validate/run.js +10 -6
- package/verify/locate.js +25 -15
- package/verify/splice.js +31 -9
package/config/anchor.js
CHANGED
|
@@ -15,7 +15,7 @@ const anchorForExplicit = (configPath, path) => {
|
|
|
15
15
|
/**
|
|
16
16
|
* K-12's project root, as amended by K-58 and C1-2, in order:
|
|
17
17
|
*
|
|
18
|
-
* 1. `pathArg`, if given. `Argument.
|
|
18
|
+
* 1. `pathArg`, if given. `Argument.Path` has already resolved it absolute.
|
|
19
19
|
* 2. otherwise, if `--config` was given: `anchorForExplicit` applied to that
|
|
20
20
|
* path.
|
|
21
21
|
* 3. otherwise, if a config was discovered by the `"project"` resolver
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BundleLoadError, ConceptId, Diagnostic, DiagnosticRange, DiagnosticSeverity, LoadedBundle, OkfitConfig, OkfitConfigFile, ValidationReport } from "@okfit/core";
|
|
2
|
-
import { Context, DateTime, Effect, FileSystem, Layer, Option, Path, PlatformError, Runtime, Schema } from "effect";
|
|
2
|
+
import { Context, Crypto, DateTime, Effect, FileSystem, Layer, Option, Path, PlatformError, Runtime, Schema } from "effect";
|
|
3
3
|
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";
|
|
@@ -32,7 +32,7 @@ interface DiscoveredConfig {
|
|
|
32
32
|
/**
|
|
33
33
|
* K-12's project root, as amended by K-58 and C1-2, in order:
|
|
34
34
|
*
|
|
35
|
-
* 1. `pathArg`, if given. `Argument.
|
|
35
|
+
* 1. `pathArg`, if given. `Argument.Path` has already resolved it absolute.
|
|
36
36
|
* 2. otherwise, if `--config` was given: `anchorForExplicit` applied to that
|
|
37
37
|
* path.
|
|
38
38
|
* 3. otherwise, if a config was discovered by the `"project"` resolver
|
|
@@ -714,7 +714,7 @@ interface SyncResult {
|
|
|
714
714
|
*
|
|
715
715
|
* @public
|
|
716
716
|
*/
|
|
717
|
-
export declare const runSync: (options: SyncOptions) => Effect.Effect<SyncResult, BundleLoadError | GeneratedAtError, Git | GitHistory | FileSystem.FileSystem | Path.Path>;
|
|
717
|
+
export declare const runSync: (options: SyncOptions) => Effect.Effect<SyncResult, BundleLoadError | GeneratedAtError, Git | GitHistory | FileSystem.FileSystem | Path.Path | Crypto.Crypto>;
|
|
718
718
|
//#endregion
|
|
719
719
|
//#region src/render/sync.d.ts
|
|
720
720
|
/** @public */
|
|
@@ -860,13 +860,17 @@ interface RunOptions {
|
|
|
860
860
|
/** `OKFIT_NOW` or `DateTime.now`, from `bin.ts` (K-47); enables the `stale` rule (D-34). */
|
|
861
861
|
readonly now: DateTime.Utc;
|
|
862
862
|
/**
|
|
863
|
-
* S-31: skip `Provenance.lint`'s git tier
|
|
864
|
-
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
* the
|
|
863
|
+
* S-31: skip `Provenance.lint`'s git TIER (tier 2, the git-derived date
|
|
864
|
+
* comparison for concepts with no recorded `generated.body_sha256`)
|
|
865
|
+
* for this one invocation, without touching the project's `[lint]`
|
|
866
|
+
* table. `commands/validate.ts` threads `--skip-provenance` here; the
|
|
867
|
+
* PostToolUse hook passes the flag so an edit-time `validate` stays
|
|
868
|
+
* git-free even when the config leaves `generated-at-drift` at its
|
|
869
|
+
* default severity. Tier 1 (the pure, in-memory body-digest comparison)
|
|
870
|
+
* still runs regardless — that is exactly the signal edit time wants,
|
|
871
|
+
* at the cost of one sha256 over text already in memory. Defaults to
|
|
872
|
+
* `false`; `run` still gates the whole lint call on `severity ===
|
|
873
|
+
* "off"`, but no longer on this flag.
|
|
870
874
|
*/
|
|
871
875
|
readonly skipProvenance?: boolean;
|
|
872
876
|
}
|
|
@@ -879,11 +883,15 @@ interface RunResult {
|
|
|
879
883
|
}
|
|
880
884
|
/**
|
|
881
885
|
* Load, validate both tiers, run the profile check, then — UNLESS the
|
|
882
|
-
* `generated-at-drift` lint is `off`
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
* `
|
|
886
|
-
*
|
|
886
|
+
* `generated-at-drift` lint is `off` — run `Provenance.lint` and append its
|
|
887
|
+
* `Diagnostic`s to `report.lint`. The "skip the whole lint" gate lives
|
|
888
|
+
* HERE, in `run`: a bundle configured `off` never calls `Provenance.lint`
|
|
889
|
+
* at all. `options.skipProvenance` is no longer that gate — it is threaded
|
|
890
|
+
* through as `Provenance.lint`'s `skipGitTier` option instead, so tier 1
|
|
891
|
+
* (the pure, in-memory `generated.body_sha256` comparison) still runs and
|
|
892
|
+
* still reports drift even when `skipProvenance` is `true`; only tier 2
|
|
893
|
+
* (the git-derived date comparison, for concepts with no recorded digest)
|
|
894
|
+
* is skipped, and with it the only git spawn `Provenance.lint` can make
|
|
887
895
|
* (S-31). An `error` severity on the appended diagnostics yields exit `1`
|
|
888
896
|
* through the EXISTING lint-tier rule in `render/exit.ts` — no renderer
|
|
889
897
|
* branch, no new `DiagnosticSource`, since these diagnostics flow through
|
|
@@ -899,7 +907,7 @@ interface RunResult {
|
|
|
899
907
|
*
|
|
900
908
|
* @public
|
|
901
909
|
*/
|
|
902
|
-
export declare const run: (options: RunOptions) => Effect.Effect<RunResult, BundleLoadError | GitHistoryError | GitCommandError | UnknownRefError | PlatformError.PlatformError, FileSystem.FileSystem | Path.Path | Git | GitHistory>;
|
|
910
|
+
export declare const run: (options: RunOptions) => Effect.Effect<RunResult, BundleLoadError | GitHistoryError | GitCommandError | UnknownRefError | PlatformError.PlatformError, FileSystem.FileSystem | Path.Path | Git | GitHistory | Crypto.Crypto>;
|
|
903
911
|
//#endregion
|
|
904
912
|
//#region src/verify/run.d.ts
|
|
905
913
|
/** @internal */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okfit/engine",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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": [
|
|
@@ -34,21 +34,21 @@
|
|
|
34
34
|
"./package.json": "./package.json"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@effect/platform-node": "4.0.0-rc.
|
|
38
|
-
"@effected/app": "^0.
|
|
39
|
-
"@effected/config-file": "^0.
|
|
40
|
-
"@effected/git": "^0.
|
|
41
|
-
"@effected/glob": "^0.
|
|
42
|
-
"@effected/jsonc": "^0.
|
|
43
|
-
"@effected/markdown": "^0.
|
|
44
|
-
"@effected/store": "^0.
|
|
45
|
-
"@effected/toml": "^0.
|
|
46
|
-
"@effected/walker": "^0.
|
|
47
|
-
"@effected/xdg": "^0.
|
|
48
|
-
"@effected/yaml": "^0.
|
|
49
|
-
"@okfit/core": "0.
|
|
50
|
-
"@okfit/profiles": "0.
|
|
51
|
-
"effect": "4.0.0-rc.
|
|
37
|
+
"@effect/platform-node": "4.0.0-rc.115",
|
|
38
|
+
"@effected/app": "^0.16.0",
|
|
39
|
+
"@effected/config-file": "^0.8.0",
|
|
40
|
+
"@effected/git": "^0.15.0",
|
|
41
|
+
"@effected/glob": "^0.6.0",
|
|
42
|
+
"@effected/jsonc": "^0.11.0",
|
|
43
|
+
"@effected/markdown": "^0.10.0",
|
|
44
|
+
"@effected/store": "^0.8.0",
|
|
45
|
+
"@effected/toml": "^0.7.0",
|
|
46
|
+
"@effected/walker": "^0.8.0",
|
|
47
|
+
"@effected/xdg": "^0.5.0",
|
|
48
|
+
"@effected/yaml": "^0.15.0",
|
|
49
|
+
"@okfit/core": "0.3.1",
|
|
50
|
+
"@okfit/profiles": "0.3.1",
|
|
51
|
+
"effect": "4.0.0-rc.115"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=24.11.0"
|
package/sync/generated.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { detectNewline, locateGenerated, stripBom } from "../verify/locate.js";
|
|
2
|
-
import {
|
|
1
|
+
import { detectNewline, locateGenerated, locateGeneratedBodySha256, stripBom } from "../verify/locate.js";
|
|
2
|
+
import { spliceGeneratedFields } from "../verify/splice.js";
|
|
3
3
|
import { writeAtomic } from "./write.js";
|
|
4
4
|
import { Timestamp } from "@okfit/core";
|
|
5
|
-
import {
|
|
5
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
6
|
+
import { Derivation } from "@okfit/profiles";
|
|
6
7
|
import { MarkdownEdit } from "@effected/markdown";
|
|
7
8
|
|
|
8
9
|
//#region src/sync/generated.ts
|
|
@@ -73,8 +74,8 @@ const syncGenerated = Effect.fn("okfit/sync/syncGenerated")(function* (bundle, p
|
|
|
73
74
|
});
|
|
74
75
|
continue;
|
|
75
76
|
}
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
77
|
+
const currentDigest = yield* Derivation.bodyDigest(concept.document.source);
|
|
78
|
+
if (generated.body_sha256 !== void 0 && generated.body_sha256 === currentDigest) {
|
|
78
79
|
unchanged.push(id);
|
|
79
80
|
continue;
|
|
80
81
|
}
|
|
@@ -82,8 +83,9 @@ const syncGenerated = Effect.fn("okfit/sync/syncGenerated")(function* (bundle, p
|
|
|
82
83
|
const source = yield* fs.readFileString(absolutePath);
|
|
83
84
|
const { text, bom } = stripBom(source);
|
|
84
85
|
const newline = detectNewline(text);
|
|
85
|
-
const
|
|
86
|
-
|
|
86
|
+
const atLocated = yield* locateGenerated(text).pipe(Effect.orDie);
|
|
87
|
+
const bodySha256Located = yield* locateGeneratedBodySha256(text).pipe(Effect.orDie);
|
|
88
|
+
if (atLocated._tag === "unsupported" || bodySha256Located._tag === "unsupported") {
|
|
87
89
|
skipped.push({
|
|
88
90
|
id,
|
|
89
91
|
reason: "generated-unsupported"
|
|
@@ -91,8 +93,8 @@ const syncGenerated = Effect.fn("okfit/sync/syncGenerated")(function* (bundle, p
|
|
|
91
93
|
continue;
|
|
92
94
|
}
|
|
93
95
|
const encodedAt = encodeAt(derived.at);
|
|
94
|
-
const
|
|
95
|
-
const finalText = bom + MarkdownEdit.applyAll(text,
|
|
96
|
+
const edits = spliceGeneratedFields(atLocated, bodySha256Located, encodedAt, currentDigest, newline);
|
|
97
|
+
const finalText = bom + MarkdownEdit.applyAll(text, edits);
|
|
96
98
|
if (dryRun) {
|
|
97
99
|
written.push(id);
|
|
98
100
|
continue;
|
package/tsdoc-metadata.json
CHANGED
package/validate/run.js
CHANGED
|
@@ -21,11 +21,15 @@ import { Provenance } from "@okfit/profiles";
|
|
|
21
21
|
var Now = class extends Context.Service()("@okfit/engine/Now") {};
|
|
22
22
|
/**
|
|
23
23
|
* Load, validate both tiers, run the profile check, then — UNLESS the
|
|
24
|
-
* `generated-at-drift` lint is `off`
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
24
|
+
* `generated-at-drift` lint is `off` — run `Provenance.lint` and append its
|
|
25
|
+
* `Diagnostic`s to `report.lint`. The "skip the whole lint" gate lives
|
|
26
|
+
* HERE, in `run`: a bundle configured `off` never calls `Provenance.lint`
|
|
27
|
+
* at all. `options.skipProvenance` is no longer that gate — it is threaded
|
|
28
|
+
* through as `Provenance.lint`'s `skipGitTier` option instead, so tier 1
|
|
29
|
+
* (the pure, in-memory `generated.body_sha256` comparison) still runs and
|
|
30
|
+
* still reports drift even when `skipProvenance` is `true`; only tier 2
|
|
31
|
+
* (the git-derived date comparison, for concepts with no recorded digest)
|
|
32
|
+
* is skipped, and with it the only git spawn `Provenance.lint` can make
|
|
29
33
|
* (S-31). An `error` severity on the appended diagnostics yields exit `1`
|
|
30
34
|
* through the EXISTING lint-tier rule in `render/exit.ts` — no renderer
|
|
31
35
|
* branch, no new `DiagnosticSource`, since these diagnostics flow through
|
|
@@ -48,7 +52,7 @@ const run = (options) => Effect.gen(function* () {
|
|
|
48
52
|
onNone: () => [],
|
|
49
53
|
onSome: (profile) => profile.check(bundle)
|
|
50
54
|
});
|
|
51
|
-
const provenance = OkfitConfig.severityFor(options.config, "generated-at-drift") === "off"
|
|
55
|
+
const provenance = OkfitConfig.severityFor(options.config, "generated-at-drift") === "off" ? [] : yield* Provenance.lint(bundle, options.config, { skipGitTier: options.skipProvenance === true });
|
|
52
56
|
return {
|
|
53
57
|
bundle,
|
|
54
58
|
report: {
|
package/verify/locate.js
CHANGED
|
@@ -157,7 +157,7 @@ const locate = Effect.fn("okfit/verify/locate")(function* (source) {
|
|
|
157
157
|
*
|
|
158
158
|
* @internal
|
|
159
159
|
*/
|
|
160
|
-
const
|
|
160
|
+
const locateGeneratedField = Effect.fn("okfit/verify/locateGeneratedField")(function* (source, field) {
|
|
161
161
|
const block = FrontmatterSource.split(source).frontmatter;
|
|
162
162
|
if (block === void 0) return {
|
|
163
163
|
_tag: "unsupported",
|
|
@@ -205,8 +205,8 @@ const locateGenerated = Effect.fn("okfit/verify/locateGenerated")(function* (sou
|
|
|
205
205
|
shape: "flow-mapping"
|
|
206
206
|
};
|
|
207
207
|
const indent = indentAt(value, node.offset);
|
|
208
|
-
const
|
|
209
|
-
if (
|
|
208
|
+
const fieldPair = node.items.find((item) => item.key instanceof YamlScalar && item.key.value === field);
|
|
209
|
+
if (fieldPair === void 0) {
|
|
210
210
|
const last = node.items[node.items.length - 1];
|
|
211
211
|
if (last === void 0) return {
|
|
212
212
|
_tag: "unsupported",
|
|
@@ -220,30 +220,40 @@ const locateGenerated = Effect.fn("okfit/verify/locateGenerated")(function* (sou
|
|
|
220
220
|
indent
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
223
|
+
const fieldValue = fieldPair.value;
|
|
224
|
+
if (fieldValue === null) return {
|
|
225
225
|
_tag: "unsupported",
|
|
226
|
-
shape:
|
|
226
|
+
shape: `${field}-empty`
|
|
227
227
|
};
|
|
228
|
-
if (
|
|
228
|
+
if (fieldValue instanceof YamlAlias) return {
|
|
229
229
|
_tag: "unsupported",
|
|
230
230
|
shape: "alias"
|
|
231
231
|
};
|
|
232
|
-
if (!(
|
|
232
|
+
if (!(fieldValue instanceof YamlScalar)) return {
|
|
233
233
|
_tag: "unsupported",
|
|
234
|
-
shape:
|
|
234
|
+
shape: `${field}-not-scalar`
|
|
235
235
|
};
|
|
236
|
-
if (
|
|
236
|
+
if (fieldValue.style === "block-literal" || fieldValue.style === "block-folded") return {
|
|
237
237
|
_tag: "unsupported",
|
|
238
|
-
shape:
|
|
238
|
+
shape: `${field}-block-scalar`
|
|
239
239
|
};
|
|
240
240
|
return {
|
|
241
241
|
_tag: "replaceScalar",
|
|
242
|
-
start: valueStart +
|
|
243
|
-
end: valueStart +
|
|
244
|
-
quote:
|
|
242
|
+
start: valueStart + fieldValue.offset,
|
|
243
|
+
end: valueStart + fieldValue.offset + fieldValue.length,
|
|
244
|
+
quote: fieldValue.style
|
|
245
245
|
};
|
|
246
246
|
});
|
|
247
|
+
const locateGenerated = (source) => locateGeneratedField(source, "at");
|
|
248
|
+
/**
|
|
249
|
+
* As {@link locateGenerated}, but for the top-level `generated.body_sha256`
|
|
250
|
+
* key (issue #19). Shares every branch with `locateGenerated` -- the two
|
|
251
|
+
* fields are siblings of the same `generated:` block mapping -- parametrized
|
|
252
|
+
* only by which key name is sought.
|
|
253
|
+
*
|
|
254
|
+
* @internal
|
|
255
|
+
*/
|
|
256
|
+
const locateGeneratedBodySha256 = (source) => locateGeneratedField(source, "body_sha256");
|
|
247
257
|
|
|
248
258
|
//#endregion
|
|
249
|
-
export { detectNewline, documentNewline, indentAt, locate, locateGenerated, stripBom };
|
|
259
|
+
export { detectNewline, documentNewline, indentAt, locate, locateGenerated, locateGeneratedBodySha256, stripBom };
|
package/verify/splice.js
CHANGED
|
@@ -45,23 +45,24 @@ const splice = (target, entry, newline) => {
|
|
|
45
45
|
};
|
|
46
46
|
/**
|
|
47
47
|
* Build the one edit for `target` (contract §6.1). Never calls a YAML
|
|
48
|
-
* stringifier: the only syntax emitted is the indent,
|
|
49
|
-
* preserved quote character (if any), and `newline`. `
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* endings, V-12/V-15
|
|
48
|
+
* stringifier: the only syntax emitted is the indent, `<field>: `, the
|
|
49
|
+
* preserved quote character (if any), and `newline`. `value` is already
|
|
50
|
+
* encoded by the caller -- `Schema.encodeSync(Timestamp)`'d for `at`, the raw
|
|
51
|
+
* lowercase hex digest for `body_sha256` (S-1: splice, never `YamlFormat.
|
|
52
|
+
* modify` — it drops quote style and re-normalises line endings, V-12/V-15
|
|
53
|
+
* violations `sync/generated.ts` cannot afford).
|
|
53
54
|
*
|
|
54
55
|
* @internal
|
|
55
56
|
*/
|
|
56
|
-
const spliceGenerated = (target,
|
|
57
|
+
const spliceGenerated = (target, value, newline, field = "at") => {
|
|
57
58
|
switch (target._tag) {
|
|
58
59
|
case "insertAfterLastKey": return MarkdownEdit.make({
|
|
59
60
|
offset: target.insertAt,
|
|
60
61
|
length: 0,
|
|
61
|
-
content: `${target.indent}
|
|
62
|
+
content: `${target.indent}${field}: ${value}${newline}`
|
|
62
63
|
});
|
|
63
64
|
case "replaceScalar": {
|
|
64
|
-
const quoted = target.quote === "single-quoted" ? `'${
|
|
65
|
+
const quoted = target.quote === "single-quoted" ? `'${value}'` : target.quote === "double-quoted" ? `"${value}"` : value;
|
|
65
66
|
return MarkdownEdit.make({
|
|
66
67
|
offset: target.start,
|
|
67
68
|
length: target.end - target.start,
|
|
@@ -70,6 +71,27 @@ const spliceGenerated = (target, encodedAt, newline) => {
|
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Build the edit(s) that write BOTH `generated.at` and `generated.
|
|
76
|
+
* body_sha256` together (issue #19: `sync/generated.ts` never writes one
|
|
77
|
+
* without the other). Each field's own `spliceGenerated` edit is used when
|
|
78
|
+
* the two land at different offsets; when both are absent they resolve to
|
|
79
|
+
* the identical `insertAfterLastKey` anchor (the mapping's last existing
|
|
80
|
+
* key), and inserting two zero-length edits at the same offset is exactly
|
|
81
|
+
* the "overlapping edits" case `MarkdownEdit.applyAll` treats as a
|
|
82
|
+
* programmer error -- so that one case is merged into a single edit whose
|
|
83
|
+
* content carries both lines, `at` first.
|
|
84
|
+
*
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
const spliceGeneratedFields = (atTarget, bodySha256Target, encodedAt, bodySha256, newline) => {
|
|
88
|
+
if (atTarget._tag === "insertAfterLastKey" && bodySha256Target._tag === "insertAfterLastKey" && atTarget.insertAt === bodySha256Target.insertAt) return [MarkdownEdit.make({
|
|
89
|
+
offset: atTarget.insertAt,
|
|
90
|
+
length: 0,
|
|
91
|
+
content: `${atTarget.indent}at: ${encodedAt}${newline}${bodySha256Target.indent}body_sha256: ${bodySha256}${newline}`
|
|
92
|
+
})];
|
|
93
|
+
return [spliceGenerated(atTarget, encodedAt, newline, "at"), spliceGenerated(bodySha256Target, bodySha256, newline, "body_sha256")];
|
|
94
|
+
};
|
|
73
95
|
|
|
74
96
|
//#endregion
|
|
75
|
-
export { splice, spliceGenerated };
|
|
97
|
+
export { splice, spliceGenerated, spliceGeneratedFields };
|