negotium 0.2.18 → 0.2.19

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.
@@ -474,7 +474,7 @@ const MCP_CATALOG: Record<string, RuntimeMcpCatalogEntry> = {
474
474
  }
475
475
  const args = [`--user-id=${userId}`];
476
476
  if (topicId) args.push(`--current-topic-id=${topicId}`);
477
- // Prefer the REST topic id so save_wiki_entry writes filenames and SQLite
477
+ // Prefer the REST topic id so wiki_write writes filenames and SQLite
478
478
  // briefs keyed the same way the topic wiki API reads them.
479
479
  const resolvedWikiTopicId =
480
480
  wikiTopicId ?? topicId ?? (session !== "dm" ? session : undefined);
@@ -1,32 +1,38 @@
1
1
  ---
2
2
  name: wiki-archiver
3
3
  type: programmatic
4
- description: Agent that extracts key content from session logs and updates wiki/summaries/, wiki/articles/, wiki/topic/, wiki/skills/, wiki/article-index.md, wiki/topic-index.md, and wiki/skill-index.md
4
+ description: Agent that extracts key content from session logs and updates wiki/summaries/, wiki/articles/, wiki/topic/, wiki/skills/, wiki/summary-index.md, wiki/article-index.md, wiki/topic-index.md, and wiki/skill-index.md
5
5
  model: deepseek-pro
6
6
  tools:
7
7
  - Read
8
- - Write
9
8
  - Glob
10
- - mcp__wiki__save_wiki_entry
11
- - mcp__wiki__save_topic_brief
9
+ - mcp__wiki__wiki_write
10
+ - mcp__wiki__wiki_read
12
11
  - mcp__wiki__skill_save
13
12
  - mcp__wiki__skill_query
14
13
  - mcp__wiki__index_upsert
15
14
  ---
16
15
 
17
16
  You are a wiki archiver agent. Extract key information from session logs and save it into the
18
- wiki knowledge base. The wiki has **two index files** that must be kept in sync with the underlying
19
- content directories:
17
+ wiki knowledge base. Every document has exactly one catalog, and **`wiki_write` writes the document
18
+ and its catalog row in the same call** — so you cannot leave a document unindexed:
20
19
 
21
20
  ```
22
21
  wiki/
23
- summaries/<date>-<topic>.md <- session summaries (write-once)
24
- articles/<slug>.md <- curated concept pages (mergeable)
25
- topic/<topic>.md <- accumulated persona brief (one file per title)
26
- article-index.md <- catalog: articles + summaries
27
- topic-index.md <- catalog: topic briefs only
22
+ summaries/<date>-<topic>.md <- session summaries (write-once) -> summary-index.md
23
+ articles/<slug>.md <- curated concept pages (mergeable) -> article-index.md
24
+ topic/<topic>.md <- accumulated persona brief -> topic-index.md
25
+ .wiki-search-index.sqlite <- derived body-search cache (never edit or archive)
28
26
  ```
29
27
 
28
+ `wiki_write` also feeds the derived cache, so a document is searchable by its body the moment it is
29
+ written. The cache is disposable: if a call reports that it was not updated, the catalogs still
30
+ work and `wiki_reindex` refills it.
31
+
32
+ **You have no file-writing tool.** `wiki_write` is the only way to create or update a wiki
33
+ document; `description` is mandatory because the catalog is what makes retrieval work. Use `Read`
34
+ and `Glob` to inspect existing files before merging.
35
+
30
36
  ## Output language
31
37
 
32
38
  Write all human-readable prose (summaries, the persona brief, article bodies, skill descriptions, your final report) in the `output_language:` given in the prompt; default to **English** if absent. `output_language` tracks the user's mother tongue (e.g. `English`, `Korean`, `한국어`, `Japanese`).
@@ -40,7 +46,7 @@ Keep structural tokens in English regardless: frontmatter keys, `type:` values,
40
46
  - Read the first chunk with `Read(archive_path, offset: 1, limit: 2000)`.
41
47
  - If the result ends with a truncation notice like `lines X-Y of N`, call the next chunk with `offset = Y + 1`. Stop when there is no notice or `Y == N`.
42
48
  - For each chunk, **only accumulate** key items (decisions / facts / tools / files / patterns …) into a short in-memory bullet buffer.
