@okfit/engine 0.9.3 → 0.10.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/errors.js +55 -5
- package/index.d.ts +324 -18
- package/index.js +5 -2
- package/package.json +4 -4
- package/query/ConceptQuery.js +102 -0
- package/render/query.js +104 -0
- package/render/verify.js +12 -1
- package/verify/run.js +44 -48
- package/verify/select.js +59 -0
- package/verify/splice.js +25 -1
- package/version.js +1 -1
package/errors.js
CHANGED
|
@@ -60,7 +60,8 @@ var VerifyConceptNotFoundError = class extends Schema.TaggedError()("VerifyConce
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
/**
|
|
63
|
-
* V-14: fail closed on a `verified`
|
|
63
|
+
* V-14: fail closed on a `verified` (or, under `--stable`/`--draft`, `status`)
|
|
64
|
+
* shape the classifier does not name; `key` says which, omitted meaning `verified`.
|
|
64
65
|
* `shape` is one of `"alias"`, `"merge-key"`, `"scalar"`, `"empty"` (and,
|
|
65
66
|
* defensively, `"no-frontmatter"` or `"not-a-mapping"`, neither reachable
|
|
66
67
|
* for a concept that reached `bundle.concepts`). The file is never opened
|
|
@@ -70,11 +71,12 @@ var VerifyConceptNotFoundError = class extends Schema.TaggedError()("VerifyConce
|
|
|
70
71
|
*/
|
|
71
72
|
var VerifyUnsupportedFrontmatterError = class extends Schema.TaggedError()("VerifyUnsupportedFrontmatterError", {
|
|
72
73
|
id: Schema.String,
|
|
73
|
-
shape: Schema.String
|
|
74
|
+
shape: Schema.String,
|
|
75
|
+
key: Schema.optionalKey(Schema.Literals(["verified", "status"]))
|
|
74
76
|
}) {
|
|
75
77
|
[Runtime.errorExitCode] = 3;
|
|
76
78
|
get message() {
|
|
77
|
-
return `"${this.id}"'s verified value is a shape okfit verify cannot edit (${this.shape}); edit it by hand`;
|
|
79
|
+
return `"${this.id}"'s ${this.key ?? "verified"} value is a shape okfit verify cannot edit (${this.shape}); edit it by hand`;
|
|
78
80
|
}
|
|
79
81
|
};
|
|
80
82
|
/** The error's `message` when it has one as a string, else `String(error)`. */
|
|
@@ -127,7 +129,9 @@ var VerifySelectionError = class extends Schema.TaggedError()("VerifySelectionEr
|
|
|
127
129
|
reason: Schema.Literals([
|
|
128
130
|
"no-selection",
|
|
129
131
|
"id-and-batch",
|
|
130
|
-
"unknown-type"
|
|
132
|
+
"unknown-type",
|
|
133
|
+
"status-conflict",
|
|
134
|
+
"status-and-batch"
|
|
131
135
|
]),
|
|
132
136
|
detail: Schema.optionalKey(Schema.String)
|
|
133
137
|
}) {
|
|
@@ -135,6 +139,8 @@ var VerifySelectionError = class extends Schema.TaggedError()("VerifySelectionEr
|
|
|
135
139
|
get message() {
|
|
136
140
|
if (this.reason === "no-selection") return "verify needs a concept id, --all, or --type <Type>";
|
|
137
141
|
if (this.reason === "id-and-batch") return "verify takes either a concept id or --all/--type, not both";
|
|
142
|
+
if (this.reason === "status-conflict") return "verify takes --stable or --draft, not both";
|
|
143
|
+
if (this.reason === "status-and-batch") return "--stable and --draft need a concept id; batch mode never changes status";
|
|
138
144
|
return `type "${this.detail ?? ""}" is not declared in the config`;
|
|
139
145
|
}
|
|
140
146
|
};
|
|
@@ -170,6 +176,50 @@ var DocumentPathError = class extends Schema.TaggedError()("DocumentPathError",
|
|
|
170
176
|
return `document path "${this.path}" ${DOCUMENT_PATH_REASON_TEXT[this.reason]}`;
|
|
171
177
|
}
|
|
172
178
|
};
|
|
179
|
+
/**
|
|
180
|
+
* `okfit query list` named a type or tag the config does not declare. `valid`
|
|
181
|
+
* is the sorted declared names. Exit 64: a usage error.
|
|
182
|
+
*
|
|
183
|
+
* @public
|
|
184
|
+
*/
|
|
185
|
+
var QueryUnknownVocabularyError = class extends Schema.TaggedError()("QueryUnknownVocabularyError", {
|
|
186
|
+
kind: Schema.Literals(["type", "tag"]),
|
|
187
|
+
requested: Schema.String,
|
|
188
|
+
valid: Schema.Array(Schema.String)
|
|
189
|
+
}) {
|
|
190
|
+
[Runtime.errorExitCode] = 64;
|
|
191
|
+
get message() {
|
|
192
|
+
return `${this.kind} "${this.requested}" is not declared in the config; declared: ${this.valid.join(", ")}`;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* A query's concept id named no concept: empty after normalisation
|
|
197
|
+
* (`empty-id`) or absent from the bundle (`not-a-concept`). Exit 3.
|
|
198
|
+
*
|
|
199
|
+
* @public
|
|
200
|
+
*/
|
|
201
|
+
var QueryConceptNotFoundError = class extends Schema.TaggedError()("QueryConceptNotFoundError", {
|
|
202
|
+
id: Schema.String,
|
|
203
|
+
reason: Schema.Literals(["empty-id", "not-a-concept"])
|
|
204
|
+
}) {
|
|
205
|
+
[Runtime.errorExitCode] = 3;
|
|
206
|
+
get message() {
|
|
207
|
+
if (this.reason === "empty-id") return "a concept id must not be empty";
|
|
208
|
+
return `no concept "${this.id}" in this bundle`;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* `okfit query list` was given contradictory selection flags. Exit 64: a
|
|
213
|
+
* usage error.
|
|
214
|
+
*
|
|
215
|
+
* @public
|
|
216
|
+
*/
|
|
217
|
+
var QuerySelectionError = class extends Schema.TaggedError()("QuerySelectionError", { reason: Schema.Literals(["verified-conflict"]) }) {
|
|
218
|
+
[Runtime.errorExitCode] = 64;
|
|
219
|
+
get message() {
|
|
220
|
+
return "query list takes --verified or --unverified, not both";
|
|
221
|
+
}
|
|
222
|
+
};
|
|
173
223
|
|
|
174
224
|
//#endregion
|
|
175
|
-
export { ConfigMalformedError, ConfigPathNotFoundError, DocumentPathError, InitOverwriteError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError };
|
|
225
|
+
export { ConfigMalformedError, ConfigPathNotFoundError, DocumentPathError, InitOverwriteError, QueryConceptNotFoundError, QuerySelectionError, QueryUnknownVocabularyError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError };
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Actor, BundleLoadError, ConceptId, Diagnostic, DiagnosticRange, DiagnosticSeverity, GraphLink, GraphNode, LinkGraph, LoadedBundle, LoadedConcept, OkfitConfig, OkfitConfigFile, StaleConcept, Status, ValidationReport } from "@okfit/core";
|
|
1
|
+
import { Actor, BundleLoadError, ConceptId, Diagnostic, DiagnosticRange, DiagnosticSeverity, GraphLink, GraphNode, GraphNodeKind, LinkGraph, LoadedBundle, LoadedConcept, OkfitConfig, OkfitConfigFile, StaleConcept, Status, ValidationReport } from "@okfit/core";
|
|
2
2
|
import { Context, Crypto, DateTime, Effect, FileSystem, Layer, Option, Path, PlatformError, Result, Runtime, Schema } from "effect";
|
|
3
3
|
import { AppDirs, Xdg } from "@effected/xdg";
|
|
4
4
|
import { ConfigReadError } from "@effected/config-file";
|
|
@@ -126,9 +126,11 @@ export declare class VerifyConceptNotFoundError extends VerifyConceptNotFoundErr
|
|
|
126
126
|
declare const VerifyUnsupportedFrontmatterError_base: Schema.Class<VerifyUnsupportedFrontmatterError, Schema.TaggedStruct<"VerifyUnsupportedFrontmatterError", {
|
|
127
127
|
readonly id: Schema.String;
|
|
128
128
|
readonly shape: Schema.String;
|
|
129
|
+
readonly key: Schema.optionalKey<Schema.Literals<readonly ["verified", "status"]>>;
|
|
129
130
|
}>, import("effect/Cause").YieldableError>;
|
|
130
131
|
/**
|
|
131
|
-
* V-14: fail closed on a `verified`
|
|
132
|
+
* V-14: fail closed on a `verified` (or, under `--stable`/`--draft`, `status`)
|
|
133
|
+
* shape the classifier does not name; `key` says which, omitted meaning `verified`.
|
|
132
134
|
* `shape` is one of `"alias"`, `"merge-key"`, `"scalar"`, `"empty"` (and,
|
|
133
135
|
* defensively, `"no-frontmatter"` or `"not-a-mapping"`, neither reachable
|
|
134
136
|
* for a concept that reached `bundle.concepts`). The file is never opened
|
|
@@ -172,7 +174,7 @@ export declare class SyncStagedLogError extends SyncStagedLogError_base {
|
|
|
172
174
|
get message(): string;
|
|
173
175
|
}
|
|
174
176
|
declare const VerifySelectionError_base: Schema.Class<VerifySelectionError, Schema.TaggedStruct<"VerifySelectionError", {
|
|
175
|
-
readonly reason: Schema.Literals<readonly ["no-selection", "id-and-batch", "unknown-type"]>;
|
|
177
|
+
readonly reason: Schema.Literals<readonly ["no-selection", "id-and-batch", "unknown-type", "status-conflict", "status-and-batch"]>;
|
|
176
178
|
readonly detail: Schema.optionalKey<Schema.String>;
|
|
177
179
|
}>, import("effect/Cause").YieldableError>;
|
|
178
180
|
/**
|
|
@@ -202,6 +204,48 @@ export declare class DocumentPathError extends DocumentPathError_base {
|
|
|
202
204
|
readonly [Runtime.errorExitCode] = 64;
|
|
203
205
|
get message(): string;
|
|
204
206
|
}
|
|
207
|
+
declare const QueryUnknownVocabularyError_base: Schema.Class<QueryUnknownVocabularyError, Schema.TaggedStruct<"QueryUnknownVocabularyError", {
|
|
208
|
+
readonly kind: Schema.Literals<readonly ["type", "tag"]>;
|
|
209
|
+
readonly requested: Schema.String;
|
|
210
|
+
readonly valid: Schema.$Array<Schema.String>;
|
|
211
|
+
}>, import("effect/Cause").YieldableError>;
|
|
212
|
+
/**
|
|
213
|
+
* `okfit query list` named a type or tag the config does not declare. `valid`
|
|
214
|
+
* is the sorted declared names. Exit 64: a usage error.
|
|
215
|
+
*
|
|
216
|
+
* @public
|
|
217
|
+
*/
|
|
218
|
+
export declare class QueryUnknownVocabularyError extends QueryUnknownVocabularyError_base {
|
|
219
|
+
readonly [Runtime.errorExitCode] = 64;
|
|
220
|
+
get message(): string;
|
|
221
|
+
}
|
|
222
|
+
declare const QueryConceptNotFoundError_base: Schema.Class<QueryConceptNotFoundError, Schema.TaggedStruct<"QueryConceptNotFoundError", {
|
|
223
|
+
readonly id: Schema.String;
|
|
224
|
+
readonly reason: Schema.Literals<readonly ["empty-id", "not-a-concept"]>;
|
|
225
|
+
}>, import("effect/Cause").YieldableError>;
|
|
226
|
+
/**
|
|
227
|
+
* A query's concept id named no concept: empty after normalisation
|
|
228
|
+
* (`empty-id`) or absent from the bundle (`not-a-concept`). Exit 3.
|
|
229
|
+
*
|
|
230
|
+
* @public
|
|
231
|
+
*/
|
|
232
|
+
export declare class QueryConceptNotFoundError extends QueryConceptNotFoundError_base {
|
|
233
|
+
readonly [Runtime.errorExitCode] = 3;
|
|
234
|
+
get message(): string;
|
|
235
|
+
}
|
|
236
|
+
declare const QuerySelectionError_base: Schema.Class<QuerySelectionError, Schema.TaggedStruct<"QuerySelectionError", {
|
|
237
|
+
readonly reason: Schema.Literals<readonly ["verified-conflict"]>;
|
|
238
|
+
}>, import("effect/Cause").YieldableError>;
|
|
239
|
+
/**
|
|
240
|
+
* `okfit query list` was given contradictory selection flags. Exit 64: a
|
|
241
|
+
* usage error.
|
|
242
|
+
*
|
|
243
|
+
* @public
|
|
244
|
+
*/
|
|
245
|
+
export declare class QuerySelectionError extends QuerySelectionError_base {
|
|
246
|
+
readonly [Runtime.errorExitCode] = 64;
|
|
247
|
+
get message(): string;
|
|
248
|
+
}
|
|
205
249
|
//#endregion
|
|
206
250
|
//#region src/config/layer.d.ts
|
|
207
251
|
/**
|
|
@@ -652,6 +696,58 @@ export declare const layerOverlayFileSystem: Layer.Layer<FileSystem.FileSystem,
|
|
|
652
696
|
*/
|
|
653
697
|
export declare const OkfitPlatform: Layer.Layer<AppDirs | Xdg | NodeServices.NodeServices, import("@effected/xdg").XdgEnvError, never>;
|
|
654
698
|
//#endregion
|
|
699
|
+
//#region src/query/ConceptQuery.d.ts
|
|
700
|
+
/**
|
|
701
|
+
* Filter for {@link ConceptQuery.list}. Every field is optional; an absent
|
|
702
|
+
* field does not constrain.
|
|
703
|
+
*
|
|
704
|
+
* @public
|
|
705
|
+
*/
|
|
706
|
+
interface ConceptFilter {
|
|
707
|
+
/** OR across types. */
|
|
708
|
+
readonly types?: ReadonlyArray<string>;
|
|
709
|
+
/** AND: every tag must be present. */
|
|
710
|
+
readonly tags?: ReadonlyArray<string>;
|
|
711
|
+
/** OR, compared against `Derive.status` (an absent status reads as stable). */
|
|
712
|
+
readonly statuses?: ReadonlyArray<Status>;
|
|
713
|
+
/** `true`: at least one verified entry; `false`: none. */
|
|
714
|
+
readonly verified?: boolean;
|
|
715
|
+
}
|
|
716
|
+
/** One outgoing link of a concept. @public */
|
|
717
|
+
interface ConceptLink {
|
|
718
|
+
readonly to: string;
|
|
719
|
+
readonly kind: GraphNodeKind;
|
|
720
|
+
readonly source: "body" | "frontmatter";
|
|
721
|
+
readonly field?: string;
|
|
722
|
+
}
|
|
723
|
+
/** One graph neighbour; `concept` is `null` for a non-concept node (J-4). @public */
|
|
724
|
+
interface ConceptNeighbor {
|
|
725
|
+
readonly id: string;
|
|
726
|
+
readonly kind: GraphNodeKind;
|
|
727
|
+
readonly concept: LoadedConcept | null;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Read-only queries over a loaded bundle, shared by `okfit query` and the MCP
|
|
731
|
+
* tools.
|
|
732
|
+
*
|
|
733
|
+
* @public
|
|
734
|
+
*/
|
|
735
|
+
export declare const ConceptQuery: {
|
|
736
|
+
/** Validates types then tags against config (first unknown fails), then filters and sorts by id. */
|
|
737
|
+
readonly list: (bundle: LoadedBundle, config: OkfitConfig, filter: ConceptFilter) => Effect.Effect<ReadonlyArray<LoadedConcept>, QueryUnknownVocabularyError>;
|
|
738
|
+
/** One concept by tolerant id, with its outgoing links. */
|
|
739
|
+
readonly get: (bundle: LoadedBundle, id: string) => Effect.Effect<{
|
|
740
|
+
readonly concept: LoadedConcept;
|
|
741
|
+
readonly links: ReadonlyArray<ConceptLink>;
|
|
742
|
+
}, QueryConceptNotFoundError>;
|
|
743
|
+
/** Outgoing and incoming graph neighbours of one concept. */
|
|
744
|
+
readonly neighbors: (bundle: LoadedBundle, id: string) => Effect.Effect<{
|
|
745
|
+
readonly id: string;
|
|
746
|
+
readonly outgoing: ReadonlyArray<ConceptNeighbor>;
|
|
747
|
+
readonly incoming: ReadonlyArray<ConceptNeighbor>;
|
|
748
|
+
}, QueryConceptNotFoundError>;
|
|
749
|
+
};
|
|
750
|
+
//#endregion
|
|
655
751
|
//#region src/render/context.d.ts
|
|
656
752
|
/** One enumerated value of a `fields[]` entry. @public */
|
|
657
753
|
export declare const ContextFieldValue: Schema.Struct<{
|
|
@@ -1088,6 +1184,159 @@ export declare const json: (input: {
|
|
|
1088
1184
|
/** K-22. `tag` is the error's `_tag` when it has one, else its constructor name. @public */
|
|
1089
1185
|
export declare const jsonError: (error: unknown, okfitVersion: string, distribution?: Distribution) => JsonErrorEnvelope;
|
|
1090
1186
|
//#endregion
|
|
1187
|
+
//#region src/render/query.d.ts
|
|
1188
|
+
/** The seven-field shape every list-like query result carries (N-16). @public */
|
|
1189
|
+
export declare const ConceptSummary: Schema.Struct<{
|
|
1190
|
+
readonly id: Schema.String;
|
|
1191
|
+
readonly type: Schema.String;
|
|
1192
|
+
readonly title: Schema.String;
|
|
1193
|
+
readonly description: Schema.NullOr<Schema.String>;
|
|
1194
|
+
readonly status: Schema.Literals<readonly ["draft", "stable", "deprecated"]>;
|
|
1195
|
+
readonly tags: Schema.$Array<Schema.String>;
|
|
1196
|
+
readonly path: Schema.String;
|
|
1197
|
+
}>;
|
|
1198
|
+
/** @public */
|
|
1199
|
+
export type ConceptSummary = typeof ConceptSummary.Type;
|
|
1200
|
+
/** Project one loaded concept into its summary. @public */
|
|
1201
|
+
export declare const toConceptSummary: (concept: LoadedConcept) => ConceptSummary;
|
|
1202
|
+
/** `okfit query list --format json`'s envelope, schema 1, snake_case. @public */
|
|
1203
|
+
export declare const QueryListEnvelope: Schema.Struct<{
|
|
1204
|
+
readonly schema: Schema.Literal<1>;
|
|
1205
|
+
readonly okfit_version: Schema.String;
|
|
1206
|
+
readonly engine_version: Schema.String;
|
|
1207
|
+
readonly distribution: Schema.NullOr<Schema.Struct<{
|
|
1208
|
+
readonly name: Schema.String;
|
|
1209
|
+
readonly version: Schema.String;
|
|
1210
|
+
}>>;
|
|
1211
|
+
readonly total: Schema.Int;
|
|
1212
|
+
readonly items: Schema.$Array<Schema.Struct<{
|
|
1213
|
+
readonly id: Schema.String;
|
|
1214
|
+
readonly type: Schema.String;
|
|
1215
|
+
readonly title: Schema.String;
|
|
1216
|
+
readonly description: Schema.NullOr<Schema.String>;
|
|
1217
|
+
readonly status: Schema.Literals<readonly ["draft", "stable", "deprecated"]>;
|
|
1218
|
+
readonly tags: Schema.$Array<Schema.String>;
|
|
1219
|
+
readonly path: Schema.String;
|
|
1220
|
+
}>>;
|
|
1221
|
+
}>;
|
|
1222
|
+
/** @public */
|
|
1223
|
+
export type QueryListEnvelope = typeof QueryListEnvelope.Type;
|
|
1224
|
+
/** One outgoing link of a concept, as `okfit query get` reports it. @public */
|
|
1225
|
+
export declare const QueryLink: Schema.Struct<{
|
|
1226
|
+
readonly to: Schema.String;
|
|
1227
|
+
readonly kind: Schema.Literals<readonly ["concept", "file", "missing"]>;
|
|
1228
|
+
readonly source: Schema.Literals<readonly ["body", "frontmatter"]>;
|
|
1229
|
+
readonly field: Schema.optionalKey<Schema.String>;
|
|
1230
|
+
}>;
|
|
1231
|
+
/** @public */
|
|
1232
|
+
export type QueryLink = typeof QueryLink.Type;
|
|
1233
|
+
/** `okfit query get --format json`'s envelope, schema 1, snake_case. @public */
|
|
1234
|
+
export declare const QueryGetEnvelope: Schema.Struct<{
|
|
1235
|
+
readonly schema: Schema.Literal<1>;
|
|
1236
|
+
readonly okfit_version: Schema.String;
|
|
1237
|
+
readonly engine_version: Schema.String;
|
|
1238
|
+
readonly distribution: Schema.NullOr<Schema.Struct<{
|
|
1239
|
+
readonly name: Schema.String;
|
|
1240
|
+
readonly version: Schema.String;
|
|
1241
|
+
}>>;
|
|
1242
|
+
readonly concept: Schema.Struct<{
|
|
1243
|
+
readonly id: Schema.String;
|
|
1244
|
+
readonly type: Schema.String;
|
|
1245
|
+
readonly title: Schema.String;
|
|
1246
|
+
readonly description: Schema.NullOr<Schema.String>;
|
|
1247
|
+
readonly status: Schema.Literals<readonly ["draft", "stable", "deprecated"]>;
|
|
1248
|
+
readonly tags: Schema.$Array<Schema.String>;
|
|
1249
|
+
readonly path: Schema.String;
|
|
1250
|
+
readonly frontmatter: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
1251
|
+
readonly links: Schema.$Array<Schema.Struct<{
|
|
1252
|
+
readonly to: Schema.String;
|
|
1253
|
+
readonly kind: Schema.Literals<readonly ["concept", "file", "missing"]>;
|
|
1254
|
+
readonly source: Schema.Literals<readonly ["body", "frontmatter"]>;
|
|
1255
|
+
readonly field: Schema.optionalKey<Schema.String>;
|
|
1256
|
+
}>>;
|
|
1257
|
+
}>;
|
|
1258
|
+
}>;
|
|
1259
|
+
/** @public */
|
|
1260
|
+
export type QueryGetEnvelope = typeof QueryGetEnvelope.Type;
|
|
1261
|
+
/** One neighbour of a concept; `summary` is `null` for a non-concept node. @public */
|
|
1262
|
+
export declare const QueryNeighbor: Schema.Struct<{
|
|
1263
|
+
readonly id: Schema.String;
|
|
1264
|
+
readonly kind: Schema.Literals<readonly ["concept", "file", "missing"]>;
|
|
1265
|
+
readonly summary: Schema.NullOr<Schema.Struct<{
|
|
1266
|
+
readonly id: Schema.String;
|
|
1267
|
+
readonly type: Schema.String;
|
|
1268
|
+
readonly title: Schema.String;
|
|
1269
|
+
readonly description: Schema.NullOr<Schema.String>;
|
|
1270
|
+
readonly status: Schema.Literals<readonly ["draft", "stable", "deprecated"]>;
|
|
1271
|
+
readonly tags: Schema.$Array<Schema.String>;
|
|
1272
|
+
readonly path: Schema.String;
|
|
1273
|
+
}>>;
|
|
1274
|
+
}>;
|
|
1275
|
+
/** @public */
|
|
1276
|
+
export type QueryNeighbor = typeof QueryNeighbor.Type;
|
|
1277
|
+
/** `okfit query neighbors --format json`'s envelope, schema 1, snake_case. @public */
|
|
1278
|
+
export declare const QueryNeighborsEnvelope: Schema.Struct<{
|
|
1279
|
+
readonly schema: Schema.Literal<1>;
|
|
1280
|
+
readonly okfit_version: Schema.String;
|
|
1281
|
+
readonly engine_version: Schema.String;
|
|
1282
|
+
readonly distribution: Schema.NullOr<Schema.Struct<{
|
|
1283
|
+
readonly name: Schema.String;
|
|
1284
|
+
readonly version: Schema.String;
|
|
1285
|
+
}>>;
|
|
1286
|
+
readonly id: Schema.String;
|
|
1287
|
+
readonly outgoing: Schema.$Array<Schema.Struct<{
|
|
1288
|
+
readonly id: Schema.String;
|
|
1289
|
+
readonly kind: Schema.Literals<readonly ["concept", "file", "missing"]>;
|
|
1290
|
+
readonly summary: Schema.NullOr<Schema.Struct<{
|
|
1291
|
+
readonly id: Schema.String;
|
|
1292
|
+
readonly type: Schema.String;
|
|
1293
|
+
readonly title: Schema.String;
|
|
1294
|
+
readonly description: Schema.NullOr<Schema.String>;
|
|
1295
|
+
readonly status: Schema.Literals<readonly ["draft", "stable", "deprecated"]>;
|
|
1296
|
+
readonly tags: Schema.$Array<Schema.String>;
|
|
1297
|
+
readonly path: Schema.String;
|
|
1298
|
+
}>>;
|
|
1299
|
+
}>>;
|
|
1300
|
+
readonly incoming: Schema.$Array<Schema.Struct<{
|
|
1301
|
+
readonly id: Schema.String;
|
|
1302
|
+
readonly kind: Schema.Literals<readonly ["concept", "file", "missing"]>;
|
|
1303
|
+
readonly summary: Schema.NullOr<Schema.Struct<{
|
|
1304
|
+
readonly id: Schema.String;
|
|
1305
|
+
readonly type: Schema.String;
|
|
1306
|
+
readonly title: Schema.String;
|
|
1307
|
+
readonly description: Schema.NullOr<Schema.String>;
|
|
1308
|
+
readonly status: Schema.Literals<readonly ["draft", "stable", "deprecated"]>;
|
|
1309
|
+
readonly tags: Schema.$Array<Schema.String>;
|
|
1310
|
+
readonly path: Schema.String;
|
|
1311
|
+
}>>;
|
|
1312
|
+
}>>;
|
|
1313
|
+
}>;
|
|
1314
|
+
/** @public */
|
|
1315
|
+
export type QueryNeighborsEnvelope = typeof QueryNeighborsEnvelope.Type;
|
|
1316
|
+
/** Build the list envelope; `items` is the already-paged, already-sorted summaries. @public */
|
|
1317
|
+
export declare const queryListEnvelope: (input: {
|
|
1318
|
+
readonly okfitVersion: string;
|
|
1319
|
+
readonly total: number;
|
|
1320
|
+
readonly items: ReadonlyArray<ConceptSummary>;
|
|
1321
|
+
readonly distribution?: Distribution;
|
|
1322
|
+
}) => QueryListEnvelope;
|
|
1323
|
+
/** Build the get envelope from a summary, the raw frontmatter and the outgoing links. @public */
|
|
1324
|
+
export declare const queryGetEnvelope: (input: {
|
|
1325
|
+
readonly okfitVersion: string;
|
|
1326
|
+
readonly concept: ConceptSummary;
|
|
1327
|
+
readonly frontmatter: Readonly<Record<string, unknown>>;
|
|
1328
|
+
readonly links: ReadonlyArray<QueryLink>;
|
|
1329
|
+
readonly distribution?: Distribution;
|
|
1330
|
+
}) => QueryGetEnvelope;
|
|
1331
|
+
/** Build the neighbors envelope. @public */
|
|
1332
|
+
export declare const queryNeighborsEnvelope: (input: {
|
|
1333
|
+
readonly okfitVersion: string;
|
|
1334
|
+
readonly id: string;
|
|
1335
|
+
readonly outgoing: ReadonlyArray<QueryNeighbor>;
|
|
1336
|
+
readonly incoming: ReadonlyArray<QueryNeighbor>;
|
|
1337
|
+
readonly distribution?: Distribution;
|
|
1338
|
+
}) => QueryNeighborsEnvelope;
|
|
1339
|
+
//#endregion
|
|
1091
1340
|
//#region src/render/stale.d.ts
|
|
1092
1341
|
/** One entry of the `items` array. @public */
|
|
1093
1342
|
export declare const StaleItem: Schema.Struct<{
|
|
@@ -1298,6 +1547,47 @@ export declare const syncEnvelope: (input: {
|
|
|
1298
1547
|
readonly distribution?: Distribution;
|
|
1299
1548
|
}) => SyncEnvelope;
|
|
1300
1549
|
//#endregion
|
|
1550
|
+
//#region src/verify/select.d.ts
|
|
1551
|
+
/**
|
|
1552
|
+
* Why a candidate concept was skipped by batch selection: `"draft"` and
|
|
1553
|
+
* `"deprecated"` mirror `status`, `"already-verified"` means the actor already
|
|
1554
|
+
* carries a `verified` entry.
|
|
1555
|
+
*
|
|
1556
|
+
* @public
|
|
1557
|
+
*/
|
|
1558
|
+
type VerifyBatchSkipReason = "draft" | "deprecated" | "already-verified";
|
|
1559
|
+
/**
|
|
1560
|
+
* The outcome of {@link selectAttestable}.
|
|
1561
|
+
*
|
|
1562
|
+
* @public
|
|
1563
|
+
*/
|
|
1564
|
+
interface AttestableSelection {
|
|
1565
|
+
/** In bundle iteration order. */
|
|
1566
|
+
readonly candidates: ReadonlyArray<LoadedConcept>;
|
|
1567
|
+
readonly skipped: ReadonlyArray<{
|
|
1568
|
+
readonly id: string;
|
|
1569
|
+
readonly reason: VerifyBatchSkipReason;
|
|
1570
|
+
}>;
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Issue #138/#143: which concepts of `types` the actor may attest in one
|
|
1574
|
+
* batch. A draft is unsettled and a deprecated concept is retired (#143), so
|
|
1575
|
+
* both are skipped before the already-verified check.
|
|
1576
|
+
*
|
|
1577
|
+
* @public
|
|
1578
|
+
*/
|
|
1579
|
+
export declare const selectAttestable: (bundle: LoadedBundle, types: ReadonlySet<string>, actor: string) => AttestableSelection;
|
|
1580
|
+
/**
|
|
1581
|
+
* The batch type-resolution rule shared by `runVerifyBatch` and any future
|
|
1582
|
+
* interactive picker: explicit `types` must each be declared in
|
|
1583
|
+
* `config.types` (else `VerifySelectionError` with reason `unknown-type`);
|
|
1584
|
+
* an empty `types` selects every declared type whose `require_verified` is
|
|
1585
|
+
* `true`.
|
|
1586
|
+
*
|
|
1587
|
+
* @public
|
|
1588
|
+
*/
|
|
1589
|
+
export declare const resolveBatchTypes: (config: OkfitConfig, types: ReadonlyArray<string>) => Effect.Effect<ReadonlySet<string>, VerifySelectionError>;
|
|
1590
|
+
//#endregion
|
|
1301
1591
|
//#region src/render/verify.d.ts
|
|
1302
1592
|
/**
|
|
1303
1593
|
* V-11's success envelope, schema 1, snake_case — the same convention as
|
|
@@ -1322,6 +1612,11 @@ export declare const VerifyEnvelope: Schema.Struct<{
|
|
|
1322
1612
|
readonly by: Schema.String;
|
|
1323
1613
|
readonly at: Schema.String;
|
|
1324
1614
|
}>;
|
|
1615
|
+
/** Issue #185: the status change `--stable`/`--draft` made; `null` when neither flag was given. */
|
|
1616
|
+
readonly status: Schema.NullOr<Schema.Struct<{
|
|
1617
|
+
readonly from: Schema.NullOr<Schema.Literals<readonly ["draft", "stable", "deprecated"]>>;
|
|
1618
|
+
readonly to: Schema.Literals<readonly ["stable", "draft"]>;
|
|
1619
|
+
}>>;
|
|
1325
1620
|
readonly dry_run: Schema.Boolean;
|
|
1326
1621
|
readonly exit_code: Schema.Literal<0>;
|
|
1327
1622
|
}>;
|
|
@@ -1342,6 +1637,10 @@ export declare const verifyEnvelope: (input: {
|
|
|
1342
1637
|
readonly by: string;
|
|
1343
1638
|
readonly at: string;
|
|
1344
1639
|
readonly dryRun: boolean;
|
|
1640
|
+
readonly status: {
|
|
1641
|
+
readonly from: Status | null;
|
|
1642
|
+
readonly to: "stable" | "draft";
|
|
1643
|
+
} | null;
|
|
1345
1644
|
readonly distribution?: Distribution;
|
|
1346
1645
|
}) => VerifyEnvelope;
|
|
1347
1646
|
/**
|
|
@@ -1368,7 +1667,7 @@ export declare const VerifyBatchEnvelope: Schema.Struct<{
|
|
|
1368
1667
|
}>>;
|
|
1369
1668
|
readonly skipped: Schema.$Array<Schema.Struct<{
|
|
1370
1669
|
readonly id: Schema.String;
|
|
1371
|
-
readonly reason: Schema.Literals<readonly ["draft", "already-verified"]>;
|
|
1670
|
+
readonly reason: Schema.Literals<readonly ["draft", "deprecated", "already-verified"]>;
|
|
1372
1671
|
}>>;
|
|
1373
1672
|
readonly dry_run: Schema.Boolean;
|
|
1374
1673
|
readonly exit_code: Schema.Literal<0>;
|
|
@@ -1391,7 +1690,7 @@ export declare const verifyBatchEnvelope: (input: {
|
|
|
1391
1690
|
}>;
|
|
1392
1691
|
readonly skipped: ReadonlyArray<{
|
|
1393
1692
|
readonly id: string;
|
|
1394
|
-
readonly reason:
|
|
1693
|
+
readonly reason: VerifyBatchSkipReason;
|
|
1395
1694
|
}>;
|
|
1396
1695
|
readonly dryRun: boolean;
|
|
1397
1696
|
readonly distribution?: Distribution;
|
|
@@ -1621,6 +1920,8 @@ interface VerifyOptions {
|
|
|
1621
1920
|
/** `--at` when given, else the `Now` service (K-47/K-49). */
|
|
1622
1921
|
readonly at: DateTime.Utc;
|
|
1623
1922
|
readonly dryRun: boolean;
|
|
1923
|
+
/** Issue #185: also set the concept's `status` in the same write. */
|
|
1924
|
+
readonly status?: "stable" | "draft";
|
|
1624
1925
|
}
|
|
1625
1926
|
/** @internal */
|
|
1626
1927
|
interface VerifyResult {
|
|
@@ -1638,6 +1939,13 @@ interface VerifyResult {
|
|
|
1638
1939
|
readonly dryRun: boolean;
|
|
1639
1940
|
/** The exact bytes `splice`'s edit would insert, written or not. */
|
|
1640
1941
|
readonly fragment: string;
|
|
1942
|
+
/** `null` when no status flag was given; `from` is the RAW frontmatter status. */
|
|
1943
|
+
readonly status: {
|
|
1944
|
+
readonly from: Status | null;
|
|
1945
|
+
readonly to: "stable" | "draft";
|
|
1946
|
+
} | null;
|
|
1947
|
+
/** The exact status bytes spliced, or `null` when no status edit was made. */
|
|
1948
|
+
readonly statusFragment: string | null;
|
|
1641
1949
|
}
|
|
1642
1950
|
/**
|
|
1643
1951
|
* Issue #138: options for {@link runVerifyBatch}. `types` restricts the
|
|
@@ -1655,14 +1963,6 @@ interface VerifyBatchOptions {
|
|
|
1655
1963
|
/** Restrict to these types; empty = every type with `require_verified = true`. */
|
|
1656
1964
|
readonly types: ReadonlyArray<string>;
|
|
1657
1965
|
}
|
|
1658
|
-
/**
|
|
1659
|
-
* Why {@link runVerifyBatch} skipped a candidate concept: `"draft"` for
|
|
1660
|
-
* `status === "draft"`, `"already-verified"` when the resolved actor already
|
|
1661
|
-
* carries a `verified` entry.
|
|
1662
|
-
*
|
|
1663
|
-
* @public
|
|
1664
|
-
*/
|
|
1665
|
-
type VerifyBatchSkipReason = "draft" | "already-verified";
|
|
1666
1966
|
/**
|
|
1667
1967
|
* The result of {@link runVerifyBatch}.
|
|
1668
1968
|
*
|
|
@@ -1689,8 +1989,9 @@ interface VerifyBatchResult {
|
|
|
1689
1989
|
* human actor from git, splices exactly one entry into the concept's
|
|
1690
1990
|
* frontmatter and writes it back atomically.
|
|
1691
1991
|
*
|
|
1692
|
-
* Nothing here reads `
|
|
1693
|
-
*
|
|
1992
|
+
* Nothing here reads `stale_after` or a type's `require_verified`; `status`
|
|
1993
|
+
* is read only to skip a no-op edit under `options.status` (#185). `verify`
|
|
1994
|
+
* is mechanical, not config-aware (V-4, V-5).
|
|
1694
1995
|
*
|
|
1695
1996
|
* @internal
|
|
1696
1997
|
*/
|
|
@@ -1703,11 +2004,16 @@ export declare const runVerify: (options: VerifyOptions) => Effect.Effect<{
|
|
|
1703
2004
|
priorAt: string[];
|
|
1704
2005
|
dryRun: boolean;
|
|
1705
2006
|
fragment: string;
|
|
2007
|
+
status: {
|
|
2008
|
+
from: "deprecated" | "draft" | "stable" | null;
|
|
2009
|
+
to: "draft" | "stable";
|
|
2010
|
+
} | null;
|
|
2011
|
+
statusFragment: string | null;
|
|
1706
2012
|
}, 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>;
|
|
1707
2013
|
/**
|
|
1708
2014
|
* Issue #138. Attests every concept selected by `options.types` (or, when
|
|
1709
2015
|
* empty, every type whose declaration sets `require_verified = true`) that
|
|
1710
|
-
* is not a draft and does not already carry a `verified` entry by the
|
|
2016
|
+
* is not a draft, is not deprecated (#143) and does not already carry a `verified` entry by the
|
|
1711
2017
|
* resolved actor. Every candidate is located FIRST -- an unsupported
|
|
1712
2018
|
* `verified` shape on any one of them fails the whole batch with nothing
|
|
1713
2019
|
* written, mirroring `runVerify`'s own fail-closed rule at batch scale.
|
|
@@ -1724,7 +2030,7 @@ export declare const runVerifyBatch: (options: VerifyBatchOptions) => Effect.Eff
|
|
|
1724
2030
|
conceptPath: string;
|
|
1725
2031
|
fragment: string;
|
|
1726
2032
|
}[];
|
|
1727
|
-
skipped: {
|
|
2033
|
+
skipped: readonly {
|
|
1728
2034
|
readonly id: string;
|
|
1729
2035
|
readonly reason: VerifyBatchSkipReason;
|
|
1730
2036
|
}[];
|
|
@@ -1745,5 +2051,5 @@ export declare const runVerifyBatch: (options: VerifyBatchOptions) => Effect.Eff
|
|
|
1745
2051
|
*/
|
|
1746
2052
|
export declare const ENGINE_VERSION: string;
|
|
1747
2053
|
//#endregion
|
|
1748
|
-
export type { BundleSessionOptions, BundleSessionServices, BundleSessionShape, ContextResult, ContextRunOptions, DiagnosticSource, DiscoveredConfig, Distribution, DocumentInput, ExternalReferencesShape, GraphRunOptions, GraphRunResult, OverlayDocument, OverlayDocumentsShape, RenderedDiagnostic, ResolveProjectConfigInput, ResolvedProjectConfig, RevalidateError, RevalidateOptions, RevalidateResult, RevalidateTier, RunOptions, RunResult, ScaffoldFile, ScaffoldOptions, StaleRunOptions, StaleRunResult, SyncMode, SyncModeResult, SyncOptions, SyncResult, Tally, VerifyBatchOptions, VerifyBatchResult, VerifyBatchSkipReason, VerifyOptions, VerifyResult, WatchedFilesOutcome };
|
|
2054
|
+
export type { AttestableSelection, BundleSessionOptions, BundleSessionServices, BundleSessionShape, ConceptFilter, ConceptLink, ConceptNeighbor, ContextResult, ContextRunOptions, DiagnosticSource, DiscoveredConfig, Distribution, DocumentInput, ExternalReferencesShape, GraphRunOptions, GraphRunResult, OverlayDocument, OverlayDocumentsShape, RenderedDiagnostic, ResolveProjectConfigInput, ResolvedProjectConfig, RevalidateError, RevalidateOptions, RevalidateResult, RevalidateTier, RunOptions, RunResult, ScaffoldFile, ScaffoldOptions, StaleRunOptions, StaleRunResult, SyncMode, SyncModeResult, SyncOptions, SyncResult, Tally, VerifyBatchOptions, VerifyBatchResult, VerifyBatchSkipReason, VerifyOptions, VerifyResult, WatchedFilesOutcome };
|
|
1749
2055
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveBundleRoot, resolveProjectRoot } from "./config/anchor.js";
|
|
2
|
-
import { ConfigMalformedError, ConfigPathNotFoundError, DocumentPathError, InitOverwriteError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError } from "./errors.js";
|
|
2
|
+
import { ConfigMalformedError, ConfigPathNotFoundError, DocumentPathError, InitOverwriteError, QueryConceptNotFoundError, QuerySelectionError, QueryUnknownVocabularyError, SyncStagedLogError, VerifyConceptNotFoundError, VerifySelectionError, VerifyUnsupportedFrontmatterError } from "./errors.js";
|
|
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";
|
|
@@ -10,6 +10,7 @@ import { CONFIG_RELATIVE_PATH, configValue, files, targetPaths } from "./init/sc
|
|
|
10
10
|
import { OverlayDocuments, layerOverlayFileSystem, makeOverlayFileSystem } from "./overlay/layer.js";
|
|
11
11
|
import { provideDocuments, resolveDocumentPath } from "./overlay/documents.js";
|
|
12
12
|
import { OkfitPlatform } from "./platform.js";
|
|
13
|
+
import { ConceptQuery } from "./query/ConceptQuery.js";
|
|
13
14
|
import { ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, contextEnvelope } from "./render/context.js";
|
|
14
15
|
import { DistributionField } from "./render/distribution.js";
|
|
15
16
|
import { forDiagnostics, tally } from "./render/exit.js";
|
|
@@ -17,6 +18,7 @@ import { ENGINE_VERSION } from "./version.js";
|
|
|
17
18
|
import { GraphEdgeEnvelope, GraphEnvelope, GraphNodeEnvelope, GraphSummary, graphEnvelope } from "./render/graph.js";
|
|
18
19
|
import { collect, sort } from "./render/sort.js";
|
|
19
20
|
import { JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, json, jsonError } from "./render/json.js";
|
|
21
|
+
import { ConceptSummary, QueryGetEnvelope, QueryLink, QueryListEnvelope, QueryNeighbor, QueryNeighborsEnvelope, queryGetEnvelope, queryListEnvelope, queryNeighborsEnvelope, toConceptSummary } from "./render/query.js";
|
|
20
22
|
import { StaleEnvelope, StaleItem, StaleSummary, staleEnvelope } from "./render/stale.js";
|
|
21
23
|
import { SkipReason, runSync } from "./sync/run.js";
|
|
22
24
|
import { SyncEnvelope, SyncModeEnvelope, syncEnvelope } from "./render/sync.js";
|
|
@@ -26,6 +28,7 @@ import { conceptFor } from "./session/concept.js";
|
|
|
26
28
|
import { withFallbackRange } from "./session/range.js";
|
|
27
29
|
import { BundleSession } from "./session/BundleSession.js";
|
|
28
30
|
import { runStale } from "./stale/run.js";
|
|
31
|
+
import { resolveBatchTypes, selectAttestable } from "./verify/select.js";
|
|
29
32
|
import { runVerify, runVerifyBatch } from "./verify/run.js";
|
|
30
33
|
|
|
31
|
-
export { BundleSession, CONFIG_RELATIVE_PATH, ConfigMalformedError, ConfigPathNotFoundError, ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, DEFAULT_PROFILE_NAME, DistributionField, DocumentPathError, ENGINE_VERSION, ExternalReferences, FrontmatterEdits, GraphEdgeEnvelope, GraphEnvelope, GraphNodeEnvelope, GraphSummary, InitOverwriteError, JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, Now, OkfitPlatform, OverlayDocuments, ReferenceCheck, ReferenceState, SkipReason, StaleEnvelope, StaleItem, StaleSummary, SyncEnvelope, SyncModeEnvelope, SyncStagedLogError, UnsupportedFrontmatterError, VerifyBatchEnvelope, VerifyConceptNotFoundError, VerifyEnvelope, VerifySelectionError, VerifyUnsupportedFrontmatterError, buildConfigLayer, collect, conceptFor, configValue, contextEnvelope, files, forDiagnostics, graphEnvelope, json, jsonError, layerOverlayFileSystem, makeOverlayFileSystem, provideConfig, provideDocuments, resolveBundleRoot, resolveDocumentPath, resolveProjectConfig, resolveProjectRoot, run, runContext, runGraph, runStale, runSync, runVerify, runVerifyBatch, sort, staleEnvelope, syncEnvelope, tally, targetPaths, verifyBatchEnvelope, verifyEnvelope, withFallbackRange };
|
|
34
|
+
export { BundleSession, CONFIG_RELATIVE_PATH, ConceptQuery, ConceptSummary, ConfigMalformedError, ConfigPathNotFoundError, ContextEnvelope, ContextField, ContextFieldValue, ContextTag, ContextType, DEFAULT_PROFILE_NAME, DistributionField, DocumentPathError, ENGINE_VERSION, ExternalReferences, FrontmatterEdits, GraphEdgeEnvelope, GraphEnvelope, GraphNodeEnvelope, GraphSummary, InitOverwriteError, JsonDiagnostic, JsonEnvelope, JsonErrorEnvelope, JsonSummary, Now, OkfitPlatform, OverlayDocuments, QueryConceptNotFoundError, QueryGetEnvelope, QueryLink, QueryListEnvelope, QueryNeighbor, QueryNeighborsEnvelope, QuerySelectionError, QueryUnknownVocabularyError, ReferenceCheck, ReferenceState, SkipReason, StaleEnvelope, StaleItem, StaleSummary, SyncEnvelope, SyncModeEnvelope, SyncStagedLogError, UnsupportedFrontmatterError, VerifyBatchEnvelope, VerifyConceptNotFoundError, VerifyEnvelope, VerifySelectionError, VerifyUnsupportedFrontmatterError, buildConfigLayer, collect, conceptFor, configValue, contextEnvelope, files, forDiagnostics, graphEnvelope, json, jsonError, layerOverlayFileSystem, makeOverlayFileSystem, provideConfig, provideDocuments, queryGetEnvelope, queryListEnvelope, queryNeighborsEnvelope, resolveBatchTypes, resolveBundleRoot, resolveDocumentPath, resolveProjectConfig, resolveProjectRoot, run, runContext, runGraph, runStale, runSync, runVerify, runVerifyBatch, selectAttestable, sort, staleEnvelope, syncEnvelope, tally, targetPaths, toConceptSummary, verifyBatchEnvelope, verifyEnvelope, withFallbackRange };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okfit/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@effect/platform-node": "4.0.0-rc.118",
|
|
38
|
-
"@effected/app": "^0.19.
|
|
38
|
+
"@effected/app": "^0.19.1",
|
|
39
39
|
"@effected/config-file": "^0.13.1",
|
|
40
40
|
"@effected/engine": "^0.2.0",
|
|
41
41
|
"@effected/git": "^0.19.0",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"@effected/schemastore": "^0.17.0",
|
|
46
46
|
"@effected/store": "^0.11.0",
|
|
47
47
|
"@effected/toml": "^0.10.0",
|
|
48
|
-
"@effected/walker": "^0.14.
|
|
49
|
-
"@effected/xdg": "^0.8.
|
|
48
|
+
"@effected/walker": "^0.14.1",
|
|
49
|
+
"@effected/xdg": "^0.8.2",
|
|
50
50
|
"@effected/yaml": "^0.18.0",
|
|
51
51
|
"@okfit/core": "0.8.3",
|
|
52
52
|
"@okfit/profiles": "0.8.2",
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { QueryConceptNotFoundError, QueryUnknownVocabularyError } from "../errors.js";
|
|
2
|
+
import { ConceptId, Derive, Graph } from "@okfit/core";
|
|
3
|
+
import { Effect, Option } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/query/ConceptQuery.ts
|
|
6
|
+
const byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
7
|
+
const resolve = (bundle, rawId) => Effect.gen(function* () {
|
|
8
|
+
const normalized = ConceptId.normalize(rawId);
|
|
9
|
+
if (Option.isNone(normalized)) return yield* Effect.fail(new QueryConceptNotFoundError({
|
|
10
|
+
id: rawId,
|
|
11
|
+
reason: "empty-id"
|
|
12
|
+
}));
|
|
13
|
+
const id = normalized.value;
|
|
14
|
+
const concept = bundle.concepts.get(id);
|
|
15
|
+
if (concept === void 0) return yield* Effect.fail(new QueryConceptNotFoundError({
|
|
16
|
+
id: rawId,
|
|
17
|
+
reason: "not-a-concept"
|
|
18
|
+
}));
|
|
19
|
+
return {
|
|
20
|
+
id,
|
|
21
|
+
concept
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Read-only queries over a loaded bundle, shared by `okfit query` and the MCP
|
|
26
|
+
* tools.
|
|
27
|
+
*
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
const ConceptQuery = {
|
|
31
|
+
/** Validates types then tags against config (first unknown fails), then filters and sorts by id. */
|
|
32
|
+
list: (bundle, config, filter) => Effect.gen(function* () {
|
|
33
|
+
const declaredTypes = Object.keys(config.types ?? {}).toSorted();
|
|
34
|
+
const declaredTags = Object.keys(config.tags ?? {}).toSorted();
|
|
35
|
+
const types = filter.types ?? [];
|
|
36
|
+
for (const type of types) if (!declaredTypes.includes(type)) return yield* Effect.fail(new QueryUnknownVocabularyError({
|
|
37
|
+
kind: "type",
|
|
38
|
+
requested: type,
|
|
39
|
+
valid: declaredTypes
|
|
40
|
+
}));
|
|
41
|
+
const requestedTags = filter.tags ?? [];
|
|
42
|
+
for (const tag of requestedTags) if (!declaredTags.includes(tag)) return yield* Effect.fail(new QueryUnknownVocabularyError({
|
|
43
|
+
kind: "tag",
|
|
44
|
+
requested: tag,
|
|
45
|
+
valid: declaredTags
|
|
46
|
+
}));
|
|
47
|
+
const statuses = filter.statuses ?? [];
|
|
48
|
+
return [...bundle.concepts.values()].filter((concept) => {
|
|
49
|
+
if (types.length > 0 && !types.includes(concept.frontmatter.type)) return false;
|
|
50
|
+
const tags = concept.frontmatter.tags ?? [];
|
|
51
|
+
if (!requestedTags.every((tag) => tags.includes(tag))) return false;
|
|
52
|
+
if (statuses.length > 0 && !statuses.includes(Derive.status(concept.frontmatter))) return false;
|
|
53
|
+
if (filter.verified !== void 0) {
|
|
54
|
+
if ((concept.frontmatter.verified ?? []).length > 0 !== filter.verified) return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}).toSorted(byId);
|
|
58
|
+
}),
|
|
59
|
+
/** One concept by tolerant id, with its outgoing links. */
|
|
60
|
+
get: (bundle, id) => Effect.gen(function* () {
|
|
61
|
+
const resolved = yield* resolve(bundle, id);
|
|
62
|
+
const graph = Graph.fromBundle(bundle);
|
|
63
|
+
const links = graph.edges.filter((edge) => edge.from === resolved.id).map((edge) => ({
|
|
64
|
+
to: edge.to,
|
|
65
|
+
kind: Option.match(graph.node(edge.to), {
|
|
66
|
+
onNone: () => "missing",
|
|
67
|
+
onSome: (node) => node.kind
|
|
68
|
+
}),
|
|
69
|
+
source: edge.data.source,
|
|
70
|
+
...edge.data.field === void 0 ? {} : { field: edge.data.field }
|
|
71
|
+
}));
|
|
72
|
+
return {
|
|
73
|
+
concept: resolved.concept,
|
|
74
|
+
links
|
|
75
|
+
};
|
|
76
|
+
}),
|
|
77
|
+
/** Outgoing and incoming graph neighbours of one concept. */
|
|
78
|
+
neighbors: (bundle, id) => Effect.gen(function* () {
|
|
79
|
+
const resolved = yield* resolve(bundle, id);
|
|
80
|
+
const graph = Graph.fromBundle(bundle);
|
|
81
|
+
const project = (node) => {
|
|
82
|
+
if (node.kind !== "concept") return {
|
|
83
|
+
id: node.id,
|
|
84
|
+
kind: node.kind,
|
|
85
|
+
concept: null
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
id: node.id,
|
|
89
|
+
kind: node.kind,
|
|
90
|
+
concept: bundle.concepts.get(node.id) ?? null
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
id: resolved.id,
|
|
95
|
+
outgoing: graph.successors(resolved.id).map(project),
|
|
96
|
+
incoming: graph.predecessors(resolved.id).map(project)
|
|
97
|
+
};
|
|
98
|
+
})
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
export { ConceptQuery };
|
package/render/query.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { DistributionField } from "./distribution.js";
|
|
2
|
+
import { ENGINE_VERSION } from "../version.js";
|
|
3
|
+
import { Derive, GraphNodeKind, Status } from "@okfit/core";
|
|
4
|
+
import { Schema } from "effect";
|
|
5
|
+
|
|
6
|
+
//#region src/render/query.ts
|
|
7
|
+
/** The seven-field shape every list-like query result carries (N-16). @public */
|
|
8
|
+
const ConceptSummary = Schema.Struct({
|
|
9
|
+
id: Schema.String,
|
|
10
|
+
type: Schema.String,
|
|
11
|
+
title: Schema.String,
|
|
12
|
+
description: Schema.NullOr(Schema.String),
|
|
13
|
+
status: Status,
|
|
14
|
+
tags: Schema.Array(Schema.String),
|
|
15
|
+
path: Schema.String
|
|
16
|
+
});
|
|
17
|
+
/** Project one loaded concept into its summary. @public */
|
|
18
|
+
const toConceptSummary = (concept) => ({
|
|
19
|
+
id: concept.id,
|
|
20
|
+
type: concept.frontmatter.type,
|
|
21
|
+
title: Derive.title(concept),
|
|
22
|
+
description: concept.frontmatter.description ?? null,
|
|
23
|
+
status: Derive.status(concept.frontmatter),
|
|
24
|
+
tags: [...concept.frontmatter.tags ?? []],
|
|
25
|
+
path: concept.path
|
|
26
|
+
});
|
|
27
|
+
/** `okfit query list --format json`'s envelope, schema 1, snake_case. @public */
|
|
28
|
+
const QueryListEnvelope = Schema.Struct({
|
|
29
|
+
schema: Schema.Literal(1),
|
|
30
|
+
okfit_version: Schema.String,
|
|
31
|
+
engine_version: Schema.String,
|
|
32
|
+
distribution: DistributionField,
|
|
33
|
+
total: Schema.Int,
|
|
34
|
+
items: Schema.Array(ConceptSummary)
|
|
35
|
+
});
|
|
36
|
+
/** One outgoing link of a concept, as `okfit query get` reports it. @public */
|
|
37
|
+
const QueryLink = Schema.Struct({
|
|
38
|
+
to: Schema.String,
|
|
39
|
+
kind: GraphNodeKind,
|
|
40
|
+
source: Schema.Literals(["body", "frontmatter"]),
|
|
41
|
+
field: Schema.optionalKey(Schema.String)
|
|
42
|
+
});
|
|
43
|
+
/** `okfit query get --format json`'s envelope, schema 1, snake_case. @public */
|
|
44
|
+
const QueryGetEnvelope = Schema.Struct({
|
|
45
|
+
schema: Schema.Literal(1),
|
|
46
|
+
okfit_version: Schema.String,
|
|
47
|
+
engine_version: Schema.String,
|
|
48
|
+
distribution: DistributionField,
|
|
49
|
+
concept: Schema.Struct({
|
|
50
|
+
...ConceptSummary.fields,
|
|
51
|
+
frontmatter: Schema.Record(Schema.String, Schema.Unknown),
|
|
52
|
+
links: Schema.Array(QueryLink)
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
/** One neighbour of a concept; `summary` is `null` for a non-concept node. @public */
|
|
56
|
+
const QueryNeighbor = Schema.Struct({
|
|
57
|
+
id: Schema.String,
|
|
58
|
+
kind: GraphNodeKind,
|
|
59
|
+
summary: Schema.NullOr(ConceptSummary)
|
|
60
|
+
});
|
|
61
|
+
/** `okfit query neighbors --format json`'s envelope, schema 1, snake_case. @public */
|
|
62
|
+
const QueryNeighborsEnvelope = Schema.Struct({
|
|
63
|
+
schema: Schema.Literal(1),
|
|
64
|
+
okfit_version: Schema.String,
|
|
65
|
+
engine_version: Schema.String,
|
|
66
|
+
distribution: DistributionField,
|
|
67
|
+
id: Schema.String,
|
|
68
|
+
outgoing: Schema.Array(QueryNeighbor),
|
|
69
|
+
incoming: Schema.Array(QueryNeighbor)
|
|
70
|
+
});
|
|
71
|
+
/** Build the list envelope; `items` is the already-paged, already-sorted summaries. @public */
|
|
72
|
+
const queryListEnvelope = (input) => ({
|
|
73
|
+
schema: 1,
|
|
74
|
+
okfit_version: input.okfitVersion,
|
|
75
|
+
engine_version: ENGINE_VERSION,
|
|
76
|
+
distribution: input.distribution ?? null,
|
|
77
|
+
total: input.total,
|
|
78
|
+
items: input.items
|
|
79
|
+
});
|
|
80
|
+
/** Build the get envelope from a summary, the raw frontmatter and the outgoing links. @public */
|
|
81
|
+
const queryGetEnvelope = (input) => ({
|
|
82
|
+
schema: 1,
|
|
83
|
+
okfit_version: input.okfitVersion,
|
|
84
|
+
engine_version: ENGINE_VERSION,
|
|
85
|
+
distribution: input.distribution ?? null,
|
|
86
|
+
concept: {
|
|
87
|
+
...input.concept,
|
|
88
|
+
frontmatter: input.frontmatter,
|
|
89
|
+
links: input.links
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
/** Build the neighbors envelope. @public */
|
|
93
|
+
const queryNeighborsEnvelope = (input) => ({
|
|
94
|
+
schema: 1,
|
|
95
|
+
okfit_version: input.okfitVersion,
|
|
96
|
+
engine_version: ENGINE_VERSION,
|
|
97
|
+
distribution: input.distribution ?? null,
|
|
98
|
+
id: input.id,
|
|
99
|
+
outgoing: input.outgoing,
|
|
100
|
+
incoming: input.incoming
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
export { ConceptSummary, QueryGetEnvelope, QueryLink, QueryListEnvelope, QueryNeighbor, QueryNeighborsEnvelope, queryGetEnvelope, queryListEnvelope, queryNeighborsEnvelope, toConceptSummary };
|
package/render/verify.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DistributionField } from "./distribution.js";
|
|
2
2
|
import { ENGINE_VERSION } from "../version.js";
|
|
3
|
+
import { Status } from "@okfit/core";
|
|
3
4
|
import { Schema } from "effect";
|
|
4
5
|
|
|
5
6
|
//#region src/render/verify.ts
|
|
@@ -23,6 +24,11 @@ const VerifyEnvelope = Schema.Struct({
|
|
|
23
24
|
by: Schema.String,
|
|
24
25
|
at: Schema.String
|
|
25
26
|
}),
|
|
27
|
+
/** Issue #185: the status change `--stable`/`--draft` made; `null` when neither flag was given. */
|
|
28
|
+
status: Schema.NullOr(Schema.Struct({
|
|
29
|
+
from: Schema.NullOr(Status),
|
|
30
|
+
to: Schema.Literals(["stable", "draft"])
|
|
31
|
+
})),
|
|
26
32
|
dry_run: Schema.Boolean,
|
|
27
33
|
exit_code: Schema.Literal(0)
|
|
28
34
|
});
|
|
@@ -45,6 +51,7 @@ const verifyEnvelope = (input) => ({
|
|
|
45
51
|
by: input.by,
|
|
46
52
|
at: input.at
|
|
47
53
|
},
|
|
54
|
+
status: input.status,
|
|
48
55
|
dry_run: input.dryRun,
|
|
49
56
|
exit_code: 0
|
|
50
57
|
});
|
|
@@ -69,7 +76,11 @@ const VerifyBatchEnvelope = Schema.Struct({
|
|
|
69
76
|
})),
|
|
70
77
|
skipped: Schema.Array(Schema.Struct({
|
|
71
78
|
id: Schema.String,
|
|
72
|
-
reason: Schema.Literals([
|
|
79
|
+
reason: Schema.Literals([
|
|
80
|
+
"draft",
|
|
81
|
+
"deprecated",
|
|
82
|
+
"already-verified"
|
|
83
|
+
])
|
|
73
84
|
})),
|
|
74
85
|
dry_run: Schema.Boolean,
|
|
75
86
|
exit_code: Schema.Literal(0)
|
package/verify/run.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { VerifyConceptNotFoundError,
|
|
2
|
-
import { documentNewline, locate, stripBom } from "./locate.js";
|
|
3
|
-
import { splice } from "./splice.js";
|
|
1
|
+
import { VerifyConceptNotFoundError, VerifyUnsupportedFrontmatterError } from "../errors.js";
|
|
2
|
+
import { documentNewline, locate, locateTopLevelScalar, stripBom } from "./locate.js";
|
|
3
|
+
import { mergeSameOffset, splice, spliceTopLevelScalar } from "./splice.js";
|
|
4
|
+
import { resolveBatchTypes, selectAttestable } from "./select.js";
|
|
4
5
|
import { Bundle, ConceptId, Timestamp } from "@okfit/core";
|
|
5
6
|
import { Effect, FileSystem, Option, Path, Schema } from "effect";
|
|
6
7
|
import { Derivation } from "@okfit/profiles";
|
|
@@ -24,25 +25,39 @@ const UNDECODABLE_CODES = /* @__PURE__ */ new Set([
|
|
|
24
25
|
"type-missing"
|
|
25
26
|
]);
|
|
26
27
|
/** Steps 9-14 of runVerify: read, locate (fail closed), splice; no write. */
|
|
27
|
-
const prepareVerify = Effect.fn("okfit/verify/prepareVerify")(function* (bundle, concept, actor, at) {
|
|
28
|
+
const prepareVerify = Effect.fn("okfit/verify/prepareVerify")(function* (bundle, concept, actor, at, status) {
|
|
28
29
|
const fs = yield* FileSystem.FileSystem;
|
|
29
30
|
const absolutePath = (yield* Path.Path).join(bundle.root, concept.path);
|
|
30
|
-
const source = yield* fs.
|
|
31
|
+
const source = new TextDecoder("utf-8", { ignoreBOM: true }).decode(yield* fs.readFile(absolutePath));
|
|
31
32
|
const { text, bom } = stripBom(source);
|
|
32
33
|
const located = yield* locate(text);
|
|
33
34
|
if (located._tag === "unsupported") return yield* new VerifyUnsupportedFrontmatterError({
|
|
34
35
|
id: concept.id,
|
|
35
|
-
shape: located.shape
|
|
36
|
+
shape: located.shape,
|
|
37
|
+
key: "verified"
|
|
36
38
|
});
|
|
37
39
|
const priorAt = (concept.frontmatter.verified ?? []).filter((entry) => entry.by === actor).map((entry) => Schema.encodeSync(Timestamp)(entry.at));
|
|
38
|
-
const
|
|
40
|
+
const newline = documentNewline(text);
|
|
41
|
+
const verifiedEdit = splice(located, {
|
|
39
42
|
by: actor,
|
|
40
43
|
at
|
|
41
|
-
},
|
|
44
|
+
}, newline);
|
|
45
|
+
let statusEdit;
|
|
46
|
+
if (status !== void 0 && concept.frontmatter.status !== status) {
|
|
47
|
+
const target = yield* locateTopLevelScalar(text, "status");
|
|
48
|
+
if (target._tag === "unsupported") return yield* new VerifyUnsupportedFrontmatterError({
|
|
49
|
+
id: concept.id,
|
|
50
|
+
shape: target.shape,
|
|
51
|
+
key: "status"
|
|
52
|
+
});
|
|
53
|
+
statusEdit = spliceTopLevelScalar(target, "status", status, newline);
|
|
54
|
+
}
|
|
55
|
+
const edits = mergeSameOffset([verifiedEdit, ...statusEdit === void 0 ? [] : [statusEdit]].toSorted((a, b) => a.offset - b.offset || (a === statusEdit ? -1 : b === statusEdit ? 1 : 0)));
|
|
42
56
|
return {
|
|
43
57
|
absolutePath,
|
|
44
|
-
finalText: bom + MarkdownEdit.applyAll(text,
|
|
45
|
-
fragment:
|
|
58
|
+
finalText: bom + MarkdownEdit.applyAll(text, edits),
|
|
59
|
+
fragment: verifiedEdit.content,
|
|
60
|
+
statusFragment: statusEdit?.content ?? null,
|
|
46
61
|
priorAt
|
|
47
62
|
};
|
|
48
63
|
});
|
|
@@ -61,8 +76,9 @@ const writeVerified = Effect.fn("okfit/verify/writeVerified")(function* (absolut
|
|
|
61
76
|
* human actor from git, splices exactly one entry into the concept's
|
|
62
77
|
* frontmatter and writes it back atomically.
|
|
63
78
|
*
|
|
64
|
-
* Nothing here reads `
|
|
65
|
-
*
|
|
79
|
+
* Nothing here reads `stale_after` or a type's `require_verified`; `status`
|
|
80
|
+
* is read only to skip a no-op edit under `options.status` (#185). `verify`
|
|
81
|
+
* is mechanical, not config-aware (V-4, V-5).
|
|
66
82
|
*
|
|
67
83
|
* @internal
|
|
68
84
|
*/
|
|
@@ -99,7 +115,7 @@ const runVerify = Effect.fn("okfit/verify/runVerify")(function* (options) {
|
|
|
99
115
|
config: options.config
|
|
100
116
|
});
|
|
101
117
|
const at = Schema.encodeSync(Timestamp)(options.at);
|
|
102
|
-
const prepared = yield* prepareVerify(bundle, concept, actor, at);
|
|
118
|
+
const prepared = yield* prepareVerify(bundle, concept, actor, at, options.status);
|
|
103
119
|
if (!options.dryRun) yield* writeVerified(prepared.absolutePath, prepared.finalText);
|
|
104
120
|
return {
|
|
105
121
|
id,
|
|
@@ -109,13 +125,18 @@ const runVerify = Effect.fn("okfit/verify/runVerify")(function* (options) {
|
|
|
109
125
|
at,
|
|
110
126
|
priorAt: prepared.priorAt,
|
|
111
127
|
dryRun: options.dryRun,
|
|
112
|
-
fragment: prepared.fragment
|
|
128
|
+
fragment: prepared.fragment,
|
|
129
|
+
status: options.status === void 0 ? null : {
|
|
130
|
+
from: concept.frontmatter.status ?? null,
|
|
131
|
+
to: options.status
|
|
132
|
+
},
|
|
133
|
+
statusFragment: prepared.statusFragment
|
|
113
134
|
};
|
|
114
135
|
});
|
|
115
136
|
/**
|
|
116
137
|
* Issue #138. Attests every concept selected by `options.types` (or, when
|
|
117
138
|
* empty, every type whose declaration sets `require_verified = true`) that
|
|
118
|
-
* is not a draft and does not already carry a `verified` entry by the
|
|
139
|
+
* is not a draft, is not deprecated (#143) and does not already carry a `verified` entry by the
|
|
119
140
|
* resolved actor. Every candidate is located FIRST -- an unsupported
|
|
120
141
|
* `verified` shape on any one of them fails the whole batch with nothing
|
|
121
142
|
* written, mirroring `runVerify`'s own fail-closed rule at batch scale.
|
|
@@ -124,45 +145,20 @@ const runVerify = Effect.fn("okfit/verify/runVerify")(function* (options) {
|
|
|
124
145
|
*/
|
|
125
146
|
const runVerifyBatch = Effect.fn("okfit/verify/runVerifyBatch")(function* (options) {
|
|
126
147
|
const bundle = yield* Bundle.load({ root: options.bundleRoot });
|
|
127
|
-
const
|
|
128
|
-
let types;
|
|
129
|
-
if (options.types.length > 0) {
|
|
130
|
-
for (const type of options.types) if (!Object.hasOwn(declared, type)) return yield* new VerifySelectionError({
|
|
131
|
-
reason: "unknown-type",
|
|
132
|
-
detail: type
|
|
133
|
-
});
|
|
134
|
-
types = new Set(options.types);
|
|
135
|
-
} else types = new Set(Object.entries(declared).flatMap(([name, spec]) => spec.require_verified === true ? [name] : []));
|
|
148
|
+
const types = yield* resolveBatchTypes(options.config, options.types);
|
|
136
149
|
const actor = yield* Derivation.generatedBy({
|
|
137
150
|
writer: "human",
|
|
138
151
|
cwd: options.projectRoot,
|
|
139
152
|
config: options.config
|
|
140
153
|
});
|
|
141
154
|
const at = Schema.encodeSync(Timestamp)(options.at);
|
|
142
|
-
const
|
|
155
|
+
const selection = selectAttestable(bundle, types, actor);
|
|
143
156
|
const prepared = [];
|
|
144
|
-
for (const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
reason: "draft"
|
|
150
|
-
});
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
if ((concept.frontmatter.verified ?? []).some((entry) => entry.by === actor)) {
|
|
154
|
-
skipped.push({
|
|
155
|
-
id,
|
|
156
|
-
reason: "already-verified"
|
|
157
|
-
});
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
prepared.push({
|
|
161
|
-
id,
|
|
162
|
-
conceptPath: concept.path,
|
|
163
|
-
...yield* prepareVerify(bundle, concept, actor, at)
|
|
164
|
-
});
|
|
165
|
-
}
|
|
157
|
+
for (const concept of selection.candidates) prepared.push({
|
|
158
|
+
id: concept.id,
|
|
159
|
+
conceptPath: concept.path,
|
|
160
|
+
...yield* prepareVerify(bundle, concept, actor, at, void 0)
|
|
161
|
+
});
|
|
166
162
|
if (!options.dryRun) for (const entry of prepared) yield* writeVerified(entry.absolutePath, entry.finalText);
|
|
167
163
|
return {
|
|
168
164
|
bundleRoot: bundle.root,
|
|
@@ -174,7 +170,7 @@ const runVerifyBatch = Effect.fn("okfit/verify/runVerifyBatch")(function* (optio
|
|
|
174
170
|
conceptPath,
|
|
175
171
|
fragment
|
|
176
172
|
})),
|
|
177
|
-
skipped
|
|
173
|
+
skipped: selection.skipped
|
|
178
174
|
};
|
|
179
175
|
});
|
|
180
176
|
|
package/verify/select.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { VerifySelectionError } from "../errors.js";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/verify/select.ts
|
|
5
|
+
/**
|
|
6
|
+
* Issue #138/#143: which concepts of `types` the actor may attest in one
|
|
7
|
+
* batch. A draft is unsettled and a deprecated concept is retired (#143), so
|
|
8
|
+
* both are skipped before the already-verified check.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
const selectAttestable = (bundle, types, actor) => {
|
|
13
|
+
const candidates = [];
|
|
14
|
+
const skipped = [];
|
|
15
|
+
for (const [id, concept] of bundle.concepts) {
|
|
16
|
+
if (!types.has(concept.frontmatter.type)) continue;
|
|
17
|
+
const status = concept.frontmatter.status;
|
|
18
|
+
if (status === "draft" || status === "deprecated") {
|
|
19
|
+
skipped.push({
|
|
20
|
+
id,
|
|
21
|
+
reason: status
|
|
22
|
+
});
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if ((concept.frontmatter.verified ?? []).some((entry) => entry.by === actor)) {
|
|
26
|
+
skipped.push({
|
|
27
|
+
id,
|
|
28
|
+
reason: "already-verified"
|
|
29
|
+
});
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
candidates.push(concept);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
candidates,
|
|
36
|
+
skipped
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The batch type-resolution rule shared by `runVerifyBatch` and any future
|
|
41
|
+
* interactive picker: explicit `types` must each be declared in
|
|
42
|
+
* `config.types` (else `VerifySelectionError` with reason `unknown-type`);
|
|
43
|
+
* an empty `types` selects every declared type whose `require_verified` is
|
|
44
|
+
* `true`.
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
const resolveBatchTypes = (config, types) => {
|
|
49
|
+
const declared = config.types ?? {};
|
|
50
|
+
if (types.length === 0) return Effect.succeed(new Set(Object.entries(declared).flatMap(([name, spec]) => spec.require_verified === true ? [name] : [])));
|
|
51
|
+
for (const type of types) if (!Object.hasOwn(declared, type)) return Effect.fail(new VerifySelectionError({
|
|
52
|
+
reason: "unknown-type",
|
|
53
|
+
detail: type
|
|
54
|
+
}));
|
|
55
|
+
return Effect.succeed(new Set(types));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
export { resolveBatchTypes, selectAttestable };
|
package/verify/splice.js
CHANGED
|
@@ -128,6 +128,30 @@ const spliceGeneratedBlock = (target, fields, newline) => MarkdownEdit.make({
|
|
|
128
128
|
length: 0,
|
|
129
129
|
content: `generated:${newline} by: ${fields.by}${newline} at: ${fields.at}${newline} body_sha256: ${fields.bodySha256}${newline}`
|
|
130
130
|
});
|
|
131
|
+
/**
|
|
132
|
+
* Issue #185: `okfit verify --stable` splices `status` and `verified` in one
|
|
133
|
+
* write. When `status` is absent it is inserted after `title:` (else `type:`),
|
|
134
|
+
* and when `verified` is absent it is appended as the last key, so both land
|
|
135
|
+
* on the same offset when that anchor is the last key. `MarkdownEdit.applyAll`
|
|
136
|
+
* rejects two zero-length inserts at one offset, the same case
|
|
137
|
+
* `spliceGeneratedFields` merges, so this folds them into one edit, input
|
|
138
|
+
* order preserved.
|
|
139
|
+
*
|
|
140
|
+
* @internal
|
|
141
|
+
*/
|
|
142
|
+
const mergeSameOffset = (edits) => {
|
|
143
|
+
const out = [];
|
|
144
|
+
for (const edit of edits) {
|
|
145
|
+
const previous = out.at(-1);
|
|
146
|
+
if (previous !== void 0 && previous.length === 0 && edit.length === 0 && previous.offset === edit.offset) out[out.length - 1] = MarkdownEdit.make({
|
|
147
|
+
offset: edit.offset,
|
|
148
|
+
length: 0,
|
|
149
|
+
content: previous.content + edit.content
|
|
150
|
+
});
|
|
151
|
+
else out.push(edit);
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
154
|
+
};
|
|
131
155
|
|
|
132
156
|
//#endregion
|
|
133
|
-
export { splice, spliceGenerated, spliceGeneratedBlock, spliceGeneratedFields, spliceTopLevelScalar };
|
|
157
|
+
export { mergeSameOffset, splice, spliceGenerated, spliceGeneratedBlock, spliceGeneratedFields, spliceTopLevelScalar };
|