superbee 0.4.4-pre.13 → 0.4.4-pre.15
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/README.md +47 -0
- package/SKILL.md +1 -1
- package/dist/publication/bridge-host-kind.d.ts +2 -0
- package/dist/publication/bridge.d.ts +1 -1
- package/dist/publication-bridge.mjs +308 -100
- package/dist/publication.mjs +308 -100
- package/dist/superbee.mjs +1720 -1080
- package/package.json +2 -2
- package/references/hosted-checkout.md +31 -0
- package/references/modeling-and-delivery.md +32 -0
package/README.md
CHANGED
|
@@ -127,6 +127,51 @@ In a hosted checkout, `superbee view read <id>` prints a View page's version on
|
|
|
127
127
|
`superbee view update <id> --expected-version <version>` replaces the page there (sync never sends
|
|
128
128
|
`views/`). Uploading a page never approves its execution.
|
|
129
129
|
|
|
130
|
+
## Copying documents between bundles
|
|
131
|
+
|
|
132
|
+
`superbee doc import` copies chosen documents from another bundle with new ids and rewritten links.
|
|
133
|
+
Declare the source first, then review the plan before applying it:
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
superbee deps add wave --bundle-id <source-bundle-id> --path ../wave
|
|
137
|
+
superbee doc import --from wave --with wave=../wave --select type=Theme --prefix wave
|
|
138
|
+
superbee doc import --from wave --with wave=../wave --select type=Theme --prefix wave --apply <plan-token>
|
|
139
|
+
superbee doc import --from wave --with wave=../wave --refresh <record-id>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The first import command only prints the plan and its token. Applying writes new documents and one
|
|
143
|
+
Promotion record, and never overwrites a document. `--resume <record-id>` finishes an interrupted
|
|
144
|
+
run. `--refresh <record-id>` updates the copies from their sources with the same plan and apply
|
|
145
|
+
steps; a copy edited since the import is a conflict until you name it with `--overwrite <id>` or
|
|
146
|
+
`--skip <id>`. The target must be a local bundle or Git board. `superbee doc import --help` has the
|
|
147
|
+
full reference.
|
|
148
|
+
|
|
149
|
+
## When a document cannot be read
|
|
150
|
+
|
|
151
|
+
Superbee bounds how much frontmatter it parses: 262,144 characters of YAML, 32 levels of nesting, and limits on
|
|
152
|
+
YAML alias use. Ordinary documents are far below these limits. A write over a limit is refused with
|
|
153
|
+
`USAGE` and nothing is stored. A document already in the bundle that is over a limit, or whose YAML
|
|
154
|
+
is invalid, cannot be read:
|
|
155
|
+
|
|
156
|
+
- `doc read` on it fails with `RUNTIME`. `details.frontmatter.code` is `FRONTMATTER_TOO_LARGE`,
|
|
157
|
+
`FRONTMATTER_TOO_DEEP`, `FRONTMATTER_ALIAS_LIMIT`, `FRONTMATTER_CYCLE` or
|
|
158
|
+
`FRONTMATTER_MALFORMED`, and `details.frontmatter.document` names the file.
|
|
159
|
+
- To find every affected document, run `superbee status` (the `malformed` count and rows) or
|
|
160
|
+
`superbee list` (the `skipped` rows). `search`, `query`, `index generate`, `link list`,
|
|
161
|
+
`link show`, `view list` and `kinds --coverage` also skip it, say so, and keep working.
|
|
162
|
+
- To fix it, edit the lines between the `---` markers in a text editor, or remove the document with
|
|
163
|
+
`superbee doc delete <id>`. `doc update` and `doc field` cannot change a document they cannot read.
|
|
164
|
+
|
|
165
|
+
On a Git board, `sync` holds a changed file it cannot read: it exits 5, names the file, and commits
|
|
166
|
+
and pushes nothing until you fix it. If a conflict kept a version that cannot be read, the receipt
|
|
167
|
+
tells you to copy your saved export over the document.
|
|
168
|
+
|
|
169
|
+
In a hosted checkout, a host document this client cannot store comes back as a `held` row named with
|
|
170
|
+
its `FRONTMATTER_*` code, exit 5, and everything else proceeds. The next sync places it once it is
|
|
171
|
+
repaired on the host. If the checkout's own local store holds a copy it cannot read, sync stops with
|
|
172
|
+
`store_damaged` and sends and places nothing. Reads continue and `status` reports it. Copy out any
|
|
173
|
+
unsent edits and make a fresh checkout, as the error's help describes.
|
|
174
|
+
|
|
130
175
|
## Upgrading from aslite
|
|
131
176
|
|
|
132
177
|
If you installed the earlier `@holaxis/aslite` package or its marketplace plugin: install
|
|
@@ -139,6 +184,8 @@ run `npm uninstall -g @holaxis/aslite`. Existing `.agentstate-lite/` bundles and
|
|
|
139
184
|
The [repository](https://github.com/Holaxis-ai/superbee) holds the source, the
|
|
140
185
|
[CLI contract](https://github.com/Holaxis-ai/superbee/blob/main/packages/superbee/AXI-CONTRACT.md),
|
|
141
186
|
and the [wire protocol](https://github.com/Holaxis-ai/superbee/blob/main/docs/WIRE-PROTOCOL.md).
|
|
187
|
+
Site builders can read a publication's Site declaration through `superbee/publication/site-manifest`;
|
|
188
|
+
see [Site declarations](https://github.com/Holaxis-ai/superbee/blob/main/packages/publication/SITE-MANIFEST.md).
|
|
142
189
|
|
|
143
190
|
## License
|
|
144
191
|
|
package/SKILL.md
CHANGED
|
@@ -74,7 +74,7 @@ files, and stop. Use `superbee <command> --help` for exact current syntax rather
|
|
|
74
74
|
copied command manual. Use `--body-file` for multiline Markdown. `list` / `query` filter metadata, not body text; `doc update` / `doc field` patch metadata while preserving omitted fields and body. For body edits, read the complete body with `doc read <id> --body-out <temp-file>` outside the bundle, edit that temporary file, then `doc update <id> --body-file <temp-file> --expected-version <head_version> --actor <actor>` using the version from the read. Re-read and reconcile on a version conflict. Never feed a truncated preview or a full-document `--out` export to `--body-file`: body input is Markdown only; tooling owns YAML frontmatter. With MCP, use available tools and version preconditions; check tool schemas rather than inventing search or patch verbs.
|
|
75
75
|
|
|
76
76
|
Focused shipped material is available under `$REFS/recipes/` for portable examples,
|
|
77
|
-
`$REFS/views/` for View authoring and examples, and `$REFS/sample-bundle/` for OKF interop. Integration authoring and explicit local source bind/run use `$REFS/author-an-integration.md`; recorded `$REFS/integrations/` examples need no credentials.
|
|
77
|
+
`$REFS/views/` for View authoring and examples, and `$REFS/sample-bundle/` for OKF interop. Integration authoring and explicit local source bind/run use `$REFS/author-an-integration.md`; recorded `$REFS/integrations/` examples need no credentials. `RUNTIME` with `details.frontmatter.code` means a document cannot be read: see "When a document cannot be read" in `$REFS/modeling-and-delivery.md`, which also covers `superbee doc import`.
|
|
78
78
|
Read only what the accepted work requires.
|
|
79
79
|
|
|
80
80
|
## Make the value visible
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BridgeHostKind } from "
|
|
1
|
+
import { type BridgeHostKind } from "./bridge-host-kind.js";
|
|
2
2
|
import { PUBLICATION_BRIDGE_V0, type PublicationBridgeAdmissionV1, type PublicationBridgeV1, type PublicationSnapshotHandleV1 } from "./types.js";
|
|
3
3
|
export interface CreatePublicationBridgeOptionsV1 {
|
|
4
4
|
protocol: typeof PUBLICATION_BRIDGE_V0;
|
|
@@ -59,8 +59,8 @@ var require_common = __commonJS({
|
|
|
59
59
|
return target;
|
|
60
60
|
}
|
|
61
61
|
function repeat(string, count) {
|
|
62
|
-
var result = "",
|
|
63
|
-
for (
|
|
62
|
+
var result = "", cycle2;
|
|
63
|
+
for (cycle2 = 0; cycle2 < count; cycle2 += 1) {
|
|
64
64
|
result += string;
|
|
65
65
|
}
|
|
66
66
|
return result;
|
|
@@ -2881,6 +2881,14 @@ var InvalidInputError = class extends Error {
|
|
|
2881
2881
|
this.name = "InvalidInputError";
|
|
2882
2882
|
}
|
|
2883
2883
|
};
|
|
2884
|
+
var FrontmatterRefusedError = class extends InvalidInputError {
|
|
2885
|
+
code;
|
|
2886
|
+
constructor(code, detail) {
|
|
2887
|
+
super(`frontmatter cannot be stored: ${detail}`);
|
|
2888
|
+
this.name = "FrontmatterRefusedError";
|
|
2889
|
+
this.code = code;
|
|
2890
|
+
}
|
|
2891
|
+
};
|
|
2884
2892
|
var FilesystemIdentityAliasError = class extends InvalidInputError {
|
|
2885
2893
|
rel;
|
|
2886
2894
|
segment;
|
|
@@ -2905,12 +2913,21 @@ var ConcurrentReplacementError = class extends Error {
|
|
|
2905
2913
|
}
|
|
2906
2914
|
};
|
|
2907
2915
|
|
|
2908
|
-
// ../core/src/
|
|
2916
|
+
// ../core/src/bounded-yaml.ts
|
|
2909
2917
|
var import_js_yaml = __toESM(require_js_yaml2(), 1);
|
|
2910
2918
|
|
|
2911
2919
|
// ../core/src/frontmatter-contract.ts
|
|
2920
|
+
var FrontmatterLimitError = class extends Error {
|
|
2921
|
+
name = "FrontmatterLimitError";
|
|
2922
|
+
code;
|
|
2923
|
+
constructor(code, message) {
|
|
2924
|
+
super(message);
|
|
2925
|
+
this.code = code;
|
|
2926
|
+
}
|
|
2927
|
+
};
|
|
2912
2928
|
var MalformedDocumentError = class extends Error {
|
|
2913
2929
|
name = "MalformedDocumentError";
|
|
2930
|
+
code;
|
|
2914
2931
|
context;
|
|
2915
2932
|
detail;
|
|
2916
2933
|
constructor(context, cause) {
|
|
@@ -2918,12 +2935,165 @@ var MalformedDocumentError = class extends Error {
|
|
|
2918
2935
|
super(
|
|
2919
2936
|
`malformed frontmatter${context ? ` in '${context}'` : ""}: ${detail} \u2014 fix the YAML or remove the file`
|
|
2920
2937
|
);
|
|
2938
|
+
this.code = cause instanceof FrontmatterLimitError ? cause.code : "FRONTMATTER_MALFORMED";
|
|
2921
2939
|
if (context !== void 0) this.context = context;
|
|
2922
2940
|
this.detail = detail;
|
|
2923
2941
|
if (cause !== void 0) this.cause = cause;
|
|
2924
2942
|
}
|
|
2925
2943
|
};
|
|
2926
2944
|
|
|
2945
|
+
// ../core/src/bounded-yaml.ts
|
|
2946
|
+
var FRONTMATTER_LIMITS = Object.freeze({
|
|
2947
|
+
sourceChars: 256 * 1024,
|
|
2948
|
+
depth: 32,
|
|
2949
|
+
aliases: 1e3,
|
|
2950
|
+
expandedNodes: 1e5,
|
|
2951
|
+
expandedChars: 1024 * 1024
|
|
2952
|
+
});
|
|
2953
|
+
var PARSE_NESTING_GUARD = FRONTMATTER_LIMITS.depth * 4;
|
|
2954
|
+
function tooDeep() {
|
|
2955
|
+
return new FrontmatterLimitError("FRONTMATTER_TOO_DEEP", `YAML nesting is deeper than ${FRONTMATTER_LIMITS.depth} levels`);
|
|
2956
|
+
}
|
|
2957
|
+
function tooLarge() {
|
|
2958
|
+
return new FrontmatterLimitError(
|
|
2959
|
+
"FRONTMATTER_ALIAS_LIMIT",
|
|
2960
|
+
`YAML aliases expand to more than ${FRONTMATTER_LIMITS.expandedNodes} nodes or ${FRONTMATTER_LIMITS.expandedChars} characters`
|
|
2961
|
+
);
|
|
2962
|
+
}
|
|
2963
|
+
function cycle() {
|
|
2964
|
+
return new FrontmatterLimitError("FRONTMATTER_CYCLE", "YAML aliases form a cycle");
|
|
2965
|
+
}
|
|
2966
|
+
function scalarExtent(value) {
|
|
2967
|
+
const chars = typeof value === "string" ? value.length : ArrayBuffer.isView(value) ? value.byteLength : 0;
|
|
2968
|
+
return { nodes: 1, chars, depth: 0 };
|
|
2969
|
+
}
|
|
2970
|
+
function isCollection(value) {
|
|
2971
|
+
if (Array.isArray(value)) return true;
|
|
2972
|
+
if (value === null || typeof value !== "object") return false;
|
|
2973
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2974
|
+
return prototype === Object.prototype || prototype === null;
|
|
2975
|
+
}
|
|
2976
|
+
var ExtentMeter = class {
|
|
2977
|
+
known = /* @__PURE__ */ new Map();
|
|
2978
|
+
path = /* @__PURE__ */ new Set();
|
|
2979
|
+
unfinished;
|
|
2980
|
+
constructor(unfinished = /* @__PURE__ */ new Set()) {
|
|
2981
|
+
this.unfinished = unfinished;
|
|
2982
|
+
}
|
|
2983
|
+
measure(value) {
|
|
2984
|
+
if (!isCollection(value)) return scalarExtent(value);
|
|
2985
|
+
const known = this.known.get(value);
|
|
2986
|
+
if (known !== void 0) return known;
|
|
2987
|
+
if (this.path.has(value) || this.unfinished.has(value)) throw cycle();
|
|
2988
|
+
return this.walk(value);
|
|
2989
|
+
}
|
|
2990
|
+
walk(value) {
|
|
2991
|
+
if (this.path.size >= FRONTMATTER_LIMITS.depth) throw tooDeep();
|
|
2992
|
+
this.path.add(value);
|
|
2993
|
+
const keyed = !Array.isArray(value);
|
|
2994
|
+
const extent = { nodes: 1, chars: 0, depth: 1 };
|
|
2995
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2996
|
+
const inner = this.measure(child);
|
|
2997
|
+
extent.nodes += inner.nodes;
|
|
2998
|
+
extent.chars += inner.chars + (keyed ? key.length : 0);
|
|
2999
|
+
extent.depth = Math.max(extent.depth, inner.depth + 1);
|
|
3000
|
+
if (extent.nodes > FRONTMATTER_LIMITS.expandedNodes || extent.chars > FRONTMATTER_LIMITS.expandedChars) throw tooLarge();
|
|
3001
|
+
if (extent.depth > FRONTMATTER_LIMITS.depth) throw tooDeep();
|
|
3002
|
+
}
|
|
3003
|
+
this.path.delete(value);
|
|
3004
|
+
this.known.set(value, extent);
|
|
3005
|
+
return extent;
|
|
3006
|
+
}
|
|
3007
|
+
};
|
|
3008
|
+
var AliasBudget = class {
|
|
3009
|
+
unfinished = /* @__PURE__ */ new Set();
|
|
3010
|
+
finished = /* @__PURE__ */ new Set();
|
|
3011
|
+
meter = new ExtentMeter(this.unfinished);
|
|
3012
|
+
aliases = 0;
|
|
3013
|
+
nodes = 0;
|
|
3014
|
+
chars = 0;
|
|
3015
|
+
/** Wrap the parser's anchor table; the parser reads it only to resolve an alias. */
|
|
3016
|
+
watch(state) {
|
|
3017
|
+
state.anchorMap = new Proxy(state.anchorMap, {
|
|
3018
|
+
get: (target, name, receiver) => {
|
|
3019
|
+
const value = Reflect.get(target, name, receiver);
|
|
3020
|
+
if (typeof name === "string") this.charge(name, value);
|
|
3021
|
+
return value;
|
|
3022
|
+
},
|
|
3023
|
+
set: (target, name, value, receiver) => {
|
|
3024
|
+
if (typeof name === "string") {
|
|
3025
|
+
this.finished.delete(name);
|
|
3026
|
+
if (isCollection(value)) this.unfinished.add(value);
|
|
3027
|
+
}
|
|
3028
|
+
return Reflect.set(target, name, value, receiver);
|
|
3029
|
+
}
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
/** Called when an anchored node closes: its anchor now names a complete value. */
|
|
3033
|
+
close(anchor, value) {
|
|
3034
|
+
this.finished.add(anchor);
|
|
3035
|
+
if (isCollection(value)) this.unfinished.delete(value);
|
|
3036
|
+
}
|
|
3037
|
+
charge(name, value) {
|
|
3038
|
+
this.aliases += 1;
|
|
3039
|
+
if (this.aliases > FRONTMATTER_LIMITS.aliases) {
|
|
3040
|
+
throw new FrontmatterLimitError(
|
|
3041
|
+
"FRONTMATTER_ALIAS_LIMIT",
|
|
3042
|
+
`YAML aliases are used more than ${FRONTMATTER_LIMITS.aliases} times`
|
|
3043
|
+
);
|
|
3044
|
+
}
|
|
3045
|
+
if (!this.finished.has(name) && isCollection(value)) throw cycle();
|
|
3046
|
+
const extent = this.meter.measure(value);
|
|
3047
|
+
this.nodes += extent.nodes;
|
|
3048
|
+
this.chars += extent.chars;
|
|
3049
|
+
if (this.nodes > FRONTMATTER_LIMITS.expandedNodes || this.chars > FRONTMATTER_LIMITS.expandedChars) throw tooLarge();
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3052
|
+
var MERGE_KEY_LIMIT_REASON = "merge keys exceeded maxTotalMergeKeys";
|
|
3053
|
+
function loadBoundedYaml(source, options = {}) {
|
|
3054
|
+
if (source.length > FRONTMATTER_LIMITS.sourceChars) {
|
|
3055
|
+
throw new FrontmatterLimitError(
|
|
3056
|
+
"FRONTMATTER_TOO_LARGE",
|
|
3057
|
+
`YAML source is longer than ${FRONTMATTER_LIMITS.sourceChars} characters`
|
|
3058
|
+
);
|
|
3059
|
+
}
|
|
3060
|
+
const schema = options.schema === "json" ? import_js_yaml.default.JSON_SCHEMA : options.schema;
|
|
3061
|
+
const budget = new AliasBudget();
|
|
3062
|
+
let watched;
|
|
3063
|
+
let nesting = 0;
|
|
3064
|
+
let value;
|
|
3065
|
+
try {
|
|
3066
|
+
value = import_js_yaml.default.safeLoad(source, {
|
|
3067
|
+
...schema ? { schema } : {},
|
|
3068
|
+
listener(event, rawState) {
|
|
3069
|
+
const state = rawState;
|
|
3070
|
+
if (state.anchorMap !== watched) {
|
|
3071
|
+
budget.watch(state);
|
|
3072
|
+
watched = state.anchorMap;
|
|
3073
|
+
}
|
|
3074
|
+
if (event === "open") {
|
|
3075
|
+
nesting += 1;
|
|
3076
|
+
if (nesting > PARSE_NESTING_GUARD) throw tooDeep();
|
|
3077
|
+
} else {
|
|
3078
|
+
nesting -= 1;
|
|
3079
|
+
if (state.anchor !== null) budget.close(state.anchor, state.result);
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
});
|
|
3083
|
+
} catch (error) {
|
|
3084
|
+
const reason = error.reason;
|
|
3085
|
+
if (typeof reason === "string" && reason.startsWith(MERGE_KEY_LIMIT_REASON)) {
|
|
3086
|
+
throw new FrontmatterLimitError("FRONTMATTER_ALIAS_LIMIT", reason);
|
|
3087
|
+
}
|
|
3088
|
+
throw error;
|
|
3089
|
+
}
|
|
3090
|
+
new ExtentMeter().measure(value);
|
|
3091
|
+
return value;
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
// ../core/src/frontmatter.ts
|
|
3095
|
+
var import_js_yaml2 = __toESM(require_js_yaml2(), 1);
|
|
3096
|
+
|
|
2927
3097
|
// ../core/src/frontmatter-splitter.ts
|
|
2928
3098
|
function delimiterLineEnd(input, offset) {
|
|
2929
3099
|
if (!input.startsWith("---", offset)) return null;
|
|
@@ -2953,12 +3123,106 @@ function splitLeadingFrontmatter(raw, context) {
|
|
|
2953
3123
|
throw new MalformedDocumentError(context, new Error("unterminated YAML frontmatter delimiter"));
|
|
2954
3124
|
}
|
|
2955
3125
|
|
|
3126
|
+
// ../core/src/frontmatter.ts
|
|
3127
|
+
var YAML_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp";
|
|
3128
|
+
function requireYamlTimestampType() {
|
|
3129
|
+
const type = import_js_yaml2.default.DEFAULT_SAFE_SCHEMA.implicit.find(
|
|
3130
|
+
(candidate) => candidate.tag === YAML_TIMESTAMP_TAG
|
|
3131
|
+
);
|
|
3132
|
+
if (!type) throw new Error("js-yaml safe schema is missing its timestamp type");
|
|
3133
|
+
return type;
|
|
3134
|
+
}
|
|
3135
|
+
var yamlTimestampType = requireYamlTimestampType();
|
|
3136
|
+
var stringTimestampType = new import_js_yaml2.default.Type(YAML_TIMESTAMP_TAG, {
|
|
3137
|
+
kind: "scalar",
|
|
3138
|
+
resolve: (value) => yamlTimestampType.resolve(value),
|
|
3139
|
+
construct: (value) => value
|
|
3140
|
+
});
|
|
3141
|
+
var losslessDateSchema = new import_js_yaml2.default.Schema({
|
|
3142
|
+
include: [import_js_yaml2.default.DEFAULT_SAFE_SCHEMA],
|
|
3143
|
+
implicit: [stringTimestampType]
|
|
3144
|
+
});
|
|
3145
|
+
var yamlEngine = {
|
|
3146
|
+
parse(input) {
|
|
3147
|
+
return loadBoundedYaml(input, { schema: losslessDateSchema }) ?? {};
|
|
3148
|
+
}
|
|
3149
|
+
};
|
|
3150
|
+
function normalizeFrontmatter(data, options) {
|
|
3151
|
+
const out = { ...data };
|
|
3152
|
+
if (options.preserveTimestamp || options.okfVersion === "0.2") return out;
|
|
3153
|
+
const value = out.timestamp;
|
|
3154
|
+
if (value instanceof Date) {
|
|
3155
|
+
out.timestamp = value.toISOString();
|
|
3156
|
+
} else if (typeof value === "number" && Number.isFinite(value)) {
|
|
3157
|
+
out.timestamp = new Date(value).toISOString();
|
|
3158
|
+
} else if (typeof value === "string" && yamlTimestampType.resolve(value)) {
|
|
3159
|
+
const parsed = yamlTimestampType.construct(value);
|
|
3160
|
+
if (parsed instanceof Date) out.timestamp = parsed.toISOString();
|
|
3161
|
+
}
|
|
3162
|
+
return out;
|
|
3163
|
+
}
|
|
3164
|
+
function parseMarkdown(raw, context, options = {}) {
|
|
3165
|
+
const split = splitLeadingFrontmatter(raw, context);
|
|
3166
|
+
if (!("yamlSource" in split)) return { frontmatter: {}, body: split.body };
|
|
3167
|
+
return { frontmatter: parseFrontmatter(split.yamlSource, context, options), body: split.body };
|
|
3168
|
+
}
|
|
3169
|
+
function parseFrontmatter(source, context, options = {}) {
|
|
3170
|
+
let parsed;
|
|
3171
|
+
try {
|
|
3172
|
+
parsed = yamlEngine.parse(source);
|
|
3173
|
+
} catch (err) {
|
|
3174
|
+
throw new MalformedDocumentError(context, err);
|
|
3175
|
+
}
|
|
3176
|
+
return normalizeFrontmatter(parsed, options);
|
|
3177
|
+
}
|
|
3178
|
+
function isStackExhaustion(error) {
|
|
3179
|
+
if (error instanceof RangeError) return /call stack/i.test(error.message);
|
|
3180
|
+
return error?.name === "InternalError" && /recursion/i.test(error.message);
|
|
3181
|
+
}
|
|
3182
|
+
function stringifyFrontmatter(data) {
|
|
3183
|
+
let dumped;
|
|
3184
|
+
try {
|
|
3185
|
+
dumped = import_js_yaml2.default.safeDump(data).trim();
|
|
3186
|
+
} catch (error) {
|
|
3187
|
+
if (isStackExhaustion(error)) throw new FrontmatterRefusedError("FRONTMATTER_TOO_DEEP", `YAML nesting is deeper than ${FRONTMATTER_LIMITS.depth} levels`);
|
|
3188
|
+
throw error;
|
|
3189
|
+
}
|
|
3190
|
+
try {
|
|
3191
|
+
yamlEngine.parse(`${dumped}
|
|
3192
|
+
`);
|
|
3193
|
+
} catch (error) {
|
|
3194
|
+
if (error instanceof FrontmatterLimitError) throw new FrontmatterRefusedError(error.code, error.message);
|
|
3195
|
+
const detail = (error instanceof Error ? error.message : String(error)).split("\n")[0] ?? "";
|
|
3196
|
+
throw new FrontmatterRefusedError("FRONTMATTER_MALFORMED", detail);
|
|
3197
|
+
}
|
|
3198
|
+
return dumped;
|
|
3199
|
+
}
|
|
3200
|
+
function normalizeDocumentBodyForStorage(body) {
|
|
3201
|
+
return body.endsWith("\n") ? body : `${body}
|
|
3202
|
+
`;
|
|
3203
|
+
}
|
|
3204
|
+
function stringifyWithData(data, body) {
|
|
3205
|
+
return stringifyWithSerializedFrontmatter(stringifyFrontmatter(data), body);
|
|
3206
|
+
}
|
|
3207
|
+
function stringifyWithSerializedFrontmatter(dumped, body) {
|
|
3208
|
+
const content = body ?? "";
|
|
3209
|
+
const newline = (value) => value.endsWith("\n") ? value : `${value}
|
|
3210
|
+
`;
|
|
3211
|
+
if (dumped === "{}") return normalizeDocumentBodyForStorage(content);
|
|
3212
|
+
return `---
|
|
3213
|
+
${newline(dumped)}---
|
|
3214
|
+
${normalizeDocumentBodyForStorage(content)}`;
|
|
3215
|
+
}
|
|
3216
|
+
function stringifyDoc(frontmatter2, body) {
|
|
3217
|
+
return stringifyWithData(frontmatter2, body);
|
|
3218
|
+
}
|
|
3219
|
+
|
|
2956
3220
|
// ../core/src/portable-frontmatter.ts
|
|
2957
3221
|
function parseLeadingFrontmatter(raw, context) {
|
|
2958
3222
|
const split = splitLeadingFrontmatter(raw, context);
|
|
2959
3223
|
if (!("yamlSource" in split)) return {};
|
|
2960
3224
|
try {
|
|
2961
|
-
const parsed =
|
|
3225
|
+
const parsed = loadBoundedYaml(split.yamlSource);
|
|
2962
3226
|
if (parsed === void 0 || parsed === null) return {};
|
|
2963
3227
|
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2964
3228
|
throw new TypeError("YAML frontmatter must be a mapping");
|
|
@@ -2969,6 +3233,18 @@ function parseLeadingFrontmatter(raw, context) {
|
|
|
2969
3233
|
throw new MalformedDocumentError(context, error);
|
|
2970
3234
|
}
|
|
2971
3235
|
}
|
|
3236
|
+
function assertStorableFrontmatter(raw) {
|
|
3237
|
+
try {
|
|
3238
|
+
parseLeadingFrontmatter(raw);
|
|
3239
|
+
const split = splitLeadingFrontmatter(raw);
|
|
3240
|
+
if ("yamlSource" in split) parseFrontmatter(split.yamlSource);
|
|
3241
|
+
} catch (error) {
|
|
3242
|
+
if (error instanceof MalformedDocumentError && error.code !== "FRONTMATTER_MALFORMED") {
|
|
3243
|
+
throw new FrontmatterRefusedError(error.code, error.detail);
|
|
3244
|
+
}
|
|
3245
|
+
if (!(error instanceof MalformedDocumentError)) throw error;
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
2972
3248
|
|
|
2973
3249
|
// ../core/src/paths.ts
|
|
2974
3250
|
var RESERVED_FILENAMES = ["index.md", "log.md"];
|
|
@@ -3321,82 +3597,6 @@ function inventoryDigest(inventory) {
|
|
|
3321
3597
|
return `sha256:${sha256HexOfUtf8(JSON.stringify(inventory.map((row) => [row.id, row.version])))}`;
|
|
3322
3598
|
}
|
|
3323
3599
|
|
|
3324
|
-
// ../core/src/frontmatter.ts
|
|
3325
|
-
var import_js_yaml2 = __toESM(require_js_yaml2(), 1);
|
|
3326
|
-
var YAML_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp";
|
|
3327
|
-
function requireYamlTimestampType() {
|
|
3328
|
-
const type = import_js_yaml2.default.DEFAULT_SAFE_SCHEMA.implicit.find(
|
|
3329
|
-
(candidate) => candidate.tag === YAML_TIMESTAMP_TAG
|
|
3330
|
-
);
|
|
3331
|
-
if (!type) throw new Error("js-yaml safe schema is missing its timestamp type");
|
|
3332
|
-
return type;
|
|
3333
|
-
}
|
|
3334
|
-
var yamlTimestampType = requireYamlTimestampType();
|
|
3335
|
-
var stringTimestampType = new import_js_yaml2.default.Type(YAML_TIMESTAMP_TAG, {
|
|
3336
|
-
kind: "scalar",
|
|
3337
|
-
resolve: (value) => yamlTimestampType.resolve(value),
|
|
3338
|
-
construct: (value) => value
|
|
3339
|
-
});
|
|
3340
|
-
var losslessDateSchema = new import_js_yaml2.default.Schema({
|
|
3341
|
-
include: [import_js_yaml2.default.DEFAULT_SAFE_SCHEMA],
|
|
3342
|
-
implicit: [stringTimestampType]
|
|
3343
|
-
});
|
|
3344
|
-
var yamlEngine = {
|
|
3345
|
-
parse(input) {
|
|
3346
|
-
return import_js_yaml2.default.safeLoad(input, { schema: losslessDateSchema }) ?? {};
|
|
3347
|
-
}
|
|
3348
|
-
};
|
|
3349
|
-
function normalizeFrontmatter(data, options) {
|
|
3350
|
-
const out = { ...data };
|
|
3351
|
-
if (options.preserveTimestamp || options.okfVersion === "0.2") return out;
|
|
3352
|
-
const value = out.timestamp;
|
|
3353
|
-
if (value instanceof Date) {
|
|
3354
|
-
out.timestamp = value.toISOString();
|
|
3355
|
-
} else if (typeof value === "number" && Number.isFinite(value)) {
|
|
3356
|
-
out.timestamp = new Date(value).toISOString();
|
|
3357
|
-
} else if (typeof value === "string" && yamlTimestampType.resolve(value)) {
|
|
3358
|
-
const parsed = yamlTimestampType.construct(value);
|
|
3359
|
-
if (parsed instanceof Date) out.timestamp = parsed.toISOString();
|
|
3360
|
-
}
|
|
3361
|
-
return out;
|
|
3362
|
-
}
|
|
3363
|
-
function parseMarkdown(raw, context, options = {}) {
|
|
3364
|
-
const split = splitLeadingFrontmatter(raw, context);
|
|
3365
|
-
if (!("yamlSource" in split)) return { frontmatter: {}, body: split.body };
|
|
3366
|
-
return { frontmatter: parseFrontmatter(split.yamlSource, context, options), body: split.body };
|
|
3367
|
-
}
|
|
3368
|
-
function parseFrontmatter(source, context, options = {}) {
|
|
3369
|
-
let parsed;
|
|
3370
|
-
try {
|
|
3371
|
-
parsed = yamlEngine.parse(source);
|
|
3372
|
-
} catch (err) {
|
|
3373
|
-
throw new MalformedDocumentError(context, err);
|
|
3374
|
-
}
|
|
3375
|
-
return normalizeFrontmatter(parsed, options);
|
|
3376
|
-
}
|
|
3377
|
-
function stringifyFrontmatter(data) {
|
|
3378
|
-
return import_js_yaml2.default.safeDump(data).trim();
|
|
3379
|
-
}
|
|
3380
|
-
function normalizeDocumentBodyForStorage(body) {
|
|
3381
|
-
return body.endsWith("\n") ? body : `${body}
|
|
3382
|
-
`;
|
|
3383
|
-
}
|
|
3384
|
-
function stringifyWithData(data, body) {
|
|
3385
|
-
return stringifyWithSerializedFrontmatter(stringifyFrontmatter(data), body);
|
|
3386
|
-
}
|
|
3387
|
-
function stringifyWithSerializedFrontmatter(dumped, body) {
|
|
3388
|
-
const content = body ?? "";
|
|
3389
|
-
const newline = (value) => value.endsWith("\n") ? value : `${value}
|
|
3390
|
-
`;
|
|
3391
|
-
if (dumped === "{}") return normalizeDocumentBodyForStorage(content);
|
|
3392
|
-
return `---
|
|
3393
|
-
${newline(dumped)}---
|
|
3394
|
-
${normalizeDocumentBodyForStorage(content)}`;
|
|
3395
|
-
}
|
|
3396
|
-
function stringifyDoc(frontmatter2, body) {
|
|
3397
|
-
return stringifyWithData(frontmatter2, body);
|
|
3398
|
-
}
|
|
3399
|
-
|
|
3400
3600
|
// ../core/src/code-unit-order.ts
|
|
3401
3601
|
function compareCodeUnits(a, b) {
|
|
3402
3602
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
@@ -3808,24 +4008,7 @@ function hasSourceIdentity(sources, identity) {
|
|
|
3808
4008
|
return Array.isArray(sources) && sources.some((entry) => matchesSourceIdentity(entry, identity));
|
|
3809
4009
|
}
|
|
3810
4010
|
|
|
3811
|
-
// ../core/src/
|
|
3812
|
-
async function readBundleOkfVersion(backend) {
|
|
3813
|
-
const index = await backend.readReserved("", "index.md");
|
|
3814
|
-
if (!index) return void 0;
|
|
3815
|
-
const value = parseLeadingFrontmatter(index.content, "index.md").okf_version;
|
|
3816
|
-
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
3817
|
-
}
|
|
3818
|
-
function assertReadableConceptId(id) {
|
|
3819
|
-
assertSafeConceptId(id);
|
|
3820
|
-
const rel = pathFromConceptId(id);
|
|
3821
|
-
if (isReservedFile(rel)) {
|
|
3822
|
-
throw new InvalidInputError(`'${id}' is a reserved file (index.md / log.md), not a concept document.`);
|
|
3823
|
-
}
|
|
3824
|
-
}
|
|
3825
|
-
async function readDocVersioned(backend, id) {
|
|
3826
|
-
assertReadableConceptId(id);
|
|
3827
|
-
return backend.read(id);
|
|
3828
|
-
}
|
|
4011
|
+
// ../core/src/document-scan.ts
|
|
3829
4012
|
function isEnoent(err) {
|
|
3830
4013
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
3831
4014
|
}
|
|
@@ -3856,6 +4039,25 @@ async function readManyExisting(backend, ids, onMalformed, onAliased) {
|
|
|
3856
4039
|
return out;
|
|
3857
4040
|
}
|
|
3858
4041
|
}
|
|
4042
|
+
|
|
4043
|
+
// ../core/src/engine.ts
|
|
4044
|
+
async function readBundleOkfVersion(backend) {
|
|
4045
|
+
const index = await backend.readReserved("", "index.md");
|
|
4046
|
+
if (!index) return void 0;
|
|
4047
|
+
const value = parseLeadingFrontmatter(index.content, "index.md").okf_version;
|
|
4048
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
4049
|
+
}
|
|
4050
|
+
function assertReadableConceptId(id) {
|
|
4051
|
+
assertSafeConceptId(id);
|
|
4052
|
+
const rel = pathFromConceptId(id);
|
|
4053
|
+
if (isReservedFile(rel)) {
|
|
4054
|
+
throw new InvalidInputError(`'${id}' is a reserved file (index.md / log.md), not a concept document.`);
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
async function readDocVersioned(backend, id) {
|
|
4058
|
+
assertReadableConceptId(id);
|
|
4059
|
+
return backend.read(id);
|
|
4060
|
+
}
|
|
3859
4061
|
async function scanMatching(backend, filter, onSkip) {
|
|
3860
4062
|
const ids = await backend.list(filter.prefix);
|
|
3861
4063
|
const results = [];
|
|
@@ -3974,6 +4176,13 @@ function resolveContentType(key, override) {
|
|
|
3974
4176
|
return inferContentTypeFromDocKey(key) ?? DEFAULT_BLOB_CONTENT_TYPE;
|
|
3975
4177
|
}
|
|
3976
4178
|
|
|
4179
|
+
// ../core/src/reserved-write.ts
|
|
4180
|
+
function assertStorableReservedWrite(dir, name, content) {
|
|
4181
|
+
assertSafeReservedDir(dir);
|
|
4182
|
+
assertSafeReservedFilename(name);
|
|
4183
|
+
if (name.endsWith(".md")) assertStorableFrontmatter(content);
|
|
4184
|
+
}
|
|
4185
|
+
|
|
3977
4186
|
// ../core/src/filesystem-identity.ts
|
|
3978
4187
|
import { promises as fs2 } from "node:fs";
|
|
3979
4188
|
import path2 from "node:path";
|
|
@@ -5328,8 +5537,7 @@ var FilesystemBackend = class _FilesystemBackend {
|
|
|
5328
5537
|
return { content, version: versionOfBytes(content) };
|
|
5329
5538
|
}
|
|
5330
5539
|
async writeReserved(dir, name, content, options = {}) {
|
|
5331
|
-
|
|
5332
|
-
assertSafeReservedFilename(name);
|
|
5540
|
+
assertStorableReservedWrite(dir, name, content);
|
|
5333
5541
|
const rel = reservedPath(dir, name);
|
|
5334
5542
|
return this.#mutateExact(rel, async (target) => {
|
|
5335
5543
|
if (options.expectedVersion !== void 0) {
|