tgo-wiki 0.1.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 (68) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +255 -0
  3. package/docs/mcp-usage.md +631 -0
  4. package/docs/v0-acceptance.md +105 -0
  5. package/docs/v0-delivery-checklist.md +57 -0
  6. package/docs/v1-acceptance.md +39 -0
  7. package/docs/v2-acceptance.md +165 -0
  8. package/package.json +69 -0
  9. package/packages/core/src/config/config-loader.ts +109 -0
  10. package/packages/core/src/config/defaults.ts +74 -0
  11. package/packages/core/src/config/workspace-resolver.ts +40 -0
  12. package/packages/core/src/documents/command-document-parser.ts +206 -0
  13. package/packages/core/src/documents/document-id.ts +26 -0
  14. package/packages/core/src/documents/document-parser-registry.ts +126 -0
  15. package/packages/core/src/documents/document-service.ts +656 -0
  16. package/packages/core/src/documents/document-store.ts +132 -0
  17. package/packages/core/src/documents/document-types.ts +33 -0
  18. package/packages/core/src/documents/pdf-text-parser.ts +35 -0
  19. package/packages/core/src/documents/text-markdown-parser.ts +50 -0
  20. package/packages/core/src/errors.ts +46 -0
  21. package/packages/core/src/git/git-service.ts +68 -0
  22. package/packages/core/src/index.ts +38 -0
  23. package/packages/core/src/markdown/markdown-scanner.ts +90 -0
  24. package/packages/core/src/permissions/permission-service.ts +50 -0
  25. package/packages/core/src/publish/publish-service.ts +142 -0
  26. package/packages/core/src/result.ts +13 -0
  27. package/packages/core/src/services/session-workflow-service.ts +493 -0
  28. package/packages/core/src/services/wiki-service.ts +119 -0
  29. package/packages/core/src/services/workspace-service.ts +223 -0
  30. package/packages/core/src/session/session-id.ts +14 -0
  31. package/packages/core/src/session/session-service.ts +77 -0
  32. package/packages/core/src/session/session-store.ts +91 -0
  33. package/packages/core/src/session/session-types.ts +17 -0
  34. package/packages/core/src/sources/source-id.ts +19 -0
  35. package/packages/core/src/sources/source-paths.ts +15 -0
  36. package/packages/core/src/sources/source-service.ts +416 -0
  37. package/packages/core/src/sources/source-types.ts +77 -0
  38. package/packages/core/src/sources/source-validator.ts +132 -0
  39. package/packages/core/src/sources/source-writer.ts +419 -0
  40. package/packages/core/src/validation/frontmatter-validator.ts +128 -0
  41. package/packages/core/src/validation/link-validator.ts +55 -0
  42. package/packages/core/src/validation/path-validator.ts +65 -0
  43. package/packages/core/src/validation/source-reference-validator.ts +191 -0
  44. package/packages/core/src/validation/validation-service.ts +106 -0
  45. package/packages/core/src/vfs/vfs-command-parser.ts +69 -0
  46. package/packages/core/src/vfs/vfs-service.ts +498 -0
  47. package/packages/core/src/web/html-to-markdown.ts +144 -0
  48. package/packages/core/src/web/static-web-fetcher.ts +537 -0
  49. package/packages/core/src/web/web-id.ts +26 -0
  50. package/packages/core/src/web/web-ingestion-service.ts +335 -0
  51. package/packages/core/src/web/web-paths.ts +6 -0
  52. package/packages/core/src/web/web-types.ts +33 -0
  53. package/packages/server/src/cli.ts +56 -0
  54. package/packages/server/src/context.ts +7 -0
  55. package/packages/server/src/index.ts +2 -0
  56. package/packages/server/src/mcp-server.ts +111 -0
  57. package/packages/server/src/schemas/documents.ts +17 -0
  58. package/packages/server/src/schemas/read.ts +16 -0
  59. package/packages/server/src/schemas/session.ts +31 -0
  60. package/packages/server/src/schemas/sources.ts +12 -0
  61. package/packages/server/src/schemas/web.ts +23 -0
  62. package/packages/server/src/tools/document-tools.ts +46 -0
  63. package/packages/server/src/tools/publish-tools.ts +33 -0
  64. package/packages/server/src/tools/read-tools.ts +52 -0
  65. package/packages/server/src/tools/response.ts +24 -0
  66. package/packages/server/src/tools/session-tools.ts +100 -0
  67. package/packages/server/src/tools/source-tools.ts +32 -0
  68. package/packages/server/src/tools/web-tools.ts +26 -0
