melchizedek-agents 0.9.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/AGENT_SETUP.md +182 -0
- package/DOCUMENTATION.md +614 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +164 -0
- package/README.md +151 -0
- package/config/agents/README.md +27 -0
- package/config/agents/examples/ares.yaml +74 -0
- package/config/agents/examples/cartographers.yaml +115 -0
- package/config/agents/examples/claude.yaml +17 -0
- package/config/agents/examples/council.yaml +65 -0
- package/config/agents/examples/critic.yaml +74 -0
- package/config/agents/examples/delegation.yaml +29 -0
- package/config/agents/examples/hierarchical.yaml +26 -0
- package/config/agents/examples/image_production.yaml +112 -0
- package/config/agents/examples/librarian.yaml +75 -0
- package/config/agents/examples/model_zoo.yaml +81 -0
- package/config/agents/examples/patient_advocate.yaml +107 -0
- package/config/agents/examples/scriptorium.yaml +96 -0
- package/config/agents/examples/style_council.yaml +83 -0
- package/config/agents/examples/syndicate.yaml +34 -0
- package/config/agents/examples/tutor.yaml +66 -0
- package/config/agents/syndicateSchema.yaml +291 -0
- package/dist/lib/config.d.ts +108 -0
- package/dist/lib/config.js +110 -0
- package/dist/lib/dispatch.d.ts +133 -0
- package/dist/lib/dispatch.js +182 -0
- package/dist/lib/grounding.d.ts +28 -0
- package/dist/lib/grounding.js +61 -0
- package/dist/lib/index.d.ts +14 -0
- package/dist/lib/index.js +13 -0
- package/dist/lib/loadEnv.d.ts +21 -0
- package/dist/lib/loadEnv.js +58 -0
- package/dist/lib/loadSyndicate.d.ts +177 -0
- package/dist/lib/loadSyndicate.js +264 -0
- package/dist/lib/memory/supabaseMemoryService.d.ts +118 -0
- package/dist/lib/memory/supabaseMemoryService.js +499 -0
- package/dist/lib/models/claudeLlm.d.ts +93 -0
- package/dist/lib/models/claudeLlm.js +332 -0
- package/dist/lib/models/gptLlm.d.ts +110 -0
- package/dist/lib/models/gptLlm.js +497 -0
- package/dist/lib/models/grokLlm.d.ts +64 -0
- package/dist/lib/models/grokLlm.js +86 -0
- package/dist/lib/models/ollamaLlm.d.ts +105 -0
- package/dist/lib/models/ollamaLlm.js +134 -0
- package/dist/lib/models/openAiCompatibleLlm.d.ts +125 -0
- package/dist/lib/models/openAiCompatibleLlm.js +331 -0
- package/dist/lib/models/providerMap.d.ts +28 -0
- package/dist/lib/models/providerMap.js +45 -0
- package/dist/lib/models/registry.d.ts +71 -0
- package/dist/lib/models/registry.js +159 -0
- package/dist/lib/models/schemaNormalize.d.ts +22 -0
- package/dist/lib/models/schemaNormalize.js +58 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +35 -0
- package/dist/lib/observability/supabaseSpanExporter.js +115 -0
- package/dist/lib/observability/tracer.d.ts +44 -0
- package/dist/lib/observability/tracer.js +242 -0
- package/dist/lib/persistence/supabaseProvider.d.ts +44 -0
- package/dist/lib/persistence/supabaseProvider.js +88 -0
- package/dist/lib/session/supabaseSessionService.d.ts +26 -0
- package/dist/lib/session/supabaseSessionService.js +193 -0
- package/dist/lib/session/transcript.d.ts +193 -0
- package/dist/lib/session/transcript.js +309 -0
- package/dist/lib/toolRegistry.d.ts +15 -0
- package/dist/lib/toolRegistry.js +58 -0
- package/dist/lib/tools/collectionsSearchTool.d.ts +55 -0
- package/dist/lib/tools/collectionsSearchTool.js +86 -0
- package/dist/lib/tools/generateImageTool.d.ts +16 -0
- package/dist/lib/tools/generateImageTool.js +119 -0
- package/dist/lib/tools/inspectImageTool.d.ts +21 -0
- package/dist/lib/tools/inspectImageTool.js +97 -0
- package/dist/lib/tools/mcpToolFactory.d.ts +2 -0
- package/dist/lib/tools/mcpToolFactory.js +116 -0
- package/dist/lib/tools/toolContract.d.ts +78 -0
- package/dist/lib/tools/toolContract.js +129 -0
- package/dist/lib/tools/webExtractTool.d.ts +70 -0
- package/dist/lib/tools/webExtractTool.js +461 -0
- package/dist/lib/tools/webSearchTool.d.ts +53 -0
- package/dist/lib/tools/webSearchTool.js +126 -0
- package/dist/lib/tools/wikiTools.d.ts +112 -0
- package/dist/lib/tools/wikiTools.js +697 -0
- package/dist/lib/tools/xSearchTool.d.ts +61 -0
- package/dist/lib/tools/xSearchTool.js +125 -0
- package/dist/lib/wiki/agentRun.d.ts +39 -0
- package/dist/lib/wiki/agentRun.js +79 -0
- package/dist/lib/wiki/builder.d.ts +71 -0
- package/dist/lib/wiki/builder.js +165 -0
- package/dist/lib/wiki/dive.d.ts +56 -0
- package/dist/lib/wiki/dive.js +208 -0
- package/dist/lib/wiki/entities.d.ts +220 -0
- package/dist/lib/wiki/entities.js +661 -0
- package/dist/lib/wiki/extract.d.ts +97 -0
- package/dist/lib/wiki/extract.js +305 -0
- package/dist/lib/wiki/fill.d.ts +37 -0
- package/dist/lib/wiki/fill.js +175 -0
- package/dist/lib/wiki/format.d.ts +132 -0
- package/dist/lib/wiki/format.js +177 -0
- package/dist/lib/wiki/graph.d.ts +80 -0
- package/dist/lib/wiki/graph.js +147 -0
- package/dist/lib/wiki/lint.d.ts +58 -0
- package/dist/lib/wiki/lint.js +267 -0
- package/dist/lib/wiki/markdown.d.ts +127 -0
- package/dist/lib/wiki/markdown.js +336 -0
- package/dist/lib/wiki/pipeline.d.ts +69 -0
- package/dist/lib/wiki/pipeline.js +119 -0
- package/dist/lib/wiki/vault.d.ts +84 -0
- package/dist/lib/wiki/vault.js +194 -0
- package/dist/scripts/a2a_server.d.ts +2 -0
- package/dist/scripts/a2a_server.js +997 -0
- package/dist/scripts/syndicate_chat.d.ts +2 -0
- package/dist/scripts/syndicate_chat.js +485 -0
- package/package.json +96 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/wiki/format.ts — the Open Knowledge Format (OKF) v0.2 profile.
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists:
|
|
5
|
+
* The wiki is a directory of markdown "concept" documents with YAML
|
|
6
|
+
* frontmatter, per the OKF v0.2 spec
|
|
7
|
+
* (github.com/GoogleCloudPlatform/knowledge-catalog, okf/SPEC.md). The spec
|
|
8
|
+
* is deliberately minimal — `type` is the only required key — so every
|
|
9
|
+
* producer needs a PROFILE: which reserved fields it writes, which concept
|
|
10
|
+
* types it recognises, and how actors are named. This file is that profile,
|
|
11
|
+
* expressed as zod schemas so the same object validates frontmatter at
|
|
12
|
+
* parse time and documents the format in one place.
|
|
13
|
+
*
|
|
14
|
+
* Spec facts this module encodes (OKF v0.2):
|
|
15
|
+
* - `type` is the only always-required frontmatter key (§4.1).
|
|
16
|
+
* - `index.md` and `log.md` are RESERVED filenames, not concepts (§3.1);
|
|
17
|
+
* reserved files are exempt from the `type` requirement (§11).
|
|
18
|
+
* - Links are normal markdown links; bundle-absolute (`/dir/doc.md`)
|
|
19
|
+
* is the recommended form (§6.1). Consumers MUST tolerate broken
|
|
20
|
+
* links — but a producer should still lint them (lib/wiki/lint.ts).
|
|
21
|
+
* - Trust/provenance family (§5): `generated {by, at}`, `verified`
|
|
22
|
+
* (mapping or list), `sources[]`, `status: draft|stable|deprecated`
|
|
23
|
+
* (absent = stable), `stale_after: YYYY-MM-DD`.
|
|
24
|
+
* - Actor convention (§7): `<producer>/<version>` for agents,
|
|
25
|
+
* `human:<id>` for persons, `process:<id>` for automated processes.
|
|
26
|
+
* Trust tiers (§5.3) key off the `human:` prefix.
|
|
27
|
+
* - `okf_version` may appear in the bundle-root index.md only (§12).
|
|
28
|
+
*/
|
|
29
|
+
import { z } from 'zod';
|
|
30
|
+
/** OKF spec version this profile targets. Written to the root index.md. */
|
|
31
|
+
export declare const OKF_VERSION = "0.2";
|
|
32
|
+
/** Reserved filenames (§3.1) — never concept documents. */
|
|
33
|
+
export declare const RESERVED_FILENAMES: readonly ["index.md", "log.md"];
|
|
34
|
+
export declare function isReservedFilename(fileName: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The subtree that never leaves this repo. Path-based visibility: a doc is
|
|
37
|
+
* private iff its bundle path starts with this prefix. The export pipeline
|
|
38
|
+
* copies the bundle WITHOUT this subtree, and lint's closure rule forbids
|
|
39
|
+
* docs outside it from linking into it — so the published bundle is
|
|
40
|
+
* link-closed by construction (see wiki/decisions/0003.)
|
|
41
|
+
*/
|
|
42
|
+
export declare const PRIVATE_PREFIX = "/private/";
|
|
43
|
+
export declare function isPrivatePath(bundlePath: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Recommended `type` values for this bundle. OKF requires consumers to
|
|
46
|
+
* tolerate unknown types (§11), so this list steers authors without
|
|
47
|
+
* rejecting anything — lint reports an unknown type as INFO, not an error.
|
|
48
|
+
*/
|
|
49
|
+
export declare const CONCEPT_TYPES: readonly ["overview", "subsystem", "syndicate", "tool", "model-provider", "schema", "protocol", "runbook", "guide", "decision", "doctrine", "reference", "meta"];
|
|
50
|
+
export type ConceptType = (typeof CONCEPT_TYPES)[number];
|
|
51
|
+
/** `human:<id>` | `process:<id>` | `<producer>/<version>` */
|
|
52
|
+
export declare const actorSchema: z.ZodString;
|
|
53
|
+
/** Actor id for structural (non-LLM) generation by this module. */
|
|
54
|
+
export declare const BUILD_ACTOR = "process:wiki-build";
|
|
55
|
+
/** Actor id for LLM gap-fill: the producer is the framework, version the model. */
|
|
56
|
+
export declare function llmActor(modelId: string): string;
|
|
57
|
+
/** Trust tiers derived from `verified` (§5.3). */
|
|
58
|
+
export type TrustTier = 'unverified' | 'machine-confirmed' | 'human-reviewed';
|
|
59
|
+
export declare const generatedSchema: z.ZodObject<{
|
|
60
|
+
by: z.ZodString;
|
|
61
|
+
at: z.ZodOptional<z.ZodString>;
|
|
62
|
+
}, z.core.$loose>;
|
|
63
|
+
export declare const verifiedEntrySchema: z.ZodObject<{
|
|
64
|
+
by: z.ZodString;
|
|
65
|
+
at: z.ZodOptional<z.ZodString>;
|
|
66
|
+
}, z.core.$loose>;
|
|
67
|
+
export declare const sourceSchema: z.ZodObject<{
|
|
68
|
+
id: z.ZodOptional<z.ZodString>;
|
|
69
|
+
resource: z.ZodString;
|
|
70
|
+
title: z.ZodOptional<z.ZodString>;
|
|
71
|
+
author: z.ZodOptional<z.ZodString>;
|
|
72
|
+
last_modified: z.ZodOptional<z.ZodString>;
|
|
73
|
+
}, z.core.$loose>;
|
|
74
|
+
/**
|
|
75
|
+
* Concept frontmatter. `.loose*` throughout: OKF conformance forbids
|
|
76
|
+
* rejecting unknown keys (§11), so extra fields pass through untouched.
|
|
77
|
+
*/
|
|
78
|
+
export declare const conceptFrontmatterSchema: z.ZodObject<{
|
|
79
|
+
type: z.ZodString;
|
|
80
|
+
title: z.ZodOptional<z.ZodString>;
|
|
81
|
+
description: z.ZodOptional<z.ZodString>;
|
|
82
|
+
resource: z.ZodOptional<z.ZodString>;
|
|
83
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
84
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
85
|
+
draft: "draft";
|
|
86
|
+
stable: "stable";
|
|
87
|
+
deprecated: "deprecated";
|
|
88
|
+
}>>;
|
|
89
|
+
stale_after: z.ZodOptional<z.ZodString>;
|
|
90
|
+
generated: z.ZodOptional<z.ZodObject<{
|
|
91
|
+
by: z.ZodString;
|
|
92
|
+
at: z.ZodOptional<z.ZodString>;
|
|
93
|
+
}, z.core.$loose>>;
|
|
94
|
+
verified: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
95
|
+
by: z.ZodString;
|
|
96
|
+
at: z.ZodOptional<z.ZodString>;
|
|
97
|
+
}, z.core.$loose>, z.ZodArray<z.ZodObject<{
|
|
98
|
+
by: z.ZodString;
|
|
99
|
+
at: z.ZodOptional<z.ZodString>;
|
|
100
|
+
}, z.core.$loose>>]>>;
|
|
101
|
+
sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
102
|
+
id: z.ZodOptional<z.ZodString>;
|
|
103
|
+
resource: z.ZodString;
|
|
104
|
+
title: z.ZodOptional<z.ZodString>;
|
|
105
|
+
author: z.ZodOptional<z.ZodString>;
|
|
106
|
+
last_modified: z.ZodOptional<z.ZodString>;
|
|
107
|
+
}, z.core.$loose>>>;
|
|
108
|
+
}, z.core.$loose>;
|
|
109
|
+
export type ConceptFrontmatter = z.infer<typeof conceptFrontmatterSchema>;
|
|
110
|
+
/** Root index.md frontmatter — the only place `okf_version` may live (§12). */
|
|
111
|
+
export declare const rootIndexFrontmatterSchema: z.ZodObject<{
|
|
112
|
+
okf_version: z.ZodOptional<z.ZodString>;
|
|
113
|
+
title: z.ZodOptional<z.ZodString>;
|
|
114
|
+
description: z.ZodOptional<z.ZodString>;
|
|
115
|
+
generated: z.ZodOptional<z.ZodObject<{
|
|
116
|
+
by: z.ZodString;
|
|
117
|
+
at: z.ZodOptional<z.ZodString>;
|
|
118
|
+
}, z.core.$loose>>;
|
|
119
|
+
}, z.core.$loose>;
|
|
120
|
+
/** Normalise `verified` to a list (§5.2) — bare mapping = one-element list. */
|
|
121
|
+
export declare function verifiedEntries(fm: ConceptFrontmatter): Array<z.infer<typeof verifiedEntrySchema>>;
|
|
122
|
+
/** Trust tier per §5.3: any `human:` verifier ⇒ human-reviewed. */
|
|
123
|
+
export declare function trustTier(fm: ConceptFrontmatter): TrustTier;
|
|
124
|
+
/** Effective lifecycle status — absent means stable (§5.4). */
|
|
125
|
+
export declare function effectiveStatus(fm: ConceptFrontmatter): 'draft' | 'stable' | 'deprecated';
|
|
126
|
+
/** Stale when today >= stale_after (§5.5). `today` is YYYY-MM-DD. */
|
|
127
|
+
export declare function isStale(fm: ConceptFrontmatter, today: string): boolean;
|
|
128
|
+
/** Operations recorded in log.md, one `## [date] op | summary` entry each. */
|
|
129
|
+
export declare const LOG_OPS: readonly ["init", "build", "fill", "garden", "ingest", "lint", "relate"];
|
|
130
|
+
export type LogOp = (typeof LOG_OPS)[number];
|
|
131
|
+
export declare const LOG_ENTRY_RE: RegExp;
|
|
132
|
+
export declare function formatLogEntry(date: string, op: LogOp, summary: string, detail?: string): string;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/wiki/format.ts — the Open Knowledge Format (OKF) v0.2 profile.
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists:
|
|
5
|
+
* The wiki is a directory of markdown "concept" documents with YAML
|
|
6
|
+
* frontmatter, per the OKF v0.2 spec
|
|
7
|
+
* (github.com/GoogleCloudPlatform/knowledge-catalog, okf/SPEC.md). The spec
|
|
8
|
+
* is deliberately minimal — `type` is the only required key — so every
|
|
9
|
+
* producer needs a PROFILE: which reserved fields it writes, which concept
|
|
10
|
+
* types it recognises, and how actors are named. This file is that profile,
|
|
11
|
+
* expressed as zod schemas so the same object validates frontmatter at
|
|
12
|
+
* parse time and documents the format in one place.
|
|
13
|
+
*
|
|
14
|
+
* Spec facts this module encodes (OKF v0.2):
|
|
15
|
+
* - `type` is the only always-required frontmatter key (§4.1).
|
|
16
|
+
* - `index.md` and `log.md` are RESERVED filenames, not concepts (§3.1);
|
|
17
|
+
* reserved files are exempt from the `type` requirement (§11).
|
|
18
|
+
* - Links are normal markdown links; bundle-absolute (`/dir/doc.md`)
|
|
19
|
+
* is the recommended form (§6.1). Consumers MUST tolerate broken
|
|
20
|
+
* links — but a producer should still lint them (lib/wiki/lint.ts).
|
|
21
|
+
* - Trust/provenance family (§5): `generated {by, at}`, `verified`
|
|
22
|
+
* (mapping or list), `sources[]`, `status: draft|stable|deprecated`
|
|
23
|
+
* (absent = stable), `stale_after: YYYY-MM-DD`.
|
|
24
|
+
* - Actor convention (§7): `<producer>/<version>` for agents,
|
|
25
|
+
* `human:<id>` for persons, `process:<id>` for automated processes.
|
|
26
|
+
* Trust tiers (§5.3) key off the `human:` prefix.
|
|
27
|
+
* - `okf_version` may appear in the bundle-root index.md only (§12).
|
|
28
|
+
*/
|
|
29
|
+
import { z } from 'zod';
|
|
30
|
+
// ── Spec constants ───────────────────────────────────────────────────────────
|
|
31
|
+
/** OKF spec version this profile targets. Written to the root index.md. */
|
|
32
|
+
export const OKF_VERSION = '0.2';
|
|
33
|
+
/** Reserved filenames (§3.1) — never concept documents. */
|
|
34
|
+
export const RESERVED_FILENAMES = ['index.md', 'log.md'];
|
|
35
|
+
export function isReservedFilename(fileName) {
|
|
36
|
+
return RESERVED_FILENAMES.includes(fileName);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The subtree that never leaves this repo. Path-based visibility: a doc is
|
|
40
|
+
* private iff its bundle path starts with this prefix. The export pipeline
|
|
41
|
+
* copies the bundle WITHOUT this subtree, and lint's closure rule forbids
|
|
42
|
+
* docs outside it from linking into it — so the published bundle is
|
|
43
|
+
* link-closed by construction (see wiki/decisions/0003.)
|
|
44
|
+
*/
|
|
45
|
+
export const PRIVATE_PREFIX = '/private/';
|
|
46
|
+
export function isPrivatePath(bundlePath) {
|
|
47
|
+
return bundlePath.startsWith(PRIVATE_PREFIX);
|
|
48
|
+
}
|
|
49
|
+
// ── Concept types (profile vocabulary) ───────────────────────────────────────
|
|
50
|
+
/**
|
|
51
|
+
* Recommended `type` values for this bundle. OKF requires consumers to
|
|
52
|
+
* tolerate unknown types (§11), so this list steers authors without
|
|
53
|
+
* rejecting anything — lint reports an unknown type as INFO, not an error.
|
|
54
|
+
*/
|
|
55
|
+
export const CONCEPT_TYPES = [
|
|
56
|
+
'overview', // orientation docs: what a thing is, why it exists
|
|
57
|
+
'subsystem', // a named part of the codebase and its responsibilities
|
|
58
|
+
'syndicate', // one agent-team YAML, documented
|
|
59
|
+
'tool', // a tool contract or tool family
|
|
60
|
+
'model-provider', // an LLM provider adapter and its dialect quirks
|
|
61
|
+
'schema', // a database or wire schema, canonically stated
|
|
62
|
+
'protocol', // an interop surface: MCP, A2A
|
|
63
|
+
'runbook', // step-by-step operational procedure
|
|
64
|
+
'guide', // task-oriented how-to
|
|
65
|
+
'decision', // one architecture decision record (ADR)
|
|
66
|
+
'doctrine', // standing rules that constrain future work
|
|
67
|
+
'reference', // pointer collection to external resources
|
|
68
|
+
'meta', // docs about the wiki itself
|
|
69
|
+
];
|
|
70
|
+
// ── Actor convention (§7) ────────────────────────────────────────────────────
|
|
71
|
+
/** `human:<id>` | `process:<id>` | `<producer>/<version>` */
|
|
72
|
+
export const actorSchema = z
|
|
73
|
+
.string()
|
|
74
|
+
.min(1)
|
|
75
|
+
.refine((a) => a.startsWith('human:') ? a.length > 'human:'.length
|
|
76
|
+
: a.startsWith('process:') ? a.length > 'process:'.length
|
|
77
|
+
: a.includes('/'), {
|
|
78
|
+
message: 'actor must be "human:<id>", "process:<id>", or "<producer>/<version>"',
|
|
79
|
+
});
|
|
80
|
+
/** Actor id for structural (non-LLM) generation by this module. */
|
|
81
|
+
export const BUILD_ACTOR = 'process:wiki-build';
|
|
82
|
+
/** Actor id for LLM gap-fill: the producer is the framework, version the model. */
|
|
83
|
+
export function llmActor(modelId) {
|
|
84
|
+
return `melchizedek/${modelId}`;
|
|
85
|
+
}
|
|
86
|
+
// ── Frontmatter schemas ──────────────────────────────────────────────────────
|
|
87
|
+
const isoDateSchema = z
|
|
88
|
+
.string()
|
|
89
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/, 'expected YYYY-MM-DD');
|
|
90
|
+
export const generatedSchema = z.looseObject({
|
|
91
|
+
by: actorSchema,
|
|
92
|
+
/** Last meaningful content change (ISO 8601). */
|
|
93
|
+
at: z.string().optional(),
|
|
94
|
+
});
|
|
95
|
+
export const verifiedEntrySchema = z.looseObject({
|
|
96
|
+
by: actorSchema,
|
|
97
|
+
at: z.string().optional(),
|
|
98
|
+
});
|
|
99
|
+
export const sourceSchema = z.looseObject({
|
|
100
|
+
/** Stable key for citing this source from the body. */
|
|
101
|
+
id: z.string().optional(),
|
|
102
|
+
/** URL or bundle path of the material this concept derives from. */
|
|
103
|
+
resource: z.string().min(1),
|
|
104
|
+
title: z.string().optional(),
|
|
105
|
+
author: z.string().optional(),
|
|
106
|
+
last_modified: isoDateSchema.optional(),
|
|
107
|
+
});
|
|
108
|
+
/**
|
|
109
|
+
* Concept frontmatter. `.loose*` throughout: OKF conformance forbids
|
|
110
|
+
* rejecting unknown keys (§11), so extra fields pass through untouched.
|
|
111
|
+
*/
|
|
112
|
+
export const conceptFrontmatterSchema = z.looseObject({
|
|
113
|
+
/** The ONLY required key (§4.1). */
|
|
114
|
+
type: z.string().min(1, 'OKF requires a non-empty `type`'),
|
|
115
|
+
title: z.string().optional(),
|
|
116
|
+
description: z.string().optional(),
|
|
117
|
+
/** URL of the live resource this concept describes. */
|
|
118
|
+
resource: z.string().optional(),
|
|
119
|
+
tags: z.array(z.string()).optional(),
|
|
120
|
+
/** Absent status means `stable` (§5.4). */
|
|
121
|
+
status: z.enum(['draft', 'stable', 'deprecated']).optional(),
|
|
122
|
+
stale_after: isoDateSchema.optional(),
|
|
123
|
+
generated: generatedSchema.optional(),
|
|
124
|
+
/** Bare mapping MUST be treated as a one-element list (§5.2). */
|
|
125
|
+
verified: z
|
|
126
|
+
.union([verifiedEntrySchema, z.array(verifiedEntrySchema)])
|
|
127
|
+
.optional(),
|
|
128
|
+
sources: z.array(sourceSchema).optional(),
|
|
129
|
+
});
|
|
130
|
+
/** Root index.md frontmatter — the only place `okf_version` may live (§12). */
|
|
131
|
+
export const rootIndexFrontmatterSchema = z.looseObject({
|
|
132
|
+
okf_version: z.string().optional(),
|
|
133
|
+
title: z.string().optional(),
|
|
134
|
+
description: z.string().optional(),
|
|
135
|
+
generated: generatedSchema.optional(),
|
|
136
|
+
});
|
|
137
|
+
// ── Derivations ──────────────────────────────────────────────────────────────
|
|
138
|
+
/** Normalise `verified` to a list (§5.2) — bare mapping = one-element list. */
|
|
139
|
+
export function verifiedEntries(fm) {
|
|
140
|
+
if (!fm.verified)
|
|
141
|
+
return [];
|
|
142
|
+
return Array.isArray(fm.verified) ? fm.verified : [fm.verified];
|
|
143
|
+
}
|
|
144
|
+
/** Trust tier per §5.3: any `human:` verifier ⇒ human-reviewed. */
|
|
145
|
+
export function trustTier(fm) {
|
|
146
|
+
const entries = verifiedEntries(fm);
|
|
147
|
+
if (entries.length === 0)
|
|
148
|
+
return 'unverified';
|
|
149
|
+
return entries.some((v) => v.by.startsWith('human:'))
|
|
150
|
+
? 'human-reviewed'
|
|
151
|
+
: 'machine-confirmed';
|
|
152
|
+
}
|
|
153
|
+
/** Effective lifecycle status — absent means stable (§5.4). */
|
|
154
|
+
export function effectiveStatus(fm) {
|
|
155
|
+
return fm.status ?? 'stable';
|
|
156
|
+
}
|
|
157
|
+
/** Stale when today >= stale_after (§5.5). `today` is YYYY-MM-DD. */
|
|
158
|
+
export function isStale(fm, today) {
|
|
159
|
+
return fm.stale_after !== undefined && today >= fm.stale_after;
|
|
160
|
+
}
|
|
161
|
+
// ── Log format (reserved log.md, §9) ─────────────────────────────────────────
|
|
162
|
+
/** Operations recorded in log.md, one `## [date] op | summary` entry each. */
|
|
163
|
+
export const LOG_OPS = [
|
|
164
|
+
'init',
|
|
165
|
+
'build',
|
|
166
|
+
'fill',
|
|
167
|
+
'garden',
|
|
168
|
+
'ingest',
|
|
169
|
+
'lint',
|
|
170
|
+
/** One asserted relation added to the entity graph (lib/wiki/entities.ts). */
|
|
171
|
+
'relate',
|
|
172
|
+
];
|
|
173
|
+
export const LOG_ENTRY_RE = /^## \[(\d{4}-\d{2}-\d{2})\] ([a-z]+) \| (.+)$/;
|
|
174
|
+
export function formatLogEntry(date, op, summary, detail) {
|
|
175
|
+
const head = `## [${date}] ${op} | ${summary}`;
|
|
176
|
+
return detail ? `${head}\n\n${detail.trim()}\n` : `${head}\n`;
|
|
177
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/wiki/graph.ts — the knowledge graph derived from links.
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists:
|
|
5
|
+
* OKF's insight is that a directory of markdown files with links IS a
|
|
6
|
+
* graph — no database required. This module makes that graph explicit:
|
|
7
|
+
* nodes are documents, edges are resolved markdown links between them.
|
|
8
|
+
* Everything downstream that "navigates" (neighbors, repo-dive reading
|
|
9
|
+
* paths, orphan detection in lint, the exported graph.json) traverses the
|
|
10
|
+
* structure built here.
|
|
11
|
+
*
|
|
12
|
+
* The graph is always DERIVED, never stored: rebuild-from-files is cheap
|
|
13
|
+
* at this scale and can't drift from the documents the way a cached
|
|
14
|
+
* index could. `wiki:build --graph` merely snapshots this derivation for
|
|
15
|
+
* visualisation.
|
|
16
|
+
*/
|
|
17
|
+
import { type TrustTier } from './format.ts';
|
|
18
|
+
import { type Vault, type WikiDoc } from './vault.ts';
|
|
19
|
+
export interface GraphNode {
|
|
20
|
+
path: string;
|
|
21
|
+
title: string;
|
|
22
|
+
/** OKF concept type; index/log files carry their reserved kind instead. */
|
|
23
|
+
type: string;
|
|
24
|
+
kind: WikiDoc['kind'];
|
|
25
|
+
status: 'draft' | 'stable' | 'deprecated';
|
|
26
|
+
trust: TrustTier;
|
|
27
|
+
isPrivate: boolean;
|
|
28
|
+
wordCount: number;
|
|
29
|
+
tags: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface GraphEdge {
|
|
32
|
+
/** Bundle path of the linking document. */
|
|
33
|
+
source: string;
|
|
34
|
+
/** Normalised bundle path of the target (which may not exist). */
|
|
35
|
+
target: string;
|
|
36
|
+
/** False when the target is not a document in the bundle. */
|
|
37
|
+
resolved: boolean;
|
|
38
|
+
/** Anchor text of the link. */
|
|
39
|
+
text: string;
|
|
40
|
+
line: number;
|
|
41
|
+
}
|
|
42
|
+
export interface WikiGraph {
|
|
43
|
+
nodes: Map<string, GraphNode>;
|
|
44
|
+
edges: GraphEdge[];
|
|
45
|
+
outbound: Map<string, GraphEdge[]>;
|
|
46
|
+
inbound: Map<string, GraphEdge[]>;
|
|
47
|
+
}
|
|
48
|
+
export declare function buildGraph(vault: Vault): WikiGraph;
|
|
49
|
+
/**
|
|
50
|
+
* Concepts nothing points to: no inbound link from any OTHER document,
|
|
51
|
+
* including index listings. The bundle root index and log are exempt.
|
|
52
|
+
*/
|
|
53
|
+
export declare function orphanConcepts(graph: WikiGraph): GraphNode[];
|
|
54
|
+
export declare function brokenEdges(graph: WikiGraph): GraphEdge[];
|
|
55
|
+
export interface Neighbor {
|
|
56
|
+
node: GraphNode;
|
|
57
|
+
/** Link hops from the origin. */
|
|
58
|
+
distance: number;
|
|
59
|
+
/** How we got here: the linking document. */
|
|
60
|
+
via: string;
|
|
61
|
+
direction: 'out' | 'in';
|
|
62
|
+
}
|
|
63
|
+
/** Breadth-first neighborhood of a document along resolved edges. */
|
|
64
|
+
export declare function neighborhood(graph: WikiGraph, originPath: string, depth?: number, direction?: 'out' | 'in' | 'both'): Neighbor[];
|
|
65
|
+
export interface GraphStats {
|
|
66
|
+
documents: number;
|
|
67
|
+
concepts: number;
|
|
68
|
+
edges: number;
|
|
69
|
+
brokenLinks: number;
|
|
70
|
+
orphans: number;
|
|
71
|
+
byType: Record<string, number>;
|
|
72
|
+
totalWords: number;
|
|
73
|
+
}
|
|
74
|
+
export declare function graphStats(graph: WikiGraph): GraphStats;
|
|
75
|
+
/** JSON-serialisable snapshot (what `wiki:build --graph` writes). */
|
|
76
|
+
export declare function graphToJson(graph: WikiGraph): {
|
|
77
|
+
stats: GraphStats;
|
|
78
|
+
nodes: GraphNode[];
|
|
79
|
+
edges: GraphEdge[];
|
|
80
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/wiki/graph.ts — the knowledge graph derived from links.
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists:
|
|
5
|
+
* OKF's insight is that a directory of markdown files with links IS a
|
|
6
|
+
* graph — no database required. This module makes that graph explicit:
|
|
7
|
+
* nodes are documents, edges are resolved markdown links between them.
|
|
8
|
+
* Everything downstream that "navigates" (neighbors, repo-dive reading
|
|
9
|
+
* paths, orphan detection in lint, the exported graph.json) traverses the
|
|
10
|
+
* structure built here.
|
|
11
|
+
*
|
|
12
|
+
* The graph is always DERIVED, never stored: rebuild-from-files is cheap
|
|
13
|
+
* at this scale and can't drift from the documents the way a cached
|
|
14
|
+
* index could. `wiki:build --graph` merely snapshots this derivation for
|
|
15
|
+
* visualisation.
|
|
16
|
+
*/
|
|
17
|
+
import { effectiveStatus, trustTier } from "./format.js";
|
|
18
|
+
import { lookupTarget, resolveLinkTarget, } from "./vault.js";
|
|
19
|
+
// ── Construction ─────────────────────────────────────────────────────────────
|
|
20
|
+
export function buildGraph(vault) {
|
|
21
|
+
const nodes = new Map();
|
|
22
|
+
const edges = [];
|
|
23
|
+
const outbound = new Map();
|
|
24
|
+
const inbound = new Map();
|
|
25
|
+
for (const doc of vault.docs.values()) {
|
|
26
|
+
nodes.set(doc.bundlePath, {
|
|
27
|
+
path: doc.bundlePath,
|
|
28
|
+
title: doc.title,
|
|
29
|
+
type: doc.kind === 'concept'
|
|
30
|
+
? (doc.parsed.frontmatter?.type ?? 'unknown')
|
|
31
|
+
: doc.kind,
|
|
32
|
+
kind: doc.kind,
|
|
33
|
+
status: doc.fm ? effectiveStatus(doc.fm) : 'stable',
|
|
34
|
+
trust: doc.fm ? trustTier(doc.fm) : 'unverified',
|
|
35
|
+
isPrivate: doc.isPrivate,
|
|
36
|
+
wordCount: doc.wordCount,
|
|
37
|
+
tags: doc.fm?.tags ?? [],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
for (const doc of vault.docs.values()) {
|
|
41
|
+
for (const link of doc.parsed.links) {
|
|
42
|
+
if (link.isImage)
|
|
43
|
+
continue;
|
|
44
|
+
const resolved = resolveLinkTarget(doc.bundlePath, link.target);
|
|
45
|
+
if (resolved.kind !== 'internal')
|
|
46
|
+
continue;
|
|
47
|
+
const targetDoc = lookupTarget(vault, resolved.bundlePath);
|
|
48
|
+
const edge = {
|
|
49
|
+
source: doc.bundlePath,
|
|
50
|
+
target: targetDoc ? targetDoc.bundlePath : resolved.bundlePath,
|
|
51
|
+
resolved: targetDoc !== null,
|
|
52
|
+
text: link.text,
|
|
53
|
+
line: link.line,
|
|
54
|
+
};
|
|
55
|
+
edges.push(edge);
|
|
56
|
+
const out = outbound.get(edge.source) ?? [];
|
|
57
|
+
out.push(edge);
|
|
58
|
+
outbound.set(edge.source, out);
|
|
59
|
+
if (edge.resolved) {
|
|
60
|
+
const inn = inbound.get(edge.target) ?? [];
|
|
61
|
+
inn.push(edge);
|
|
62
|
+
inbound.set(edge.target, inn);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { nodes, edges, outbound, inbound };
|
|
67
|
+
}
|
|
68
|
+
// ── Queries ──────────────────────────────────────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* Concepts nothing points to: no inbound link from any OTHER document,
|
|
71
|
+
* including index listings. The bundle root index and log are exempt.
|
|
72
|
+
*/
|
|
73
|
+
export function orphanConcepts(graph) {
|
|
74
|
+
return [...graph.nodes.values()].filter((node) => {
|
|
75
|
+
if (node.kind !== 'concept')
|
|
76
|
+
return false;
|
|
77
|
+
const inn = graph.inbound.get(node.path) ?? [];
|
|
78
|
+
return !inn.some((e) => e.source !== node.path);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export function brokenEdges(graph) {
|
|
82
|
+
return graph.edges.filter((e) => !e.resolved);
|
|
83
|
+
}
|
|
84
|
+
/** Breadth-first neighborhood of a document along resolved edges. */
|
|
85
|
+
export function neighborhood(graph, originPath, depth = 1, direction = 'both') {
|
|
86
|
+
const seen = new Set([originPath]);
|
|
87
|
+
const result = [];
|
|
88
|
+
let frontier = [originPath];
|
|
89
|
+
for (let d = 1; d <= depth; d++) {
|
|
90
|
+
const next = [];
|
|
91
|
+
for (const path of frontier) {
|
|
92
|
+
const candidates = [];
|
|
93
|
+
if (direction !== 'in') {
|
|
94
|
+
for (const e of graph.outbound.get(path) ?? []) {
|
|
95
|
+
if (e.resolved)
|
|
96
|
+
candidates.push({ target: e.target, dir: 'out' });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (direction !== 'out') {
|
|
100
|
+
for (const e of graph.inbound.get(path) ?? []) {
|
|
101
|
+
candidates.push({ target: e.source, dir: 'in' });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const c of candidates) {
|
|
105
|
+
if (seen.has(c.target))
|
|
106
|
+
continue;
|
|
107
|
+
seen.add(c.target);
|
|
108
|
+
const node = graph.nodes.get(c.target);
|
|
109
|
+
if (!node)
|
|
110
|
+
continue;
|
|
111
|
+
result.push({ node, distance: d, via: path, direction: c.dir });
|
|
112
|
+
next.push(c.target);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
frontier = next;
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
export function graphStats(graph) {
|
|
120
|
+
const byType = {};
|
|
121
|
+
let totalWords = 0;
|
|
122
|
+
let concepts = 0;
|
|
123
|
+
for (const node of graph.nodes.values()) {
|
|
124
|
+
totalWords += node.wordCount;
|
|
125
|
+
if (node.kind !== 'concept')
|
|
126
|
+
continue;
|
|
127
|
+
concepts++;
|
|
128
|
+
byType[node.type] = (byType[node.type] ?? 0) + 1;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
documents: graph.nodes.size,
|
|
132
|
+
concepts,
|
|
133
|
+
edges: graph.edges.length,
|
|
134
|
+
brokenLinks: brokenEdges(graph).length,
|
|
135
|
+
orphans: orphanConcepts(graph).length,
|
|
136
|
+
byType,
|
|
137
|
+
totalWords,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** JSON-serialisable snapshot (what `wiki:build --graph` writes). */
|
|
141
|
+
export function graphToJson(graph) {
|
|
142
|
+
return {
|
|
143
|
+
stats: graphStats(graph),
|
|
144
|
+
nodes: [...graph.nodes.values()],
|
|
145
|
+
edges: graph.edges,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/wiki/lint.ts — producer-side health checks for the bundle.
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists:
|
|
5
|
+
* OKF consumers MUST tolerate broken links, missing indexes, unknown
|
|
6
|
+
* fields — the spec is forgiving on purpose. A PRODUCER that leans on
|
|
7
|
+
* that forgiveness rots: links dangle, pages orphan, indexes lag their
|
|
8
|
+
* directories. This is Karpathy's "lint" operation from the LLM-wiki
|
|
9
|
+
* pattern, run as code instead of vibes. It is also the gate every write
|
|
10
|
+
* passes through: `wiki_save` refuses drafts whose errors would degrade
|
|
11
|
+
* the bundle.
|
|
12
|
+
*
|
|
13
|
+
* Severity doctrine:
|
|
14
|
+
* error — violates OKF conformance or the profile's hard rules;
|
|
15
|
+
* blocks wiki_save and fails `wiki:build --check`.
|
|
16
|
+
* warning — the bundle works but is decaying (broken link, orphan,
|
|
17
|
+
* stale doc, index gap). Surfaced, never blocking.
|
|
18
|
+
* info — style guidance (relative links, unknown type).
|
|
19
|
+
*
|
|
20
|
+
* The private-closure rule is the publishing invariant: documents outside
|
|
21
|
+
* /private/ must never link into it, so exporting the bundle minus the
|
|
22
|
+
* private subtree can never produce a dangling reference the producer
|
|
23
|
+
* didn't choose. Extra `forbiddenPatterns` (names that must not appear in
|
|
24
|
+
* public docs) are supplied BY THE CALLER — this public module does not
|
|
25
|
+
* itself know any private vocabulary.
|
|
26
|
+
*/
|
|
27
|
+
import { type Vault, type WikiDoc } from './vault.ts';
|
|
28
|
+
export type LintSeverity = 'error' | 'warning' | 'info';
|
|
29
|
+
export interface LintFinding {
|
|
30
|
+
severity: LintSeverity;
|
|
31
|
+
/** Stable rule id, e.g. 'frontmatter', 'broken-link', 'index-coverage'. */
|
|
32
|
+
rule: string;
|
|
33
|
+
docPath: string;
|
|
34
|
+
line?: number;
|
|
35
|
+
message: string;
|
|
36
|
+
}
|
|
37
|
+
export interface LintReport {
|
|
38
|
+
findings: LintFinding[];
|
|
39
|
+
errors: number;
|
|
40
|
+
warnings: number;
|
|
41
|
+
ok: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface LintOptions {
|
|
44
|
+
/** YYYY-MM-DD used for staleness; defaults to the current date. */
|
|
45
|
+
today?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Patterns that must not appear in PUBLIC documents (names of private
|
|
48
|
+
* systems, etc.). Checked against each public doc's full text.
|
|
49
|
+
*/
|
|
50
|
+
forbiddenPatterns?: RegExp[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Rules that need only the document itself (+ the vault for link targets).
|
|
54
|
+
* Used both by lintVault and by the wiki_save gate on unsaved drafts.
|
|
55
|
+
*/
|
|
56
|
+
export declare function lintDoc(vault: Vault, doc: WikiDoc, options?: LintOptions): LintFinding[];
|
|
57
|
+
export declare function lintVault(vault: Vault, options?: LintOptions): LintReport;
|
|
58
|
+
export declare function formatLintReport(result: LintReport): string;
|