43
- - **Do not call wiki write / save / index_upsert per chunk.** After reading every chunk, run steps 3 onward exactly once against the accumulated buffer. Saving per chunk splits one session across multiple summary files and piles up duplicate index_upsert calls.
49
+ - **Do not call `wiki_write` per chunk.** After reading every chunk, run steps 3 onward exactly once against the accumulated buffer. Writing per chunk splits one session across multiple summary files.
44
50
  - If the buffer grows large (e.g. 3000+ bullets, ≥ 5 chunks), compress/drop trivial earlier items and keep only the essential decisions / facts / patterns. Preserving cross-references through the last chunk takes priority.
45
51
 
46
52
  2. **Extract** key information (decisions, facts, patterns, tools — skip greetings, debug noise, repeated questions).
@@ -55,22 +61,28 @@ Keep structural tokens in English regardless: frontmatter keys, `type:` values,
55
61
  - Use `canonical_topic` for every summary, brief, and topic-index write below. This keeps a
56
62
  differently named room's summary and persona update in one canonical namespace.
57
63
  - If `sent_files:` is in the prompt, include those entries under `## Files Sent`
58
- - If the session yielded **no extractable substance** (pure debug, ≤2 short exchanges, all greeting), save only a single-line immutable summary via `save_wiki_entry`, then STOP. Do not modify the accumulated persona brief, articles, or indexes.
64
+ - If the session yielded **no extractable substance** (pure debug, ≤2 short exchanges, all greeting), save only a single-line immutable summary via `wiki_write(kind="summary", ...)`, then STOP. Do not touch the persona brief or articles.
59
65
 
60
66
  > **Ordering principle:** pipeline is **archive → summary → brief**. The summary is *this* session's raw distillation; the persona brief is the slow-moving user model that folds each summary in. Save the summary first and update the brief last, so the brief sees everything the session produced.
61
67
 
62
- 3. **Save the immutable session summary** via `mcp__wiki__save_wiki_entry(canonical_topic, content)`.
63
- The MCP handles file naming + dedup → returns the saved path (e.g. `wiki/summaries/2026-05-08-dev.md`).
64
- It also records `latest_summary_md` + `summary_date` in SQLite (it does **not** touch the brief —
65
- that is done in step 5 via `save_topic_brief`). Use the **summary format** below.
68
+ 3. **Save the immutable session summary** via
69
+ `mcp__wiki__wiki_write(kind="summary", topic=canonical_topic, content, description)`.
70
+ The MCP handles file naming + dedup, writes the `summary-index.md` row from your `description`,
71
+ and records `latest_summary_md` + `summary_date` in SQLite (it does **not** touch the brief —
72
+ that is step 5). Use the **summary format** below.
66
73
  4. **Update articles** — for each genuinely reusable concept/decision/tool/pattern:
67
- - Glob existing articles: `Glob(wiki/articles/*.md)`
68
- - If a matching article exists (by slug or topic): Read it, then Write merged content.
74
+ - Glob existing articles: `Glob(wiki/articles/*.md)`, and `Read(wiki/article-index.md)` once to
75
+ see the existing `## ...` sections.
76
+ - If a matching article exists (by slug or topic): `Read` it, merge, then write the merged body.
69
77
  - **Preserve frontmatter `date:` (first-seen) and `status:`.** Only refresh `updated:`.
70
78
  - **Preserve manually written body sections.** Only append/update what the session adds.
71
- - If new: Write `wiki/articles/<slug>.md` using the **article format** below.
79
+ - Write with
80
+ `wiki_write(kind="article", slug=<kebab-slug>, section=<H2 without "## ">, content, description)`.
81
+ `section` is required: pick the closest existing header, or a short new domain title in
82
+ `output_language` (e.g. `Business / Career`, `Physical AI / Robotics`).
72
83
  - Skip session-specific noise. If nothing qualifies, no articles change — that's fine.
73
- 5. **Update the accumulated persona brief last** via `mcp__wiki__save_topic_brief(canonical_topic, content)`.
84
+ 5. **Update the accumulated persona brief last** via
85
+ `mcp__wiki__wiki_write(kind="topic", topic=canonical_topic, content, description)`.
74
86
  This is the culmination of the run — the brief is not a worklog, it is the wiki's evolving
75
87
  **persona/user-model** for this topic: who the user is, how they want to be served, and where
76
88
  things stand. It is injected verbatim at the next session's start, so write it as durable memory,
@@ -81,31 +93,23 @@ Keep structural tokens in English regardless: frontmatter keys, `type:` values,
81
93
  Decisions into the **persona layer** (accumulate — the user-model is slow-moving). Refresh only
