diff --git a/.changeset/adopt-okf-v0-2.md b/.changeset/adopt-okf-v0-2.md new file mode 100644 index 00000000..8833cb16 --- /dev/null +++ b/.changeset/adopt-okf-v0-2.md @@ -0,0 +1,5 @@ +--- +"openwiki": minor +--- + +Adopt OKF v0.2 output: the bundle-root index now declares `okf_version: "0.2"`, agent prompts instruct the `generated: {by, at}` trust field (actor `openwiki/`) in place of the superseded `timestamp`, and the front-matter validator checks the v0.2 provenance, trust, and lifecycle families (`generated`, `verified`, `sources`, `status`, `stale_after`) while still tolerating the legacy `timestamp` on v0.1 pages. diff --git a/README.md b/README.md index 9520a702..097e6a81 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ OpenWiki is a CLI that writes and maintains a wiki for your codebase or your per - **Built-in connectors** for Notion, Slack, Gmail, X, Web Search, Hacker News, and local git repositories. - **An interactive visualizer** that turns any wiki into a live, explorable node graph. - **Self-updating** through GitHub Actions, GitLab CI, or Bitbucket Pipelines. -- **Open Knowledge Format** ([OKF v0.1](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md)) output with validated Mermaid diagrams. +- **Open Knowledge Format** ([OKF v0.2](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md)) output with validated Mermaid diagrams. ## 🎉 What's new @@ -149,12 +149,14 @@ Everything OpenWiki writes is plain Markdown you own and version alongside your ## Open Knowledge Format -OpenWiki emits [Google Open Knowledge Format (OKF) v0.1](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md) bundles in both modes, so your wiki is portable to any OKF-aware tool. +OpenWiki emits [Google Open Knowledge Format (OKF) v0.2](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md) bundles in both modes, so your wiki is portable to any OKF-aware tool. - Every concept document carries YAML front matter with a non-empty `type`; all other standard fields are optional. +- Pages record their last meaningful change as `generated: {by, at}`; the legacy v0.1 `timestamp` field is still tolerated on existing pages. +- The optional v0.2 provenance, trust, and lifecycle families (`sources`, `verified`, `status`, `stale_after`) are validated when present. - Standard Markdown links between concept documents express their relationships. -- `index.md` and `log.md` are reserved documents rather than concepts. The root index declares `okf_version: "0.1"`. -- Valid `timestamp` values and producer-defined extension fields are preserved across updates and migrations. +- `index.md` and `log.md` are reserved documents rather than concepts. The root index declares `okf_version: "0.2"`. +- Producer-defined extension fields are preserved across updates and migrations. ## Diagrams diff --git a/src/agent/prompt.ts b/src/agent/prompt.ts index 36d6e9d6..104b8ebc 100644 --- a/src/agent/prompt.ts +++ b/src/agent/prompt.ts @@ -1,3 +1,4 @@ +import { OPENWIKI_VERSION } from "../version.js"; import type { OpenWikiIgnore } from "./openwiki-ignore.js"; import { CODE_SYSTEM_PROMPTS, CODE_USER_PROMPTS } from "./prompts/code.js"; import { @@ -30,6 +31,7 @@ export function createSystemPrompt( : PERSONAL_SYSTEM_PROMPTS[command]; const prompt = template + .replaceAll("{OKF_PRODUCER_ACTOR}", `openwiki/${OPENWIKI_VERSION}`) .replace( "{OUTPUT_LANGUAGE_INSTRUCTIONS}", formatLanguageInstructions(language), diff --git a/src/agent/prompts/code.ts b/src/agent/prompts/code.ts index eadf6771..17c6a252 100644 --- a/src/agent/prompts/code.ts +++ b/src/agent/prompts/code.ts @@ -60,8 +60,8 @@ Security and privacy rules: Front matter requirements (OKF): - Every non-reserved Markdown concept file you create or update under the target repository's openwiki/ directory, including the temporary /openwiki/_plan.md file, MUST begin with OKF-compliant YAML front matter. -- The front matter MUST follow the Google Knowledge Catalog OKF v0.1 schema. -- \`index.md\` and \`log.md\` are reserved OKF documents and must not be given concept front matter. Directory indexes are generated deterministically; only the bundle-root index may contain \`okf_version: "0.1"\` front matter. +- The front matter MUST follow the Google Knowledge Catalog OKF v0.2 schema. +- \`index.md\` and \`log.md\` are reserved OKF documents and must not be given concept front matter. Directory indexes are generated deterministically; only the bundle-root index may contain \`okf_version: "0.2"\` front matter. - Use this formatter at the very beginning of concept files, replacing placeholders with real values and omitting optional fields that do not apply: @@ -71,14 +71,14 @@ title: description: resource: tags: [, , …] # Optional -timestamp: +generated: {by: , at: } # Optional # Producer-defined extension fields are allowed. --- - Only \`type\` is required. Choose a short, descriptive, self-explanatory concept kind, such as \`BigQuery Table\`, \`BigQuery Dataset\`, \`API Endpoint\`, \`Metric\`, \`Playbook\`, or \`Reference\`. Type values are not centrally registered, so do not restrict them to a fixed list. - Recommended fields, in priority order, are: \`title\`, a human-readable display name; \`description\`, a one to two sentence summary optimized for search and retrieval; \`resource\`, the canonical URI of the underlying asset when one exists; and \`tags\`, a YAML list of short cross-cutting category strings. -- \`timestamp\` is an optional ISO 8601 datetime for the last meaningful change. +- \`generated\` optionally records the content's last meaningful change: \`by\` is required within it and names the producing actor (\`/\` for agents, \`human:\` for people, \`process:\` for processes); \`at\` is an ISO 8601 datetime. When you create or meaningfully change a concept, write \`generated: {by: {OKF_PRODUCER_ACTOR}, at: }\` and drop any superseded legacy \`timestamp\` field from that concept; otherwise leave existing \`generated\` and \`timestamp\` values untouched. - Produce valid YAML. Do not leave placeholder text or explanatory comments in written files. - Preserve all existing producer-defined front matter fields when updating a concept. Unknown extension fields are valid OKF and must survive round trips. Change metadata only when the underlying fact or meaningful content changes. - The description field is especially useful for retrieval tools. When present, make it clear, detailed, and optimized for search. @@ -182,7 +182,7 @@ Do not draft wiki prose until every planned substantive page has an evidence bri - Only begin writing after this evidence gate is satisfied for the complete inventory. Do not start with quickstart prose while major components still have only manifest- or README-level understanding. Metadata and links (OKF): -- Every non-reserved Markdown concept must begin with valid OKF v0.1 YAML front matter. index.md and log.md are reserved and must not receive concept front matter. +- Every non-reserved Markdown concept must begin with valid OKF v0.2 YAML front matter. index.md and log.md are reserved and must not receive concept front matter. - Use this shape, omitting optional or empty fields: \`\`\`yaml @@ -192,11 +192,12 @@ title: description: resource: tags: [] -timestamp: +generated: {by: , at: } # optional --- \`\`\` - Only type is required by OKF, but add accurate title and description for retrieval. +- Write \`generated: {by: {OKF_PRODUCER_ACTOR}, at: }\` on pages you author; do not write the superseded legacy \`timestamp\` field. - Treat Markdown links between concept pages as semantic relationships. Put links in the prose that explains runtime, dependency, ownership, data-flow, lifecycle, or user-flow relationships; quickstart navigation alone is not a substitute. Diagrams: @@ -303,8 +304,8 @@ OKF relationship modeling: Front matter requirements (OKF): - Every non-reserved Markdown concept file you create or update under the target repository's openwiki/ directory, including the temporary /openwiki/_plan.md file, MUST begin with OKF-compliant YAML front matter. -- The front matter MUST follow the Google Knowledge Catalog OKF v0.1 schema. -- \`index.md\` and \`log.md\` are reserved OKF documents and must not be given concept front matter. Directory indexes are generated deterministically; only the bundle-root index may contain \`okf_version: "0.1"\` front matter. +- The front matter MUST follow the Google Knowledge Catalog OKF v0.2 schema. +- \`index.md\` and \`log.md\` are reserved OKF documents and must not be given concept front matter. Directory indexes are generated deterministically; only the bundle-root index may contain \`okf_version: "0.2"\` front matter. - Use this formatter at the very beginning of concept files, replacing placeholders with real values and omitting optional fields that do not apply: @@ -314,14 +315,14 @@ title: description: resource: tags: [, , …] # Optional -timestamp: +generated: {by: , at: } # Optional # Producer-defined extension fields are allowed. --- - Only \`type\` is required. Choose a short, descriptive, self-explanatory concept kind, such as \`BigQuery Table\`, \`BigQuery Dataset\`, \`API Endpoint\`, \`Metric\`, \`Playbook\`, or \`Reference\`. Type values are not centrally registered, so do not restrict them to a fixed list. - Recommended fields, in priority order, are: \`title\`, a human-readable display name; \`description\`, a one to two sentence summary optimized for search and retrieval; \`resource\`, the canonical URI of the underlying asset when one exists; and \`tags\`, a YAML list of short cross-cutting category strings. -- \`timestamp\` is an optional ISO 8601 datetime for the last meaningful change. +- \`generated\` optionally records the content's last meaningful change: \`by\` is required within it and names the producing actor (\`/\` for agents, \`human:\` for people, \`process:\` for processes); \`at\` is an ISO 8601 datetime. When you create or meaningfully change a concept, write \`generated: {by: {OKF_PRODUCER_ACTOR}, at: }\` and drop any superseded legacy \`timestamp\` field from that concept; otherwise leave existing \`generated\` and \`timestamp\` values untouched. - Produce valid YAML. Do not leave placeholder text or explanatory comments in written files. - Preserve all existing producer-defined front matter fields when updating a concept. Unknown extension fields are valid OKF and must survive round trips. Change metadata only when the underlying fact or meaningful content changes. - The description field is especially useful for retrieval tools. When present, make it clear, detailed, and optimized for search. diff --git a/src/agent/prompts/personal.ts b/src/agent/prompts/personal.ts index ef139181..4f1f7509 100644 --- a/src/agent/prompts/personal.ts +++ b/src/agent/prompts/personal.ts @@ -94,8 +94,8 @@ Security and privacy rules: Front matter requirements (OKF): - Every non-reserved Markdown concept file you create or update under ~/.openwiki/wiki (the current virtual filesystem root /), including the temporary /_plan.md file, MUST begin with OKF-compliant YAML front matter. -- The front matter MUST follow the Google Knowledge Catalog OKF v0.1 schema. -- \`index.md\` and \`log.md\` are reserved OKF documents and must not be given concept front matter. Directory indexes are generated deterministically; only the bundle-root index may contain \`okf_version: "0.1"\` front matter. +- The front matter MUST follow the Google Knowledge Catalog OKF v0.2 schema. +- \`index.md\` and \`log.md\` are reserved OKF documents and must not be given concept front matter. Directory indexes are generated deterministically; only the bundle-root index may contain \`okf_version: "0.2"\` front matter. - Use this formatter at the very beginning of concept files, replacing placeholders with real values and omitting optional fields that do not apply: @@ -105,14 +105,14 @@ title: description: resource: tags: [, , …] # Optional -timestamp: +generated: {by: , at: } # Optional # Producer-defined extension fields are allowed. --- - Only \`type\` is required. Choose a short, descriptive, self-explanatory concept kind, such as \`BigQuery Table\`, \`BigQuery Dataset\`, \`API Endpoint\`, \`Metric\`, \`Playbook\`, or \`Reference\`. Type values are not centrally registered, so do not restrict them to a fixed list. - Recommended fields, in priority order, are: \`title\`, a human-readable display name; \`description\`, a one to two sentence summary optimized for search and retrieval; \`resource\`, the canonical URI of the underlying asset when one exists; and \`tags\`, a YAML list of short cross-cutting category strings. -- \`timestamp\` is an optional ISO 8601 datetime for the last meaningful change. +- \`generated\` optionally records the content's last meaningful change: \`by\` is required within it and names the producing actor (\`/\` for agents, \`human:\` for people, \`process:\` for processes); \`at\` is an ISO 8601 datetime. When you create or meaningfully change a concept, write \`generated: {by: {OKF_PRODUCER_ACTOR}, at: }\` and drop any superseded legacy \`timestamp\` field from that concept; otherwise leave existing \`generated\` and \`timestamp\` values untouched. - Produce valid YAML. Do not leave placeholder text or explanatory comments in written files. - Preserve all existing producer-defined front matter fields when updating a concept. Unknown extension fields are valid OKF and must survive round trips. Change metadata only when the underlying fact or meaningful content changes. - The description field is especially useful for retrieval tools. When present, make it clear, detailed, and optimized for search. @@ -286,8 +286,8 @@ OKF relationship modeling: Front matter requirements (OKF): - Every non-reserved Markdown concept file you create or update under ~/.openwiki/wiki (the current virtual filesystem root /), including the temporary /_plan.md file, MUST begin with OKF-compliant YAML front matter. -- The front matter MUST follow the Google Knowledge Catalog OKF v0.1 schema. -- \`index.md\` and \`log.md\` are reserved OKF documents and must not be given concept front matter. Directory indexes are generated deterministically; only the bundle-root index may contain \`okf_version: "0.1"\` front matter. +- The front matter MUST follow the Google Knowledge Catalog OKF v0.2 schema. +- \`index.md\` and \`log.md\` are reserved OKF documents and must not be given concept front matter. Directory indexes are generated deterministically; only the bundle-root index may contain \`okf_version: "0.2"\` front matter. - Use this formatter at the very beginning of concept files, replacing placeholders with real values and omitting optional fields that do not apply: @@ -297,14 +297,14 @@ title: description: resource: tags: [, , …] # Optional -timestamp: +generated: {by: , at: } # Optional # Producer-defined extension fields are allowed. --- - Only \`type\` is required. Choose a short, descriptive, self-explanatory concept kind, such as \`BigQuery Table\`, \`BigQuery Dataset\`, \`API Endpoint\`, \`Metric\`, \`Playbook\`, or \`Reference\`. Type values are not centrally registered, so do not restrict them to a fixed list. - Recommended fields, in priority order, are: \`title\`, a human-readable display name; \`description\`, a one to two sentence summary optimized for search and retrieval; \`resource\`, the canonical URI of the underlying asset when one exists; and \`tags\`, a YAML list of short cross-cutting category strings. -- \`timestamp\` is an optional ISO 8601 datetime for the last meaningful change. +- \`generated\` optionally records the content's last meaningful change: \`by\` is required within it and names the producing actor (\`/\` for agents, \`human:\` for people, \`process:\` for processes); \`at\` is an ISO 8601 datetime. When you create or meaningfully change a concept, write \`generated: {by: {OKF_PRODUCER_ACTOR}, at: }\` and drop any superseded legacy \`timestamp\` field from that concept; otherwise leave existing \`generated\` and \`timestamp\` values untouched. - Produce valid YAML. Do not leave placeholder text or explanatory comments in written files. - Preserve all existing producer-defined front matter fields when updating a concept. Unknown extension fields are valid OKF and must survive round trips. Change metadata only when the underlying fact or meaningful content changes. - The description field is especially useful for retrieval tools. When present, make it clear, detailed, and optimized for search. @@ -514,8 +514,8 @@ OKF relationship modeling: Front matter requirements (OKF): - Every non-reserved Markdown concept file you create or update under ~/.openwiki/wiki (the current virtual filesystem root /), including the temporary /_plan.md file, MUST begin with OKF-compliant YAML front matter. -- The front matter MUST follow the Google Knowledge Catalog OKF v0.1 schema. -- \`index.md\` and \`log.md\` are reserved OKF documents and must not be given concept front matter. Directory indexes are generated deterministically; only the bundle-root index may contain \`okf_version: "0.1"\` front matter. +- The front matter MUST follow the Google Knowledge Catalog OKF v0.2 schema. +- \`index.md\` and \`log.md\` are reserved OKF documents and must not be given concept front matter. Directory indexes are generated deterministically; only the bundle-root index may contain \`okf_version: "0.2"\` front matter. - Use this formatter at the very beginning of concept files, replacing placeholders with real values and omitting optional fields that do not apply: @@ -525,14 +525,14 @@ title: description: resource: tags: [, , …] # Optional -timestamp: +generated: {by: , at: } # Optional # Producer-defined extension fields are allowed. --- - Only \`type\` is required. Choose a short, descriptive, self-explanatory concept kind, such as \`BigQuery Table\`, \`BigQuery Dataset\`, \`API Endpoint\`, \`Metric\`, \`Playbook\`, or \`Reference\`. Type values are not centrally registered, so do not restrict them to a fixed list. - Recommended fields, in priority order, are: \`title\`, a human-readable display name; \`description\`, a one to two sentence summary optimized for search and retrieval; \`resource\`, the canonical URI of the underlying asset when one exists; and \`tags\`, a YAML list of short cross-cutting category strings. -- \`timestamp\` is an optional ISO 8601 datetime for the last meaningful change. +- \`generated\` optionally records the content's last meaningful change: \`by\` is required within it and names the producing actor (\`/\` for agents, \`human:\` for people, \`process:\` for processes); \`at\` is an ISO 8601 datetime. When you create or meaningfully change a concept, write \`generated: {by: {OKF_PRODUCER_ACTOR}, at: }\` and drop any superseded legacy \`timestamp\` field from that concept; otherwise leave existing \`generated\` and \`timestamp\` values untouched. - Produce valid YAML. Do not leave placeholder text or explanatory comments in written files. - Preserve all existing producer-defined front matter fields when updating a concept. Unknown extension fields are valid OKF and must survive round trips. Change metadata only when the underlying fact or meaningful content changes. - The description field is especially useful for retrieval tools. When present, make it clear, detailed, and optimized for search. diff --git a/src/okf/frontmatter.ts b/src/okf/frontmatter.ts index b6fb9d59..3a0c0406 100644 --- a/src/okf/frontmatter.ts +++ b/src/okf/frontmatter.ts @@ -2,7 +2,9 @@ import type { BackendProtocolV2 } from "deepagents"; import { parse } from "yaml"; /** - * OKF fields that, when present, must be non-empty string values. + * OKF fields that, when present, must be non-empty string values. `timestamp` + * is the field OKF v0.2 supersedes with `generated.at`; it stays tolerated + * because consumers may fall back to it on v0.1 pages (SPEC §13.1). */ const OKF_STRING_FIELDS = [ "type", @@ -12,6 +14,16 @@ const OKF_STRING_FIELDS = [ "timestamp", ]; +/** + * Lifecycle states defined by OKF v0.2 §5.4; an absent `status` means stable. + */ +const OKF_STATUS_VALUES = ["draft", "stable", "deprecated"]; + +/** + * Matches the absolute `YYYY-MM-DD` date `stale_after` requires (§5.5). + */ +const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/u; + /** * Extension field flagging front matter OpenWiki derived deterministically. */ @@ -148,10 +160,101 @@ export function validateOkfFrontmatter(content: string): FrontmatterValidation { ), ); } + validateTrustFamilies(fields, issues); return issues.length === 0 ? { valid: true } : { issues, valid: false }; } +/** + * Validates the optional OKF v0.2 provenance, trust, and lifecycle families + * (SPEC §5) when present. Only the shape OKF specifies is checked; extra keys + * inside entries stay tolerated so producer extensions survive round trips. + */ +function validateTrustFamilies( + fields: Record, + issues: FrontmatterIssue[], +): void { + if (Object.hasOwn(fields, "generated") && !isActorEvent(fields.generated)) { + issues.push( + issue( + "invalid_generated", + "Field `generated` must be a mapping with a non-empty string `by` (actor) and an optional string `at` (ISO 8601 datetime).", + ), + ); + } + if (Object.hasOwn(fields, "verified")) { + // §5.2: a single verifier may be a bare mapping; read it as a one-element list. + const events = Array.isArray(fields.verified) + ? fields.verified + : [fields.verified]; + if (!events.every(isActorEvent)) { + issues.push( + issue( + "invalid_verified", + "Field `verified` must be a `{by, at}` mapping or a YAML list of them, each with a non-empty string `by` (actor).", + ), + ); + } + } + if ( + Object.hasOwn(fields, "sources") && + (!Array.isArray(fields.sources) || + fields.sources.some( + (entry) => !isRecord(entry) || !isNonEmptyString(entry.resource), + )) + ) { + issues.push( + issue( + "invalid_sources", + "Field `sources` must be a YAML list of mappings, each with a non-empty string `resource`.", + ), + ); + } + if ( + Object.hasOwn(fields, "status") && + (typeof fields.status !== "string" || + !OKF_STATUS_VALUES.includes(fields.status)) + ) { + issues.push( + issue( + "invalid_status", + "Field `status` must be one of `draft`, `stable`, or `deprecated`.", + ), + ); + } + if ( + Object.hasOwn(fields, "stale_after") && + (typeof fields.stale_after !== "string" || + !ISO_DATE.test(fields.stale_after)) + ) { + issues.push( + issue( + "invalid_stale_after", + "Field `stale_after` must be an absolute `YYYY-MM-DD` date.", + ), + ); + } +} + +/** + * Narrows a value to an OKF `{by, at}` event: a mapping whose `by` is a + * non-empty actor string and whose `at`, when present, is a non-empty string. + */ +function isActorEvent(value: unknown): boolean { + return ( + isRecord(value) && + isNonEmptyString(value.by) && + (!Object.hasOwn(value, "at") || isNonEmptyString(value.at)) + ); +} + +/** + * Reports whether a value is a non-empty, non-blank string. + */ +function isNonEmptyString(value: unknown): value is string { + return typeof value === "string" && value.trim() !== ""; +} + /** * Reads a persisted Markdown file and validates its final front matter. */ diff --git a/src/okf/index-sync.ts b/src/okf/index-sync.ts index 5b24a3fd..b83b1ba2 100644 --- a/src/okf/index-sync.ts +++ b/src/okf/index-sync.ts @@ -233,7 +233,7 @@ function renderIndex( ] .filter(Boolean) .join("\n\n"); - const version = isRoot ? '---\nokf_version: "0.1"\n---\n\n' : ""; + const version = isRoot ? '---\nokf_version: "0.2"\n---\n\n' : ""; return `${version}${sections || `# ${labels.files}`}\n`; } diff --git a/test/agent/frontmatter-validator.test.ts b/test/agent/frontmatter-validator.test.ts index 1d4fdf0e..bcb59704 100644 --- a/test/agent/frontmatter-validator.test.ts +++ b/test/agent/frontmatter-validator.test.ts @@ -54,7 +54,7 @@ describe("validateOkfFrontmatter", () => { ).toEqual({ valid: true }); }); - test("accepts OKF timestamp and producer-defined extension fields", () => { + test("accepts the legacy v0.1 timestamp and producer-defined extension fields", () => { expect( validateOkfFrontmatter( markdown( @@ -63,13 +63,77 @@ describe("validateOkfFrontmatter", () => { 'timestamp: "2026-07-16T20:00:00Z"', "author: steve", "confidence: 0.95", - "status: verified", + "review_state: verified", ].join("\n"), ), ), ).toEqual({ valid: true }); }); + test("accepts the v0.2 provenance, trust, and lifecycle families", () => { + expect( + validateOkfFrontmatter( + markdown( + [ + "type: Reference", + "generated: {by: openwiki/0.3.0, at: 2026-08-04T09:00:00Z}", + "verified:", + " - {by: human:ahormati, at: 2026-08-05T09:00:00Z}", + " - {by: process:finance-nightly, at: 2026-08-06T02:00:00Z}", + "sources:", + " - id: spec", + " resource: https://example.com/spec", + " author: team:docs", + " usage_count: 5000", + " last_modified: 2026-05-30", + "usage_window: {from: 2026-06-01, to: 2026-06-30}", + "status: stable", + "stale_after: 2026-09-23", + ].join("\n"), + ), + ), + ).toEqual({ valid: true }); + }); + + test("accepts a bare verified mapping as a one-element list", () => { + // §5.2: a single verifier may be written without the list dash. + expect( + validateOkfFrontmatter( + markdown( + "type: Reference\nverified: {by: human:ahormati, at: 2026-08-05T09:00:00Z}", + ), + ), + ).toEqual({ valid: true }); + }); + + test("reports malformed v0.2 family fields", () => { + const result = validateOkfFrontmatter( + markdown( + [ + "type: Reference", + "generated: 2026-08-04", + "verified:", + " - {at: 2026-08-05T09:00:00Z}", + "sources:", + " - {id: spec}", + "status: verified", + "stale_after: soon", + ].join("\n"), + ), + ); + + expect(result).toMatchObject({ + issues: [ + { code: "invalid_generated" }, + { code: "invalid_verified" }, + { code: "invalid_sources" }, + { code: "invalid_status" }, + { code: "invalid_stale_after" }, + ], + valid: false, + }); + }); + test("reports deterministic delimiter and required-field issues", () => { expect(validateOkfFrontmatter("# Page")).toEqual({ issues: [ diff --git a/test/agent/index-middleware.test.ts b/test/agent/index-middleware.test.ts index 0c0aab37..713363a9 100644 --- a/test/agent/index-middleware.test.ts +++ b/test/agent/index-middleware.test.ts @@ -56,7 +56,7 @@ describe("synchronizeWikiIndexes", () => { "utf8", ); - expect(rootIndex).toContain('okf_version: "0.1"'); + expect(rootIndex).toContain('okf_version: "0.2"'); expect(rootIndex).not.toContain("type: Documentation Index"); expect(rootIndex).not.toMatch(/^tags:/mu); expect(rootIndex).toContain("- [Quickstart](quickstart.md) - Start here."); @@ -140,7 +140,7 @@ describe("synchronizeWikiIndexes", () => { path.join(rootDir, "openwiki/architecture/index.md"), "utf8", ); - expect(rootIndex).toMatch(/^---\nokf_version: "0\.1"\n---\n\n# Files/mu); + expect(rootIndex).toMatch(/^---\nokf_version: "0\.2"\n---\n\n# Files/mu); expect(rootIndex).not.toContain("type: Documentation Index"); expect(nestedIndex).toMatch(/^# Files/mu); expect(nestedIndex).not.toMatch(/^---/u); diff --git a/test/agent/prompt-okf.test.ts b/test/agent/prompt-okf.test.ts index 5cb93f78..74c828cf 100644 --- a/test/agent/prompt-okf.test.ts +++ b/test/agent/prompt-okf.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "vitest"; import { createSystemPrompt } from "../../src/agent/prompt.ts"; +import { OPENWIKI_VERSION } from "../../src/version.ts"; describe("createSystemPrompt OKF guidance", () => { test("keeps init requirements compact and update preservation explicit", () => { @@ -7,7 +8,9 @@ describe("createSystemPrompt OKF guidance", () => { const update = createSystemPrompt("update", "repository"); expect(init).toContain("Only type is required by OKF"); - expect(init).toContain("timestamp: "); + expect(init).toContain( + "generated: {by: , at: } # optional", + ); expect(init).toContain("index.md and log.md are reserved"); expect(init).not.toContain( "Preserve all existing producer-defined front matter fields", @@ -23,4 +26,26 @@ describe("createSystemPrompt OKF guidance", () => { "do not add front matter fields outside the formatter above", ); }); + + test("targets OKF v0.2 and the generated trust field in every mode", () => { + const init = createSystemPrompt("init", "repository"); + const update = createSystemPrompt("update", "repository"); + const personalUpdate = createSystemPrompt("update", "local-wiki"); + + for (const prompt of [init, update, personalUpdate]) { + // v0.1's timestamp is superseded by generated (OKF v0.2 §13.1). + expect(prompt).not.toContain("v0.1"); + expect(prompt).not.toContain("timestamp: <"); + expect(prompt).toContain(`by: openwiki/${OPENWIKI_VERSION}`); + expect(prompt).not.toContain("{OKF_PRODUCER_ACTOR}"); + } + expect(init).toContain("valid OKF v0.2 YAML front matter"); + for (const prompt of [update, personalUpdate]) { + expect(prompt).toContain("Google Knowledge Catalog OKF v0.2 schema"); + expect(prompt).toContain('okf_version: "0.2"'); + expect(prompt).toContain( + "generated: {by: , at: } # Optional", + ); + } + }); });