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.
Files changed (111) hide show
  1. package/AGENT_SETUP.md +182 -0
  2. package/DOCUMENTATION.md +614 -0
  3. package/LICENSE +21 -0
  4. package/QUICKSTART.md +164 -0
  5. package/README.md +151 -0
  6. package/config/agents/README.md +27 -0
  7. package/config/agents/examples/ares.yaml +74 -0
  8. package/config/agents/examples/cartographers.yaml +115 -0
  9. package/config/agents/examples/claude.yaml +17 -0
  10. package/config/agents/examples/council.yaml +65 -0
  11. package/config/agents/examples/critic.yaml +74 -0
  12. package/config/agents/examples/delegation.yaml +29 -0
  13. package/config/agents/examples/hierarchical.yaml +26 -0
  14. package/config/agents/examples/image_production.yaml +112 -0
  15. package/config/agents/examples/librarian.yaml +75 -0
  16. package/config/agents/examples/model_zoo.yaml +81 -0
  17. package/config/agents/examples/patient_advocate.yaml +107 -0
  18. package/config/agents/examples/scriptorium.yaml +96 -0
  19. package/config/agents/examples/style_council.yaml +83 -0
  20. package/config/agents/examples/syndicate.yaml +34 -0
  21. package/config/agents/examples/tutor.yaml +66 -0
  22. package/config/agents/syndicateSchema.yaml +291 -0
  23. package/dist/lib/config.d.ts +108 -0
  24. package/dist/lib/config.js +110 -0
  25. package/dist/lib/dispatch.d.ts +133 -0
  26. package/dist/lib/dispatch.js +182 -0
  27. package/dist/lib/grounding.d.ts +28 -0
  28. package/dist/lib/grounding.js +61 -0
  29. package/dist/lib/index.d.ts +14 -0
  30. package/dist/lib/index.js +13 -0
  31. package/dist/lib/loadEnv.d.ts +21 -0
  32. package/dist/lib/loadEnv.js +58 -0
  33. package/dist/lib/loadSyndicate.d.ts +177 -0
  34. package/dist/lib/loadSyndicate.js +264 -0
  35. package/dist/lib/memory/supabaseMemoryService.d.ts +118 -0
  36. package/dist/lib/memory/supabaseMemoryService.js +499 -0
  37. package/dist/lib/models/claudeLlm.d.ts +93 -0
  38. package/dist/lib/models/claudeLlm.js +332 -0
  39. package/dist/lib/models/gptLlm.d.ts +110 -0
  40. package/dist/lib/models/gptLlm.js +497 -0
  41. package/dist/lib/models/grokLlm.d.ts +64 -0
  42. package/dist/lib/models/grokLlm.js +86 -0
  43. package/dist/lib/models/ollamaLlm.d.ts +105 -0
  44. package/dist/lib/models/ollamaLlm.js +134 -0
  45. package/dist/lib/models/openAiCompatibleLlm.d.ts +125 -0
  46. package/dist/lib/models/openAiCompatibleLlm.js +331 -0
  47. package/dist/lib/models/providerMap.d.ts +28 -0
  48. package/dist/lib/models/providerMap.js +45 -0
  49. package/dist/lib/models/registry.d.ts +71 -0
  50. package/dist/lib/models/registry.js +159 -0
  51. package/dist/lib/models/schemaNormalize.d.ts +22 -0
  52. package/dist/lib/models/schemaNormalize.js +58 -0
  53. package/dist/lib/observability/supabaseSpanExporter.d.ts +35 -0
  54. package/dist/lib/observability/supabaseSpanExporter.js +115 -0
  55. package/dist/lib/observability/tracer.d.ts +44 -0
  56. package/dist/lib/observability/tracer.js +242 -0
  57. package/dist/lib/persistence/supabaseProvider.d.ts +44 -0
  58. package/dist/lib/persistence/supabaseProvider.js +88 -0
  59. package/dist/lib/session/supabaseSessionService.d.ts +26 -0
  60. package/dist/lib/session/supabaseSessionService.js +193 -0
  61. package/dist/lib/session/transcript.d.ts +193 -0
  62. package/dist/lib/session/transcript.js +309 -0
  63. package/dist/lib/toolRegistry.d.ts +15 -0
  64. package/dist/lib/toolRegistry.js +58 -0
  65. package/dist/lib/tools/collectionsSearchTool.d.ts +55 -0
  66. package/dist/lib/tools/collectionsSearchTool.js +86 -0
  67. package/dist/lib/tools/generateImageTool.d.ts +16 -0
  68. package/dist/lib/tools/generateImageTool.js +119 -0
  69. package/dist/lib/tools/inspectImageTool.d.ts +21 -0
  70. package/dist/lib/tools/inspectImageTool.js +97 -0
  71. package/dist/lib/tools/mcpToolFactory.d.ts +2 -0
  72. package/dist/lib/tools/mcpToolFactory.js +116 -0
  73. package/dist/lib/tools/toolContract.d.ts +78 -0
  74. package/dist/lib/tools/toolContract.js +129 -0
  75. package/dist/lib/tools/webExtractTool.d.ts +70 -0
  76. package/dist/lib/tools/webExtractTool.js +461 -0
  77. package/dist/lib/tools/webSearchTool.d.ts +53 -0
  78. package/dist/lib/tools/webSearchTool.js +126 -0
  79. package/dist/lib/tools/wikiTools.d.ts +112 -0
  80. package/dist/lib/tools/wikiTools.js +697 -0
  81. package/dist/lib/tools/xSearchTool.d.ts +61 -0
  82. package/dist/lib/tools/xSearchTool.js +125 -0
  83. package/dist/lib/wiki/agentRun.d.ts +39 -0
  84. package/dist/lib/wiki/agentRun.js +79 -0
  85. package/dist/lib/wiki/builder.d.ts +71 -0
  86. package/dist/lib/wiki/builder.js +165 -0
  87. package/dist/lib/wiki/dive.d.ts +56 -0
  88. package/dist/lib/wiki/dive.js +208 -0
  89. package/dist/lib/wiki/entities.d.ts +220 -0
  90. package/dist/lib/wiki/entities.js +661 -0
  91. package/dist/lib/wiki/extract.d.ts +97 -0
  92. package/dist/lib/wiki/extract.js +305 -0
  93. package/dist/lib/wiki/fill.d.ts +37 -0
  94. package/dist/lib/wiki/fill.js +175 -0
  95. package/dist/lib/wiki/format.d.ts +132 -0
  96. package/dist/lib/wiki/format.js +177 -0
  97. package/dist/lib/wiki/graph.d.ts +80 -0
  98. package/dist/lib/wiki/graph.js +147 -0
  99. package/dist/lib/wiki/lint.d.ts +58 -0
  100. package/dist/lib/wiki/lint.js +267 -0
  101. package/dist/lib/wiki/markdown.d.ts +127 -0
  102. package/dist/lib/wiki/markdown.js +336 -0
  103. package/dist/lib/wiki/pipeline.d.ts +69 -0
  104. package/dist/lib/wiki/pipeline.js +119 -0
  105. package/dist/lib/wiki/vault.d.ts +84 -0
  106. package/dist/lib/wiki/vault.js +194 -0
  107. package/dist/scripts/a2a_server.d.ts +2 -0
  108. package/dist/scripts/a2a_server.js +997 -0
  109. package/dist/scripts/syndicate_chat.d.ts +2 -0
  110. package/dist/scripts/syndicate_chat.js +485 -0
  111. 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;