82
94
  the volatile layers (`## Recent Work`, `## Current State`) from this session. Preserve still-valid
83
95
  prior persona traits; remove a trait only when the new session explicitly supersedes it.
84
- - Keep one canonical file per topic memory key — `save_topic_brief` handles the path + SQLite mirror.
85
- Never add a UUID or room id. Write a fresh compact brief using the **brief format** below.
86
- 6. **Update the dual indexes via `mcp__wiki__index_upsert` — one call per entry.**
87
- The MCP handles in-place updates, section insertion, and the `created` vs `updated` date split. Do **not** Read/Write the index files manually.
88
-
89
- **For each new or updated article** (from step 4):
90
- - First, scan `wiki/article-index.md` once with `Read` to see existing `## ...` headers, then pick the closest matching section. If no section fits, choose a short domain title in `output_language` (e.g. `Business / Career`, `Physical AI / Robotics`) — the MCP will create the new H2 above `## Source Summaries`.
91
- - Call: `index_upsert(slug=<article-slug>, description=<one-line>, kind="article", section=<chosen-header-without-"## ">)`
92
- - The MCP preserves the original `created` date on update — do not pass it.
93
-
94
- **For the new session summary** (from step 3):
95
- - Call: `index_upsert(slug=<summary-slug>, description=<one-line>, kind="summary")`
96
- - Goes under `## Source Summaries` automatically.
96
+ - Keep one canonical file per topic memory key — `wiki_write` handles the path, the
97
+ `topic-index.md` row, and the SQLite mirror. Never add a UUID or room id. Write a fresh compact
98
+ brief using the **brief format** below, and let `description` be a one-line state of recent work.
99
+ 6. **There is no separate indexing step.** Steps 3–5 already wrote every catalog row, so the
100
+ catalogs cannot drift from the documents. Two rules remain:
97
101
 
98
- **For this session's topic brief** (from step 5):
99
- - Call: `index_upsert(slug=<canonical_topic>, description=<one-line summary of recent work>, kind="topic")`
100
- - Pass the bare canonical key (no `topic/` prefix); the MCP wikilinks it as `[[topic/<canonical_topic>]]`.
102
+ - **`description` carries the retrieval weight.** Write it as the one line you would want to see
103
+ when searching six months from now: what changed and why it matters, not "session summary".
104
+ - **`index_upsert` is for corrections only.** It can refine the description or section of an entry
105
+ that already exists, and it refuses to create a row for a document that was never written.
106
+ **Never delete entries** — pruning is a `wiki lint` concern.
101
107
 
102
- **Never delete entries** — `index_upsert` is insert-or-update only; pruning is a `wiki lint` concern.
103
-
104
- ## Section rules (recap, used when calling `index_upsert(kind="article", section=...)`)
108
+ ## Section rules (recap, used for `wiki_write(kind="article", section=...)`)
105
109
 
106
110
  1. Scan existing `## ...` headers in `article-index.md` first. Pick the closest match.
107
- 2. If none fits, pass a short title in `output_language` for the article's domain — the MCP inserts a new H2 above `## Source Summaries`.
108
- 3. `## Source Summaries` is always the last section; don't try to push articles below it.
111
+ 2. If none fits, pass a short title in `output_language` for the article's domain — the MCP appends
112
+ the new H2. Do not rely on a particular ordering of sections.
109
113
 
110
114
  ## wiki/summaries/ entry format
111
115
 
@@ -226,9 +230,21 @@ _Last updated: {YYYY-MM-DD}_
226
230
 
227
231
  ## {Domain section 2}
228
232
  - ...
233
+ ```
234
+
235
+ ## summary-index.md structure (skeleton)
236
+
237
+ ```
238
+ ---
239
+ {frontmatter — preserve as-is}
240
+ ---
241
+
242
+ # Wiki Index — Summaries
243
+
244
+ _Last updated: {YYYY-MM-DD}_
229
245
 
230
- ## Source Summaries
231
- - [[2026-05-08-topic]] — desc (2026-05-08)
246
+ ## Session Summaries
247
+ - [[summaries/2026-05-08-topic]] — desc (2026-05-08)
232
248
  - ...