@@ -0,0 +1,105 @@
1
+ # Agent Wiki v0 Acceptance Gate
2
+
3
+ Date: 2026-06-11
4
+
5
+ ## Purpose
6
+
7
+ This document defines the local acceptance gate for the v0 MCP-first Agent Wiki slice. Run this gate before treating v0 as ready for handoff, release, or follow-on feature work.
8
+
9
+ v0 is now historical scope. For the current v2 release gate, use [v2-acceptance.md](./v2-acceptance.md), which extends the v1 document ingestion, source read, and fast-forward publish baseline with web ingestion.
10
+
11
+ The gate is intentionally black-box at the outer edge: the dedicated black-box package starts the public CLI and talks to the MCP server over stdio JSON-RPC. It must not import `@tgo-wiki/core`, `@tgo-wiki/server`, or internal service modules.
12
+
13
+ ## Required Commands
14
+
15
+ For historical v0 verification, run from the repository root:
16
+
17
+ ```bash
18
+ bun install
19
+ bun run verify:v0
20
+ ```
21
+
22
+ Acceptance requires every command to exit with code 0.
23
+
24
+ At the time of v0 delivery, `bun run release:check` was the single local release-readiness gate and expanded to:
25
+
26
+ ```bash
27
+ bun run verify:v0 && bun run smoke:pack && bun pm pack --dry-run
28
+ ```
29
+
30
+ `bun run verify:v0` runs the full local v0 gate:
31
+
32
+ ```bash
33
+ bun run check && bun run test:blackbox
34
+ ```
35
+
36
+ `bun run check` runs the default unit and integration test suite for `packages/core` and `packages/server`, then runs `tsc --noEmit`.
37
+
38
+ `bun run test:blackbox` runs the independent `packages/blackbox-tests` package. This suite is explicit by design and is not part of the default `bun run test` command.
39
+
40
+ `bun run smoke:pack` creates a tarball with `bun pm pack`, installs that tarball into a temporary consumer project, and exercises the installed `tgo-wiki` bin for CLI init/status plus MCP `initialize` and `tools/list`.
41
+
42
+ `bun run smoke:cli` runs the same CLI/MCP flow through a faster `file:` install. It is useful during local development, while `smoke:pack` is the release-readiness gate.
43
+
44
+ The GitHub Actions workflow at `.github/workflows/v0-acceptance.yml` runs `bun run check`, `bun run test:blackbox`, and `bun run smoke:pack` as separate jobs.
45
+
46
+ ## Historical v0 Black-Box Coverage
47
+
48
+ The black-box package must verify these public behaviors through `packages/server/src/cli.ts` only:
49
+
50
+ - `tgo-wiki init --workspace <path>` creates a usable local workspace.
51
+ - `tgo-wiki status --workspace <path>` reports the stable branch, clean status, and session count.
52
+ - MCP `initialize` and `tools/list` expose exactly the v0 tool surface.
53
+ - `wiki_read` reads stable Markdown content.
54
+ - `vfs_exec` executes allowed read-only VFS commands.
55
+ - A writer can complete the session flow:
56
+ - `wiki_session_start`
57
+ - `wiki_session_patch`
58
+ - `wiki_session_validate`
59
+ - `wiki_session_diff`
60
+ - `wiki_session_commit`
61
+ - A committed session branch does not mutate stable content.
62
+ - A reader receives permission errors for every writer-only session tool.
63
+ - Read tools reject unsupported refs and path traversal attempts.
64
+ - Session patch rejects unsafe paths and unified patch payloads.
65
+ - Invalid Markdown produces validation errors and cannot be committed.
66
+ - Empty session commits return a typed tool error.
67
+
68
+ ## v0 Scope Guard
69
+
70
+ The following tools are intentionally out of scope for v0 and must not appear in `tools/list`:
71
+
72
+ - `wiki_publish_session`
73
+ - `wiki_rollback`
74
+ - `wiki_search`
75
+ - document ingestion tools
76
+ - web ingestion tools
77
+ - QA or semantic search tools
78
+
79
+ `publisher` and `admin` roles may exist as higher-privilege roles for the implemented read/write tools, but v0 must not expose publish or rollback behavior.
80
+
81
+ v1 historically superseded this guard by adding document ingestion, source read tools, and `wiki_publish_session`. v2 is the current release gate and adds web ingestion; see [v2-acceptance.md](./v2-acceptance.md) for the active public behavior list.
82
+
83
+ ## Manual Spot Check
84
+
85
+ Use this only when changing CLI startup, MCP transport, or packaging:
86
+
87
+ ```bash
88
+ workspace="$(mktemp -d)"
89
+ bun packages/server/src/cli.ts init --workspace "$workspace"
90
+ bun packages/server/src/cli.ts status --workspace "$workspace"
91
+ bun packages/server/src/cli.ts mcp --workspace "$workspace" --role reader
92
+ ```
93
+
94
+ For MCP manual checks, send `initialize`, `notifications/initialized`, and `tools/list` messages over stdio JSON-RPC. Prefer the automated black-box package for repeatable verification.
95
+
96
+ ## Failure Policy
97
+
98
+ If any command in the required gate fails:
99
+
100
+ 1. Do not merge or hand off the change.
101
+ 2. Identify whether the failure is in unit/integration tests, type checking, or black-box behavior.
102
+ 3. Fix the smallest root cause.
103
+ 4. Re-run the full required command set from this document.
104
+
105
+ Black-box failures should be treated as public contract failures unless investigation proves the test is asserting behavior outside v0 scope.
@@ -0,0 +1,57 @@
1
+ # Agent Wiki v0 Delivery Checklist
2
+
3
+ Date: 2026-06-12
4
+
5
+ ## v0 Scope
6
+
7
+ This checklist summarizes the historical v0 handoff and delivery state for the MCP-first v0 slice.
8
+
9
+ v0 is historical scope. v1 superseded and extended it with document ingestion, source reads, and fast-forward publish. v2 is the current release gate and adds web ingestion; use [v2-acceptance.md](./v2-acceptance.md) for current acceptance.
10
+
11
+ Implemented:
12
+
13
+ - Local wiki workspace initialization and status inspection.
14
+ - MCP stdio server startup for reader and writer roles.
15
+ - Stable wiki reads through `wiki_read`, `wiki_channel_status`, and read-only `vfs_exec`.
16
+ - Isolated writer sessions with start, full-content patch, validate, diff, and commit.
17
+ - Reader/writer permission enforcement.
18
+ - Markdown validation for frontmatter, H1, path safety, and links.
19
+ - Public black-box coverage through CLI and MCP stdio JSON-RPC.
20
+ - Packed tarball smoke coverage for the installed `tgo-wiki` bin.
21
+
22
+ Deferred:
23
+
24
+ - `publish/rollback` behavior and tools.
25
+ - Search, QA, document ingestion, and web ingestion.
26
+ - Remote git, pull request integration, and web UI.
27
+
28
+ ## Required Verification
29
+
30
+ For the current release gate, run:
31
+
32
+ ```bash
33
+ bun install --frozen-lockfile
34
+ bun run release:check
35
+ ```
36
+
37
+ For v0 delivery, `bun run release:check` ran:
38
+
39
+ ```bash
40
+ bun run verify:v0 && bun run smoke:pack && bun pm pack --dry-run
41
+ ```
42
+
43
+ The release gate must report:
44
+
45
+ - default unit/integration tests passing;
46
+ - independent black-box tests passing;
47
+ - packed tarball smoke passing through the installed `.bin/tgo-wiki`;
48
+ - pack dry-run listing the expected narrow file set.
49
+
50
+ ## Handoff Notes
51
+
52
+ - Use `docs/mcp-usage.md` for MCP client setup and tool examples.
53
+ - Use `docs/v2-acceptance.md` as the current release acceptance policy.
54
+ - Use `docs/v1-acceptance.md` as the historical v1 acceptance policy.
55
+ - Use `docs/v0-acceptance.md` as the historical v0 acceptance policy.
56
+ - Use `CHANGELOG.md` as the release-note draft source.
57
+ - Keep v0 publish/rollback deferral notes historical; v1 adds ff-only session publish while rollback remains deferred.
@@ -0,0 +1,39 @@
1
+ # Agent Wiki v1 Acceptance Gate
2
+
3
+ Date: 2026-06-13
4
+
5
+ ## Purpose
6
+
7
+ This document defines the historical v1 release acceptance gate for Agent Wiki. For the current v2 release gate, use [v2-acceptance.md](./v2-acceptance.md). v1 adds document ingestion on top of the v0 local git, worktree, session, validation, commit, and MCP surface.
8
+
9
+ The gate verifies that agents can upload raw document blobs into workspace state, parse those documents into git-tracked source files inside an isolated session worktree, cite those sources from wiki page frontmatter, commit the combined wiki/source changes, and fast-forward publish the clean committed session to stable with the appropriate role.
10
+
11
+ The v1.1 slice supports direct ingestion for `application/pdf`, `text/markdown`, `text/x-markdown`, and `text/plain`.
12
+
13
+ ## Required Commands
14
+
15
+ Run from the repository root:
16
+
17
+ ```bash
18
+ bun install --frozen-lockfile
19
+ bun run release:check
20
+ ```
21
+
22
+ Acceptance requires every command to exit with code 0.
23
+
24
+ ## Required Public Behaviors
25
+
26
+ - `wiki_publish_session` fast-forwards stable for committed clean sessions.
27
+ - `document_upload` stores raw blobs under workspace state, not git.
28
+ - `document_upload` accepts PDF, Markdown, and plain text documents.
29
+ - `document_parse` writes `sources/<document_id>/raw.md` and `metadata.json` into the session worktree.
30
+ - `document_parse` preserves Markdown uploads as raw Markdown and wraps plain text uploads in readable Markdown.
31
+ - Workspace config can enable command-backed parsers with explicit supported MIME types.
32
+ - Command parser stdout is written as source raw Markdown; command failures return `parse_failed`.
33
+ - `source_list` and `source_read` read stable and session refs.
34
+ - `wiki_session_patch` rejects `sources/**` with `source_write_denied`.
35
+ - `wiki_session_validate` accepts wiki pages with valid `sources` frontmatter and rejects missing source IDs.
36
+ - `wiki_session_commit` commits both wiki pages and source files.
37
+ - Reader can read sources but cannot upload or parse documents.
38
+ - Writer can upload and parse but cannot publish.
39
+ - Publisher and admin can publish.
@@ -0,0 +1,165 @@
1
+ # Agent Wiki v2 Acceptance Gate
2
+
3
+ Date: 2026-06-13
4
+
5
+ ## Purpose
6
+
7
+ This document defines the current release acceptance gate for Agent Wiki v2. v2 adds static web ingestion on top of the v1 local git, worktree, session, validation, source, commit, and publish surface.
8
+
9
+ The gate verifies that agents can fetch one HTML page into an isolated session, read the generated source, cite the returned source id from wiki page frontmatter, validate and commit the combined wiki/source changes, fast-forward publish the clean committed session to stable, and read the published source from stable.
10
+
11
+ ## Required Commands
12
+
13
+ Run from the repository root:
14
+
15
+ ```bash
16
+ bun install --frozen-lockfile
17
+ bun run release:check
18
+ ```
19
+
20
+ Acceptance requires every command to exit with code 0.
21
+
22
+ ## Required Public Behaviors
23
+
24
+ - `web_fetch` is available to publisher and admin roles only.
25
+ - `web_fetch` fetches exactly one absolute public `http:` or `https:` URL with the static fetcher.
26
+ - `web_fetch` rejects private, loopback, link-local, multicast, and reserved network targets by default, including redirects to those targets. Workspace config may explicitly allow private hosts with `web.allowedPrivateHosts`.
27
+ - `web_fetch` stores the original HTML blob outside git under `state/web/blobs/sha256/<sha256>`.
28
+ - `web_fetch` writes `sources/<source_id>/raw.md` and `sources/<source_id>/metadata.json` into the session worktree.
29
+ - `web_fetch` returns `source_id`, `version`, `raw_markdown_path`, `metadata_path`, `html_blob_sha256`, `status`, optional `title`, and `warnings`.
30
+ - Existing source APIs expose the generated raw Markdown and metadata. `source_read` still accepts `document_id`; pass the returned web `source_id` as that value.
31
+ - `source_list` and `source_read` read web sources from stable and session refs.
32
+ - `wiki_session_patch` can cite the returned source id in page `sources` frontmatter.
33
+ - `wiki_session_validate` accepts valid web source citations and rejects missing source IDs.
34
+ - `wiki_session_commit` commits wiki pages and generated source files together.
35
+ - `wiki_publish_session` fast-forwards stable for committed clean sessions.
36
+ - Reader and writer roles can read permitted sources but cannot call `web_fetch`.
37
+
38
+ ## Required Flow
39
+
40
+ Start the MCP server with `--role publisher` or `--role admin`, then initialize MCP.
41
+
42
+ ```text
43
+ wiki_session_start
44
+ web_fetch
45
+ source_read with ref = session_id
46
+ wiki_session_patch with frontmatter.sources citing returned source_id
47
+ wiki_session_validate
48
+ wiki_session_diff
49
+ wiki_session_commit
50
+ wiki_publish_session as publisher/admin
51
+ source_read from stable
52
+ ```
53
+
54
+ For separated duties, a writer can start the session, patch, validate, diff, and commit. A publisher or admin performs `web_fetch` and the final publish step.
55
+
56
+ ## `web_fetch` Example
57
+
58
+ Input:
59
+
60
+ ```json
61
+ {
62
+ "session_id": "SESSION_ID",
63
+ "url": "https://example.com/docs"
64
+ }
65
+ ```
66
+
67
+ Result:
68
+
69
+ ```json
70
+ {
71
+ "source_id": "web_20260613_ab12cd34ef567890",
72
+ "version": 1,
73
+ "raw_markdown_path": "sources/web_20260613_ab12cd34ef567890/raw.md",
74
+ "metadata_path": "sources/web_20260613_ab12cd34ef567890/metadata.json",
75
+ "html_blob_sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
76
+ "status": "fetched",
77
+ "title": "Example Docs",
78
+ "warnings": []
79
+ }
80
+ ```
81
+
82
+ The original HTML bytes for this result are stored outside git at:
83
+
84
+ ```text
85
+ state/web/blobs/sha256/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
86
+ ```
87
+
88
+ The session worktree receives:
89
+
90
+ ```text
91
+ sources/web_20260613_ab12cd34ef567890/raw.md
92
+ sources/web_20260613_ab12cd34ef567890/metadata.json
93
+ ```
94
+
95
+ ## Source Read Example
96
+
97
+ Use the returned `source_id` as `document_id` when reading through the existing source API:
98
+
99
+ ```json
100
+ {
101
+ "document_id": "web_20260613_ab12cd34ef567890",
102
+ "ref": "SESSION_ID"
103
+ }
104
+ ```
105
+
106
+ Result shape:
107
+
108
+ ```json
109
+ {
110
+ "document_id": "web_20260613_ab12cd34ef567890",
111
+ "ref": "SESSION_ID",
112
+ "metadata": {
113
+ "documentId": "web_20260613_ab12cd34ef567890",
114
+ "version": 1,
115
+ "sourceType": "web",
116
+ "url": "https://example.com/docs",
117
+ "finalUrl": "https://example.com/docs",
118
+ "title": "Example Docs",
119
+ "contentType": "text/html; charset=utf-8",
120
+ "statusCode": 200,
121
+ "htmlBlobSha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
122
+ "rawMarkdownPath": "sources/web_20260613_ab12cd34ef567890/raw.md",
123
+ "fetcher": {
124
+ "name": "static-fetch",
125
+ "version": "1.0.0"
126
+ },
127
+ "fetchMetadata": {
128
+ "htmlBytes": 1234,
129
+ "markdownBytes": 567
130
+ },
131
+ "status": "fetched"
132
+ },
133
+ "raw_markdown": "# Example Docs\n\n> Source web page: https://example.com/docs\n> Final URL: https://example.com/docs\n> Fetched at: 2026-06-13T00:00:00.000Z\n> Fetcher: static-fetch\n\n..."
134
+ }
135
+ ```
136
+
137
+ ## Wiki Citation Example
138
+
139
+ Patch a normal wiki page with frontmatter that cites the fetched source:
140
+
141
+ ```markdown
142
+ ---
143
+ title: "Example Docs"
144
+ summary: "Notes derived from the fetched web source."
145
+ tags: ["web", "imported"]
146
+ updated: "2026-06-13"
147
+ sources:
148
+ - web_20260613_ab12cd34ef567890
149
+ ---
150
+
151
+ # Example Docs
152
+
153
+ The published page content should cite the fetched source id in frontmatter.
154
+ ```
155
+
156
+ ## Stable Read Requirement
157
+
158
+ After `wiki_session_commit` and `wiki_publish_session`, `source_read` with `ref: "stable"` must return the same source metadata and raw Markdown for the web source:
159
+
160
+ ```json
161
+ {
162
+ "document_id": "web_20260613_ab12cd34ef567890",
163
+ "ref": "stable"
164
+ }
165
+ ```
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "tgo-wiki",
3
+ "version": "0.1.0",
4
+ "description": "Local git-backed Agent Wiki MCP server and CLI.",
5
+ "type": "module",
6
+ "homepage": "https://github.com/tgoai/tgo-wiki#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/tgoai/tgo-wiki.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/tgoai/tgo-wiki/issues"
13
+ },
14
+ "keywords": [
15
+ "agent",
16
+ "bun",
17
+ "cli",
18
+ "knowledge-base",
19
+ "mcp",
20
+ "wiki"
21
+ ],
22
+ "engines": {
23
+ "bun": ">=1.3.0"
24
+ },
25
+ "workspaces": [
26
+ "packages/*"
27
+ ],
28
+ "bin": {
29
+ "tgo-wiki": "./packages/server/src/cli.ts"
30
+ },
31
+ "files": [
32
+ "packages/core/src/**/*.ts",
33
+ "packages/server/src/**/*.ts",
34
+ "!packages/**/*.test.ts",
35
+ "README.md",
36
+ "CHANGELOG.md",
37
+ "docs/mcp-usage.md",
38
+ "docs/v2-acceptance.md",
39
+ "docs/v1-acceptance.md",
40
+ "docs/v0-acceptance.md",
41
+ "docs/v0-delivery-checklist.md"
42
+ ],
43
+ "scripts": {
44
+ "test": "bun test packages/core/src packages/server/src",
45
+ "test:blackbox": "bun test packages/blackbox-tests/src",
46
+ "typecheck": "tsc --noEmit",
47
+ "check": "bun run test && tsc --noEmit",
48
+ "verify:v0": "bun run check && bun run test:blackbox",
49
+ "verify:v1": "bun run check && bun run test:blackbox",
50
+ "verify:v2": "bun run check && bun run test:blackbox",
51
+ "example:mcp": "bun examples/mcp-stdio-client.ts",
52
+ "smoke:cli": "bun scripts/smoke-cli.ts",
53
+ "smoke:pack": "bun scripts/smoke-cli.ts --source pack",
54
+ "release:check": "bun run verify:v2 && bun run smoke:pack && bun pm pack --dry-run",
55
+ "prepublishOnly": "bun run release:check"
56
+ },
57
+ "dependencies": {
58
+ "@cfworker/json-schema": "^4.1.1",
59
+ "@modelcontextprotocol/server": "^2.0.0-alpha.2",
60
+ "gray-matter": "^4.0.3",
61
+ "pdf-parse": "^2.4.5",
62
+ "zod": "^4.4.3"
63
+ },
64
+ "devDependencies": {
65
+ "@types/node": "^25.9.3",
66
+ "bun-types": "^1.3.14",
67
+ "typescript": "^6.0.3"
68
+ }
69
+ }
@@ -0,0 +1,109 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { defaultConfig, type DocumentParserConfig, type WikiConfig } from "./defaults.js";
4
+ import type { WorkspacePaths } from "./workspace-resolver.js";
5
+ import { WikiError } from "../errors.js";
6
+
7
+ type ParsedWikiConfig = Partial<Omit<WikiConfig, "documents">> & {
8
+ documents?: Partial<Omit<WikiConfig["documents"], "parsers">> & {
9
+ parsers?: Record<string, DocumentParserConfig>;
10
+ };
11
+ web?: Partial<WikiConfig["web"]>;
12
+ };
13
+
14
+ export async function loadConfig(paths: WorkspacePaths): Promise<WikiConfig> {
15
+ const configPath = path.join(paths.statePath, "config.json");
16
+
17
+ try {
18
+ const raw = await readFile(configPath, "utf8");
19
+ const parsed = JSON.parse(raw) as ParsedWikiConfig;
20
+ assertOptionalConfigObject("web", parsed.web);
21
+
22
+ const merged = {
23
+ ...defaultConfig,
24
+ ...parsed,
25
+ channels: {
26
+ ...defaultConfig.channels,
27
+ ...parsed.channels,
28
+ stable: {
29
+ ...defaultConfig.channels.stable,
30
+ ...parsed.channels?.stable
31
+ }
32
+ },
33
+ vfs: {
34
+ ...defaultConfig.vfs,
35
+ ...parsed.vfs
36
+ },
37
+ validation: {
38
+ ...defaultConfig.validation,
39
+ ...parsed.validation
40
+ },
41
+ documents: {
42
+ ...defaultConfig.documents,
43
+ ...parsed.documents,
44
+ parsers: mergeParserConfigs(defaultConfig.documents.parsers, parsed.documents?.parsers)
45
+ },
46
+ web: {
47
+ ...defaultConfig.web,
48
+ ...parsed.web
49
+ }
50
+ };
51
+
52
+ validateConfig(merged);
53
+ return merged;
54
+ } catch (error) {
55
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
56
+ return defaultConfig;
57
+ }
58
+
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ function mergeParserConfigs(
64
+ defaultParsers: Record<string, DocumentParserConfig>,
65
+ configuredParsers: Record<string, DocumentParserConfig> = {}
66
+ ): Record<string, DocumentParserConfig> {
67
+ const merged = { ...defaultParsers };
68
+
69
+ for (const [parserName, configuredParser] of Object.entries(configuredParsers)) {
70
+ merged[parserName] = {
71
+ ...defaultParsers[parserName],
72
+ ...configuredParser
73
+ };
74
+ }
75
+
76
+ return merged;
77
+ }
78
+
79
+ function validateConfig(config: WikiConfig): void {
80
+ assertPositiveInteger("web.requestTimeoutMs", config.web.requestTimeoutMs, 120000);
81
+ assertPositiveInteger("web.maxResponseBytes", config.web.maxResponseBytes, 52428800);
82
+
83
+ if (
84
+ !Array.isArray(config.web.allowedPrivateHosts) ||
85
+ config.web.allowedPrivateHosts.some(host => typeof host !== "string" || host.trim().length === 0)
86
+ ) {
87
+ throw new WikiError("validation_failed", "web.allowedPrivateHosts must be an array of non-empty host strings");
88
+ }
89
+ }
90
+
91
+ function assertOptionalConfigObject(fieldName: string, value: unknown): void {
92
+ if (value === undefined) {
93
+ return;
94
+ }
95
+
96
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
97
+ throw new WikiError("validation_failed", `${fieldName} must be an object`);
98
+ }
99
+ }
100
+
101
+ function assertPositiveInteger(fieldName: string, value: unknown, max: number): void {
102
+ if (!Number.isSafeInteger(value) || typeof value !== "number" || value <= 0) {
103
+ throw new WikiError("validation_failed", `${fieldName} must be a positive integer`);
104
+ }
105
+
106
+ if (value > max) {
107
+ throw new WikiError("validation_failed", `${fieldName} must be <= ${max}`);
108
+ }
109
+ }
@@ -0,0 +1,74 @@
1
+ export type WikiConfig = {
2
+ version: 1;
3
+ wikiRoot: string;
4
+ stableBranch: string;
5
+ repoManagementBranch: string;
6
+ sessionBranchPrefix: string;
7
+ channels: {
8
+ stable: {
9
+ branch: string;
10
+ worktree: string;
11
+ };
12
+ };
13
+ vfs: {
14
+ allowedCommands: string[];
15
+ };
16
+ validation: {
17
+ requireH1: boolean;
18
+ requireExistingInternalLinks: boolean;
19
+ };
20
+ documents: {
21
+ defaultParser: string;
22
+ parsers: Record<string, DocumentParserConfig>;
23
+ };
24
+ web: {
25
+ requestTimeoutMs: number;
26
+ maxResponseBytes: number;
27
+ allowedPrivateHosts: string[];
28
+ };
29
+ };
30
+
31
+ export type DocumentParserConfig = {
32
+ enabled: boolean;
33
+ command?: string;
34
+ supportedMimeTypes?: string[];
35
+ version?: string;
36
+ timeoutMs?: number;
37
+ };
38
+
39
+ export const defaultConfig: WikiConfig = {
40
+ version: 1,
41
+ wikiRoot: "wiki",
42
+ stableBranch: "wiki/stable",
43
+ repoManagementBranch: "wiki/repo",
44
+ sessionBranchPrefix: "wiki/session/",
45
+ channels: {
46
+ stable: {
47
+ branch: "wiki/stable",
48
+ worktree: "worktrees/stable"
49
+ }
50
+ },
51
+ vfs: {
52
+ allowedCommands: ["pwd", "ls", "find", "cat", "grep", "head", "tail", "wc", "tree"]
53
+ },
54
+ validation: {
55
+ requireH1: true,
56
+ requireExistingInternalLinks: true
57
+ },
58
+ documents: {
59
+ defaultParser: "pdf-text",
60
+ parsers: {
61
+ "pdf-text": {
62
+ enabled: true
63
+ },
64
+ "text-markdown": {
65
+ enabled: true
66
+ }
67
+ }
68
+ },
69
+ web: {
70
+ requestTimeoutMs: 15000,
71
+ maxResponseBytes: 5242880,
72
+ allowedPrivateHosts: []
73
+ }
74
+ };
@@ -0,0 +1,40 @@
1
+ import path from "node:path";
2
+
3
+ export type WorkspacePaths = {
4
+ workspaceRoot: string;
5
+ repoPath: string;
6
+ statePath: string;
7
+ sessionsStatePath: string;
8
+ documentsStatePath: string;
9
+ documentBlobsPath: string;
10
+ webStatePath: string;
11
+ webBlobsPath: string;
12
+ pendingDocumentsPath: string;
13
+ worktreesPath: string;
14
+ stableWorktreePath: string;
15
+ sessionsWorktreePath: string;
16
+ wikiRootName: "wiki";
17
+ };
18
+
19
+ export function resolveWorkspacePaths(workspaceRoot: string): WorkspacePaths {
20
+ const root = path.resolve(workspaceRoot);
21
+ const statePath = path.join(root, "state");
22
+ const documentsStatePath = path.join(statePath, "documents");
23
+ const worktreesPath = path.join(root, "worktrees");
24
+
25
+ return {
26
+ workspaceRoot: root,
27
+ repoPath: path.join(root, "repo"),
28
+ statePath,
29
+ sessionsStatePath: path.join(statePath, "sessions"),
30
+ documentsStatePath,
31
+ documentBlobsPath: path.join(documentsStatePath, "blobs", "sha256"),
32
+ webStatePath: path.join(statePath, "web"),
33
+ webBlobsPath: path.join(statePath, "web", "blobs", "sha256"),
34
+ pendingDocumentsPath: path.join(documentsStatePath, "pending"),
35
+ worktreesPath,
36
+ stableWorktreePath: path.join(worktreesPath, "stable"),
37
+ sessionsWorktreePath: path.join(worktreesPath, "sessions"),
38
+ wikiRootName: "wiki"
39
+ };
40
+ }