@okfit/engine 0.3.0 → 0.4.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/graph/run.js ADDED
@@ -0,0 +1,20 @@
1
+ import { Bundle, Graph } from "@okfit/core";
2
+ import { Effect } from "effect";
3
+
4
+ //#region src/graph/run.ts
5
+ /**
6
+ * Load the bundle and build its link graph (D-6, D-26). Pure over the
7
+ * loaded bundle once loading succeeds; `okfit graph` always exits `0`.
8
+ *
9
+ * @public
10
+ */
11
+ const runGraph = (options) => Effect.gen(function* () {
12
+ const bundle = yield* Bundle.load({ root: options.root });
13
+ return {
14
+ bundle,
15
+ graph: Graph.fromBundle(bundle)
16
+ };
17
+ });
18
+
19
+ //#endregion
20
+ export { runGraph };
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BundleLoadError, ConceptId, Diagnostic, DiagnosticRange, DiagnosticSeverity, LoadedBundle, OkfitConfig, OkfitConfigFile, ValidationReport } from "@okfit/core";
1
+ import { BundleLoadError, ConceptId, Diagnostic, DiagnosticRange, DiagnosticSeverity, GraphLink, GraphNode, LinkGraph, LoadedBundle, OkfitConfig, OkfitConfigFile, StaleConcept, ValidationReport } from "@okfit/core";
2
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";
@@ -331,6 +331,25 @@ interface ContextResult {
331
331
  */
332
332
  export declare const runContext: (options: ContextRunOptions) => Effect.Effect<ContextResult, never, FileSystem.FileSystem | Path.Path>;
333
333
  //#endregion
334
+ //#region src/graph/run.d.ts
335
+ /** @public */
336
+ interface GraphRunOptions {
337
+ /** Absolute bundle root; `Bundle.load` never reads cwd (D-8). */
338
+ readonly root: string;
339
+ }
340
+ /** @public */
341
+ interface GraphRunResult {
342
+ readonly bundle: LoadedBundle;
343
+ readonly graph: LinkGraph;
344
+ }
345
+ /**
346
+ * Load the bundle and build its link graph (D-6, D-26). Pure over the
347
+ * loaded bundle once loading succeeds; `okfit graph` always exits `0`.
348
+ *
349
+ * @public
350
+ */
351
+ export declare const runGraph: (options: GraphRunOptions) => Effect.Effect<GraphRunResult, BundleLoadError, FileSystem.FileSystem | Path.Path>;
352
+ //#endregion
334
353
  //#region src/init/scaffold.d.ts
335
354
  /** `.config/okfit.toml`, relative to the project root (C-10). @public */
336
355
  export declare const CONFIG_RELATIVE_PATH = ".config/okfit.toml";
@@ -626,6 +645,79 @@ export declare const tally: (diagnostics: ReadonlyArray<RenderedDiagnostic>) =>
626
645
  */
627
646
  export declare const forDiagnostics: (diagnostics: ReadonlyArray<RenderedDiagnostic>) => 0 | 1 | 2;
628
647
  //#endregion
648
+ //#region src/render/graph.d.ts
649
+ /** One entry of the `nodes` array. @public */
650
+ export declare const GraphNodeEnvelope: Schema.Struct<{
651
+ readonly id: Schema.String;
652
+ readonly kind: Schema.Literals<readonly ["concept", "file", "missing"]>;
653
+ }>;
654
+ /** @public */
655
+ export type GraphNodeEnvelope = typeof GraphNodeEnvelope.Type;
656
+ /** One entry of the `edges` array; `field` is omitted, never `null` (exactOptionalPropertyTypes). @public */
657
+ export declare const GraphEdgeEnvelope: Schema.Struct<{
658
+ readonly from: Schema.String;
659
+ readonly to: Schema.String;
660
+ readonly source: Schema.Literals<readonly ["body", "frontmatter"]>;
661
+ readonly field: Schema.optionalKey<Schema.String>;
662
+ readonly raw: Schema.String;
663
+ }>;
664
+ /** @public */
665
+ export type GraphEdgeEnvelope = typeof GraphEdgeEnvelope.Type;
666
+ /** @public */
667
+ export declare const GraphSummary: Schema.Struct<{
668
+ readonly nodes: Schema.Number;
669
+ readonly edges: Schema.Number;
670
+ }>;
671
+ /** @public */
672
+ export type GraphSummary = typeof GraphSummary.Type;
673
+ /**
674
+ * `okfit graph --format json`'s envelope, schema 1, snake_case — the same
675
+ * convention as `render/json.ts`'s `JsonEnvelope`. Built from
676
+ * `LinkGraph.nodes`/`LinkGraph.edges` (D-26).
677
+ *
678
+ * @public
679
+ */
680
+ export declare const GraphEnvelope: Schema.Struct<{
681
+ readonly schema: Schema.Literal<1>;
682
+ readonly okfit_version: Schema.String;
683
+ readonly producer: Schema.String;
684
+ readonly okf_version: Schema.String;
685
+ readonly root: Schema.String;
686
+ readonly profile: Schema.NullOr<Schema.String>;
687
+ readonly summary: Schema.Struct<{
688
+ readonly nodes: Schema.Number;
689
+ readonly edges: Schema.Number;
690
+ }>;
691
+ readonly nodes: Schema.$Array<Schema.Struct<{
692
+ readonly id: Schema.String;
693
+ readonly kind: Schema.Literals<readonly ["concept", "file", "missing"]>;
694
+ }>>;
695
+ readonly edges: Schema.$Array<Schema.Struct<{
696
+ readonly from: Schema.String;
697
+ readonly to: Schema.String;
698
+ readonly source: Schema.Literals<readonly ["body", "frontmatter"]>;
699
+ readonly field: Schema.optionalKey<Schema.String>;
700
+ readonly raw: Schema.String;
701
+ }>>;
702
+ }>;
703
+ /** @public */
704
+ export type GraphEnvelope = typeof GraphEnvelope.Type;
705
+ /**
706
+ * Build the envelope from a loaded `LinkGraph`'s own `nodes`/`edges`
707
+ * arrays.
708
+ *
709
+ * @public
710
+ */
711
+ export declare const graphEnvelope: (input: {
712
+ readonly okfitVersion: string;
713
+ readonly producer: string;
714
+ readonly okfVersion: string;
715
+ readonly root: string;
716
+ readonly profile: string | null;
717
+ readonly nodes: ReadonlyArray<GraphNode>;
718
+ readonly edges: ReadonlyArray<GraphLink>;
719
+ }) => GraphEnvelope;
720
+ //#endregion
629
721
  //#region src/render/json.d.ts
630
722
  /** One entry of the `diagnostics` array (K-21). @public */
631
723
  export declare const JsonDiagnostic: Schema.Struct<{
@@ -652,13 +744,18 @@ export type JsonSummary = typeof JsonSummary.Type;
652
744
  /**
653
745
  * K-21's success envelope, snake_case. `exit_code` is `0 | 1 | 2` only: an
654
746
  * infrastructure failure never produces this envelope, it produces the
655
- * `JsonErrorEnvelope` (K-22).
747
+ * `JsonErrorEnvelope` (K-22). `okfit_version` names the version of the
748
+ * package that produced the report (J-2), so the CLI and the MCP server
749
+ * legitimately report different numbers over one bundle; `producer` names
750
+ * that package (`okfit` or `@okfit/mcp`) so the difference reads as two
751
+ * producers, not as drift (okfit #75).
656
752
  *
657
753
  * @public
658
754
  */
659
755
  export declare const JsonEnvelope: Schema.Struct<{
660
756
  readonly schema: Schema.Literal<1>;
661
757
  readonly okfit_version: Schema.String;
758
+ readonly producer: Schema.String;
662
759
  readonly okf_version: Schema.String;
663
760
  readonly root: Schema.String;
664
761
  readonly profile: Schema.NullOr<Schema.String>;
@@ -707,6 +804,7 @@ export type JsonErrorEnvelope = typeof JsonErrorEnvelope.Type;
707
804
  */
708
805
  export declare const json: (input: {
709
806
  readonly okfitVersion: string;
807
+ readonly producer: string;
710
808
  readonly okfVersion: string;
711
809
  readonly root: string;
712
810
  readonly profile: string | null;
@@ -717,6 +815,69 @@ export declare const json: (input: {
717
815
  /** K-22. `tag` is the error's `_tag` when it has one, else its constructor name. @public */
718
816
  export declare const jsonError: (error: unknown, okfitVersion: string) => JsonErrorEnvelope;
719
817
  //#endregion
818
+ //#region src/render/stale.d.ts
819
+ /** One entry of the `items` array. @public */
820
+ export declare const StaleItem: Schema.Struct<{
821
+ readonly id: Schema.String;
822
+ readonly stale_after: Schema.String;
823
+ readonly days_past: Schema.Number;
824
+ }>;
825
+ /** @public */
826
+ export type StaleItem = typeof StaleItem.Type;
827
+ /** @public */
828
+ export declare const StaleSummary: Schema.Struct<{
829
+ readonly concepts: Schema.Number;
830
+ readonly stale: Schema.Number;
831
+ }>;
832
+ /** @public */
833
+ export type StaleSummary = typeof StaleSummary.Type;
834
+ /**
835
+ * `okfit stale --format json`'s envelope, schema 1, snake_case — the same
836
+ * convention as `render/json.ts`'s `JsonEnvelope`. `as_of` and each item's
837
+ * `stale_after` are `DateTime.formatIso` strings (mirrors
838
+ * `packages/mcp/src/tools/staleReport.ts`). `stale` is always `0` (the
839
+ * report never fails a run).
840
+ *
841
+ * @public
842
+ */
843
+ export declare const StaleEnvelope: Schema.Struct<{
844
+ readonly schema: Schema.Literal<1>;
845
+ readonly okfit_version: Schema.String;
846
+ readonly producer: Schema.String;
847
+ readonly okf_version: Schema.String;
848
+ readonly root: Schema.String;
849
+ readonly profile: Schema.NullOr<Schema.String>;
850
+ readonly as_of: Schema.String;
851
+ readonly summary: Schema.Struct<{
852
+ readonly concepts: Schema.Number;
853
+ readonly stale: Schema.Number;
854
+ }>;
855
+ readonly items: Schema.$Array<Schema.Struct<{
856
+ readonly id: Schema.String;
857
+ readonly stale_after: Schema.String;
858
+ readonly days_past: Schema.Number;
859
+ }>>;
860
+ }>;
861
+ /** @public */
862
+ export type StaleEnvelope = typeof StaleEnvelope.Type;
863
+ /**
864
+ * Build the envelope. `items` is `Derive.staleReport`'s output verbatim
865
+ * (already sorted by id); this just reshapes it to snake_case and formats
866
+ * the two `DateTime.Utc` fields.
867
+ *
868
+ * @public
869
+ */
870
+ export declare const staleEnvelope: (input: {
871
+ readonly okfitVersion: string;
872
+ readonly producer: string;
873
+ readonly okfVersion: string;
874
+ readonly root: string;
875
+ readonly profile: string | null;
876
+ readonly now: DateTime.Utc;
877
+ readonly concepts: number;
878
+ readonly items: ReadonlyArray<StaleConcept>;
879
+ }) => StaleEnvelope;
880
+ //#endregion
720
881
  //#region src/sync/run.d.ts
721
882
  /** The three families `okfit sync` regenerates (contract §5). @public */
722
883
  type SyncMode = "generated" | "index" | "log";
@@ -886,6 +1047,30 @@ export declare const verifyEnvelope: (input: {
886
1047
  readonly dryRun: boolean;
887
1048
  }) => VerifyEnvelope;
888
1049
  //#endregion
1050
+ //#region src/stale/run.d.ts
1051
+ /** @public */
1052
+ interface StaleRunOptions {
1053
+ /** Absolute bundle root; `Bundle.load` never reads cwd (D-8). */
1054
+ readonly root: string;
1055
+ /** `OKFIT_NOW` or `DateTime.now`, from the caller's own `Now` service (K-47). */
1056
+ readonly now: DateTime.Utc;
1057
+ }
1058
+ /** @public */
1059
+ interface StaleRunResult {
1060
+ readonly bundle: LoadedBundle;
1061
+ /** `Derive.staleReport`'s output verbatim: every stale concept, sorted by id. */
1062
+ readonly items: ReadonlyArray<StaleConcept>;
1063
+ }
1064
+ /**
1065
+ * Load the bundle and derive the stale report against `now` (D-10, D-36:
1066
+ * `Derive.staleReport` is pure and takes `now` as an argument, never a
1067
+ * clock). This is a report, not a check — `okfit stale` always exits `0`;
1068
+ * the `stale` lint rule is where staleness fails a run.
1069
+ *
1070
+ * @public
1071
+ */
1072
+ export declare const runStale: (options: StaleRunOptions) => Effect.Effect<StaleRunResult, BundleLoadError, FileSystem.FileSystem | Path.Path>;
1073
+ //#endregion
889
1074
  //#region src/validate/run.d.ts
890
1075
  declare const Now_base: Context.ServiceClass<Now, "@okfit/engine/Now", DateTime.Utc>;
891
1076
  /**
@@ -1016,5 +1201,5 @@ export declare const runVerify: (options: VerifyOptions) => Effect.Effect<{
1016
1201
  fragment: string;
1017
1202
  }, import("effect/PlatformError").PlatformError | VerifyConceptNotFoundError | VerifyUnsupportedFrontmatterError | import("@effected/yaml").YamlParseError | import("@okfit/core").BundleLoadError | import("@okfit/profiles").GeneratedByError, FileSystem.FileSystem | import("@effected/git").Git | Path.Path>;
1018
1203
  //#endregion
1019
- export type { ContextResult, ContextRunOptions, DiagnosticSource, DiscoveredConfig, RenderedDiagnostic, ResolveProjectConfigInput, ResolvedProjectConfig, RunOptions, RunResult, ScaffoldFile, ScaffoldOptions, SyncMode, SyncModeResult, SyncOptions, SyncResult, Tally, VerifyOptions, VerifyResult };
1204
+ export type { ContextResult, ContextRunOptions, DiagnosticSource, DiscoveredConfig, GraphRunOptions, GraphRunResult, RenderedDiagnostic, ResolveProjectConfigInput, ResolvedProjectConfig, RunOptions, RunResult, ScaffoldFile, ScaffoldOptions, StaleRunOptions, StaleRunResult, SyncMode, SyncModeResult, SyncOptions, SyncResult, Tally, VerifyOptions, VerifyResult };
1020
1205
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -3,16 +3,20 @@ import { ConfigMalformedError, ConfigPathNotFoundError, InitOverwriteError, Veri
3
3
  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
+ import { runGraph } from "./graph/run.js";
6
7
  import { CONFIG_RELATIVE_PATH, SCHEMA_DIRECTIVE, configValue, files, targetPaths } from "./init/scaffold.js";
7
8
  import { OkfitPlatform } from "./platform.js";
8
9
  import { ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, contextEnvelope } from "./render/context.js";
9
10
  import { forDiagnostics, tally } from "./render/exit.js";
11
+ import { GraphEdgeEnvelope, GraphEnvelope, GraphNodeEnvelope, GraphSummary, graphEnvelope } from "./render/graph.js";
10
12
  import { collect, sort } from "./render/sort.js";
11
13
  import { JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, json, jsonError } from "./render/json.js";
14
+ import { StaleEnvelope, StaleItem, StaleSummary, staleEnvelope } from "./render/stale.js";
12
15
  import { SkipReason, runSync } from "./sync/run.js";
13
16
  import { SyncEnvelope, SyncModeEnvelope, syncEnvelope } from "./render/sync.js";
14
17
  import { VerifyEnvelope, verifyEnvelope } from "./render/verify.js";
18
+ import { runStale } from "./stale/run.js";
15
19
  import { Now, run } from "./validate/run.js";
16
20
  import { runVerify } from "./verify/run.js";
17
21
 
18
- export { CONFIG_RELATIVE_PATH, ConfigMalformedError, ConfigPathNotFoundError, ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, DEFAULT_PROFILE_NAME, InitOverwriteError, JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, Now, OkfitPlatform, SCHEMA_DIRECTIVE, SkipReason, SyncEnvelope, SyncModeEnvelope, VerifyConceptNotFoundError, VerifyEnvelope, VerifyUnsupportedFrontmatterError, buildConfigLayer, collect, configValue, contextEnvelope, files, forDiagnostics, json, jsonError, provideConfig, resolveBundleRoot, resolveProjectConfig, resolveProjectRoot, run, runContext, runSync, runVerify, sort, 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, provideConfig, resolveBundleRoot, resolveProjectConfig, resolveProjectRoot, run, runContext, runGraph, runStale, runSync, runVerify, sort, staleEnvelope, syncEnvelope, tally, targetPaths, verifyEnvelope };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okfit/engine",
3
- "version": "0.3.0",
3
+ "version": "0.4.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": [
@@ -46,8 +46,8 @@
46
46
  "@effected/walker": "^0.9.0",
47
47
  "@effected/xdg": "^0.5.1",
48
48
  "@effected/yaml": "^0.15.1",
49
- "@okfit/core": "0.4.0",
50
- "@okfit/profiles": "0.4.0",
49
+ "@okfit/core": "0.4.1",
50
+ "@okfit/profiles": "0.6.0",
51
51
  "effect": "4.0.0-rc.115"
52
52
  },
53
53
  "engines": {
@@ -0,0 +1,77 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/render/graph.ts
4
+ /** One entry of the `nodes` array. @public */
5
+ const GraphNodeEnvelope = Schema.Struct({
6
+ id: Schema.String,
7
+ kind: Schema.Literals([
8
+ "concept",
9
+ "file",
10
+ "missing"
11
+ ])
12
+ });
13
+ /** One entry of the `edges` array; `field` is omitted, never `null` (exactOptionalPropertyTypes). @public */
14
+ const GraphEdgeEnvelope = Schema.Struct({
15
+ from: Schema.String,
16
+ to: Schema.String,
17
+ source: Schema.Literals(["body", "frontmatter"]),
18
+ field: Schema.optionalKey(Schema.String),
19
+ raw: Schema.String
20
+ });
21
+ /** @public */
22
+ const GraphSummary = Schema.Struct({
23
+ nodes: Schema.Number,
24
+ edges: Schema.Number
25
+ });
26
+ /**
27
+ * `okfit graph --format json`'s envelope, schema 1, snake_case — the same
28
+ * convention as `render/json.ts`'s `JsonEnvelope`. Built from
29
+ * `LinkGraph.nodes`/`LinkGraph.edges` (D-26).
30
+ *
31
+ * @public
32
+ */
33
+ const GraphEnvelope = Schema.Struct({
34
+ schema: Schema.Literal(1),
35
+ okfit_version: Schema.String,
36
+ producer: Schema.String,
37
+ okf_version: Schema.String,
38
+ root: Schema.String,
39
+ profile: Schema.NullOr(Schema.String),
40
+ summary: GraphSummary,
41
+ nodes: Schema.Array(GraphNodeEnvelope),
42
+ edges: Schema.Array(GraphEdgeEnvelope)
43
+ });
44
+ const toNode = (node) => ({
45
+ id: node.id,
46
+ kind: node.kind
47
+ });
48
+ const toEdge = (edge) => ({
49
+ from: edge.from,
50
+ to: edge.to,
51
+ source: edge.data.source,
52
+ ...edge.data.field === void 0 ? {} : { field: edge.data.field },
53
+ raw: edge.data.raw
54
+ });
55
+ /**
56
+ * Build the envelope from a loaded `LinkGraph`'s own `nodes`/`edges`
57
+ * arrays.
58
+ *
59
+ * @public
60
+ */
61
+ const graphEnvelope = (input) => ({
62
+ schema: 1,
63
+ okfit_version: input.okfitVersion,
64
+ producer: input.producer,
65
+ okf_version: input.okfVersion,
66
+ root: input.root,
67
+ profile: input.profile,
68
+ summary: {
69
+ nodes: input.nodes.length,
70
+ edges: input.edges.length
71
+ },
72
+ nodes: input.nodes.map(toNode),
73
+ edges: input.edges.map(toEdge)
74
+ });
75
+
76
+ //#endregion
77
+ export { GraphEdgeEnvelope, GraphEnvelope, GraphNodeEnvelope, GraphSummary, graphEnvelope };
package/render/json.js CHANGED
@@ -29,13 +29,18 @@ const JsonSummary = Schema.Struct({
29
29
  /**
30
30
  * K-21's success envelope, snake_case. `exit_code` is `0 | 1 | 2` only: an
31
31
  * infrastructure failure never produces this envelope, it produces the
32
- * `JsonErrorEnvelope` (K-22).
32
+ * `JsonErrorEnvelope` (K-22). `okfit_version` names the version of the
33
+ * package that produced the report (J-2), so the CLI and the MCP server
34
+ * legitimately report different numbers over one bundle; `producer` names
35
+ * that package (`okfit` or `@okfit/mcp`) so the difference reads as two
36
+ * producers, not as drift (okfit #75).
33
37
  *
34
38
  * @public
35
39
  */
36
40
  const JsonEnvelope = Schema.Struct({
37
41
  schema: Schema.Literal(1),
38
42
  okfit_version: Schema.String,
43
+ producer: Schema.String,
39
44
  okf_version: Schema.String,
40
45
  root: Schema.String,
41
46
  profile: Schema.NullOr(Schema.String),
@@ -81,6 +86,7 @@ const json = (input) => {
81
86
  return {
82
87
  schema: 1,
83
88
  okfit_version: input.okfitVersion,
89
+ producer: input.producer,
84
90
  okf_version: input.okfVersion,
85
91
  root: input.root,
86
92
  profile: input.profile,
@@ -0,0 +1,62 @@
1
+ import { DateTime, Schema } from "effect";
2
+
3
+ //#region src/render/stale.ts
4
+ /** One entry of the `items` array. @public */
5
+ const StaleItem = Schema.Struct({
6
+ id: Schema.String,
7
+ stale_after: Schema.String,
8
+ days_past: Schema.Number
9
+ });
10
+ /** @public */
11
+ const StaleSummary = Schema.Struct({
12
+ concepts: Schema.Number,
13
+ stale: Schema.Number
14
+ });
15
+ /**
16
+ * `okfit stale --format json`'s envelope, schema 1, snake_case — the same
17
+ * convention as `render/json.ts`'s `JsonEnvelope`. `as_of` and each item's
18
+ * `stale_after` are `DateTime.formatIso` strings (mirrors
19
+ * `packages/mcp/src/tools/staleReport.ts`). `stale` is always `0` (the
20
+ * report never fails a run).
21
+ *
22
+ * @public
23
+ */
24
+ const StaleEnvelope = Schema.Struct({
25
+ schema: Schema.Literal(1),
26
+ okfit_version: Schema.String,
27
+ producer: Schema.String,
28
+ okf_version: Schema.String,
29
+ root: Schema.String,
30
+ profile: Schema.NullOr(Schema.String),
31
+ as_of: Schema.String,
32
+ summary: StaleSummary,
33
+ items: Schema.Array(StaleItem)
34
+ });
35
+ /**
36
+ * Build the envelope. `items` is `Derive.staleReport`'s output verbatim
37
+ * (already sorted by id); this just reshapes it to snake_case and formats
38
+ * the two `DateTime.Utc` fields.
39
+ *
40
+ * @public
41
+ */
42
+ const staleEnvelope = (input) => ({
43
+ schema: 1,
44
+ okfit_version: input.okfitVersion,
45
+ producer: input.producer,
46
+ okf_version: input.okfVersion,
47
+ root: input.root,
48
+ profile: input.profile,
49
+ as_of: DateTime.formatIso(input.now),
50
+ summary: {
51
+ concepts: input.concepts,
52
+ stale: input.items.length
53
+ },
54
+ items: input.items.map((item) => ({
55
+ id: item.id,
56
+ stale_after: DateTime.formatIso(item.staleAfter),
57
+ days_past: item.daysPast
58
+ }))
59
+ });
60
+
61
+ //#endregion
62
+ export { StaleEnvelope, StaleItem, StaleSummary, staleEnvelope };
package/stale/run.js ADDED
@@ -0,0 +1,22 @@
1
+ import { Bundle, Derive } from "@okfit/core";
2
+ import { Effect } from "effect";
3
+
4
+ //#region src/stale/run.ts
5
+ /**
6
+ * Load the bundle and derive the stale report against `now` (D-10, D-36:
7
+ * `Derive.staleReport` is pure and takes `now` as an argument, never a
8
+ * clock). This is a report, not a check — `okfit stale` always exits `0`;
9
+ * the `stale` lint rule is where staleness fails a run.
10
+ *
11
+ * @public
12
+ */
13
+ const runStale = (options) => Effect.gen(function* () {
14
+ const bundle = yield* Bundle.load({ root: options.root });
15
+ return {
16
+ bundle,
17
+ items: Derive.staleReport(bundle, options.now)
18
+ };
19
+ });
20
+
21
+ //#endregion
22
+ export { runStale };