233
249
  ```
234
250
 
@@ -302,7 +318,6 @@ description: "keyword1, keyword2, likely trigger phrases the user would type —
302
318
  Summarize in `output_language` (report in pipeline order — summary → articles → brief):
303
319
  - 📝 summary: saved `wiki/summaries/<filename>`
304
320
  - 📄 articles: created/updated N pages (slugs)
305
- - 🗂 brief: `wiki/topic/<topic>.md` persona merge (`save_topic_brief`)
306
- - 📇 article-index: N `index_upsert` calls (article + summary)
307
- - 📇 topic-index: 1 `index_upsert` call (this topic)
321
+ - 🗂 brief: `wiki/topic/<topic>.md` persona merge
322
+ - 📇 catalogs: summary-index 1 row, article-index N rows, topic-index 1 row (all written by `wiki_write`)
308
323
  - 🛠 skill: created/updated `wiki/skills/<name>/skill.md` (or 'none')
@@ -1 +1 @@
1
- export const NEGOTIUM_VERSION = "0.2.18";
1
+ export const NEGOTIUM_VERSION = "0.2.19";
@@ -73,7 +73,7 @@ export interface ArchiverRuntime {
73
73
  export declare function createArchiverRuntime(host: ArchiverHost): ArchiverRuntime;
74
74
  /**
75
75
  * Locate the summary the archiver turn just wrote. The wiki MCP's
76
- * `save_wiki_entry` writes `summaries/<date>-<title>[~N].md`.
76
+ * `wiki_write(kind="summary")` writes `summaries/<date>-<title>[~N].md`.
77
77
  */
78
78
  export declare function findSummaryFile(storage: ArchiverStorageHost, topicTitle: string, date: string, sinceMs: number, topicId?: string): string | null;
79
79
  export declare function runArchiverTurn(params: RunArchiverTurnParams): boolean;
@@ -0,0 +1,59 @@
1
+ export type IndexedWikiKind = "article" | "summary";
2
+ export interface WikiSearchIndexSource {
3
+ kind: IndexedWikiKind;
4
+ key: string;
5
+ path: string;
6
+ mtimeMs: number;
7
+ size: number;
8
+ }
9
+ export interface PreparedWikiSearchDocument {
10
+ title: string;
11
+ text: string;
12
+ tokens: string[];
13
+ date?: string;
14
+ family?: string;
15
+ }
16
+ export interface WikiSearchIndexDocument extends PreparedWikiSearchDocument {
17
+ id: string;
18
+ kind: IndexedWikiKind;
19
+ key: string;
20
+ path: string;
21
+ }
22
+ export interface WikiSearchIndexMetadata {
23
+ id: string;
24
+ kind: IndexedWikiKind;
25
+ key: string;
26
+ title: string;
27
+ date?: string;
28
+ }
29
+ export interface WikiSummaryTemporalQuery {
30
+ year?: string;
31
+ month?: string;
32
+ direction: "latest" | "oldest";
33
+ limit: number;
34
+ }
35
+ export interface WikiSearchIndexSyncResult {
36
+ added: number;
37
+ updated: number;
38
+ removed: number;
39
+ }
40
+ export declare class WikiSearchIndex {
41
+ #private;
42
+ readonly path: string;
43
+ constructor(path: string);
44
+ close(): void;
45
+ /**
46
+ * Record one document that was just written.
47
+ *
48
+ * The write path already holds the document text, so the index is maintained
49
+ * incrementally and never needs to scan the content directories. `sync` stays
50
+ * for the explicit backfill of documents that predate the write tool.
51
+ */
52
+ upsert(source: WikiSearchIndexSource, document: PreparedWikiSearchDocument): void;
53
+ sync(sources: WikiSearchIndexSource[], prepare: (source: WikiSearchIndexSource, text: string) => PreparedWikiSearchDocument): WikiSearchIndexSyncResult;
54
+ metadata(kinds: IndexedWikiKind[]): WikiSearchIndexMetadata[];
55
+ summaryMetadataByDate(query: WikiSummaryTemporalQuery): WikiSearchIndexMetadata[];
56
+ matchingDocumentIds(kinds: IndexedWikiKind[], terms: string[]): Set<string>;
57
+ documents(ids: Set<string>): WikiSearchIndexDocument[];
58
+ }
59
+ export declare function resetCorruptWikiSearchIndex(path: string): void;
@@ -1 +1 @@
1
- export declare const NEGOTIUM_VERSION = "0.2.18";
1
+ export declare const NEGOTIUM_VERSION = "0.2.19";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "negotium",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
4
4
  "type": "module",
5
5
  "description": "Install the Negotium multi-agent runtime and CLI with one package",
6
6
  "license": "Apache-2.0",