@stackbilt/aegis-core 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/src/adapters/voice/cloudflare-agent.ts +0 -0
- package/src/bluesky.ts +0 -0
- package/src/claude-tools/content.ts +0 -0
- package/src/claude-tools/email.ts +0 -0
- package/src/codebeast.ts +0 -0
- package/src/content/column.ts +0 -0
- package/src/content/hero-image.ts +0 -0
- package/src/content/index.ts +0 -0
- package/src/content/journal.ts +0 -0
- package/src/content/roundtable.ts +0 -0
- package/src/contracts/agenda-item.contract.ts +0 -0
- package/src/contracts/cc-task.contract.ts +0 -0
- package/src/contracts/goal.contract.ts +0 -0
- package/src/contracts/memory-entry.contract.ts +0 -0
- package/src/core.ts +0 -0
- package/src/dashboard.ts +0 -0
- package/src/decision-docs.ts +0 -0
- package/src/dispatch.ts +0 -0
- package/src/edge-env.ts +0 -0
- package/src/exports.ts +0 -0
- package/src/github-projects.ts +0 -0
- package/src/kernel/argus-actions.ts +0 -0
- package/src/kernel/argus-correlation.ts +0 -0
- package/src/kernel/board.ts +0 -0
- package/src/kernel/classify-memory-topic.ts +0 -0
- package/src/kernel/dynamic-tools.ts +0 -0
- package/src/kernel/executor-port.ts +0 -0
- package/src/kernel/insight-cache.ts +0 -0
- package/src/kernel/memory/insights.ts +0 -0
- package/src/kernel/memory-guardrails.ts +0 -0
- package/src/kernel/port.ts +0 -0
- package/src/kernel/resilience.ts +0 -0
- package/src/kernel/scheduled/agent-dispatch.ts +0 -0
- package/src/kernel/scheduled/argus-analytics.ts +0 -0
- package/src/kernel/scheduled/argus-heartbeat.ts +0 -0
- package/src/kernel/scheduled/argus-notify.ts +0 -0
- package/src/kernel/scheduled/board-sync.ts +0 -0
- package/src/kernel/scheduled/ci-watcher.ts +0 -0
- package/src/kernel/scheduled/content-drip.ts +0 -0
- package/src/kernel/scheduled/content.ts +0 -0
- package/src/kernel/scheduled/conversation-facts.ts +0 -0
- package/src/kernel/scheduled/cost-report.ts +0 -0
- package/src/kernel/scheduled/dev-activity.ts +0 -0
- package/src/kernel/scheduled/digest.ts +0 -0
- package/src/kernel/scheduled/dreaming/agenda-triage.ts +0 -0
- package/src/kernel/scheduled/dreaming/facts.ts +0 -0
- package/src/kernel/scheduled/dreaming/index.ts +0 -0
- package/src/kernel/scheduled/dreaming/llm.ts +0 -0
- package/src/kernel/scheduled/dreaming/pattern-synthesis.ts +0 -0
- package/src/kernel/scheduled/dreaming/persona.ts +0 -0
- package/src/kernel/scheduled/dreaming/symbolic.ts +0 -0
- package/src/kernel/scheduled/dreaming/task-proposals.ts +0 -0
- package/src/kernel/scheduled/entropy.ts +0 -0
- package/src/kernel/scheduled/feed-watcher.ts +0 -0
- package/src/kernel/scheduled/inbox-processor.ts +0 -0
- package/src/kernel/scheduled/issue-proposer.ts +0 -0
- package/src/kernel/scheduled/issue-watcher.ts +0 -0
- package/src/kernel/scheduled/pr-automerge.ts +0 -0
- package/src/kernel/scheduled/product-health.ts +0 -0
- package/src/kernel/scheduled/self-improvement.ts +0 -0
- package/src/kernel/scheduled/social-engage.ts +0 -0
- package/src/kernel/scheduled/task-audit.ts +0 -0
- package/src/landing.ts +0 -0
- package/src/lib/audit-chain/chain.ts +0 -0
- package/src/lib/audit-chain/types.ts +0 -0
- package/src/lib/observability/errors.ts +0 -0
- package/src/operator/config.ts +0 -0
- package/src/operator/persona.ts +0 -0
- package/src/pulse.ts +0 -0
- package/src/routes/bluesky.ts +0 -0
- package/src/routes/codebeast.ts +0 -0
- package/src/routes/content.ts +0 -0
- package/src/routes/dynamic-tools.ts +0 -0
- package/src/routes/observability.ts +0 -0
- package/src/routes/operator-logs.ts +0 -0
- package/src/routes/pages.ts +0 -0
- package/src/schema-enums.ts +0 -0
- package/src/task-intelligence.ts +0 -0
- package/src/ui.ts +0 -0
- package/src/wiki/client.ts +346 -0
- package/src/wiki/types.ts +90 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackbilt/aegis-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Persistent AI agent framework for Cloudflare Workers. Multi-tier memory, autonomous goals, dreaming cycles, MCP native.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -59,7 +59,9 @@
|
|
|
59
59
|
"./contracts/goal": "./src/contracts/goal.contract.ts",
|
|
60
60
|
"./contracts/agenda-item": "./src/contracts/agenda-item.contract.ts",
|
|
61
61
|
"./contracts/cc-task": "./src/contracts/cc-task.contract.ts",
|
|
62
|
-
"./contracts/memory-entry": "./src/contracts/memory-entry.contract.ts"
|
|
62
|
+
"./contracts/memory-entry": "./src/contracts/memory-entry.contract.ts",
|
|
63
|
+
"./wiki/client": "./src/wiki/client.ts",
|
|
64
|
+
"./wiki/types": "./src/wiki/types.ts"
|
|
63
65
|
},
|
|
64
66
|
"scripts": {
|
|
65
67
|
"dev": "wrangler dev",
|
|
File without changes
|
package/src/bluesky.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/codebeast.ts
CHANGED
|
File without changes
|
package/src/content/column.ts
CHANGED
|
File without changes
|
|
File without changes
|
package/src/content/index.ts
CHANGED
|
File without changes
|
package/src/content/journal.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/core.ts
CHANGED
|
File without changes
|
package/src/dashboard.ts
CHANGED
|
File without changes
|
package/src/decision-docs.ts
CHANGED
|
File without changes
|
package/src/dispatch.ts
CHANGED
|
File without changes
|
package/src/edge-env.ts
CHANGED
|
File without changes
|
package/src/exports.ts
CHANGED
|
File without changes
|
package/src/github-projects.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/kernel/board.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/kernel/port.ts
CHANGED
|
File without changes
|
package/src/kernel/resilience.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/landing.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/operator/config.ts
CHANGED
|
File without changes
|
package/src/operator/persona.ts
CHANGED
|
File without changes
|
package/src/pulse.ts
CHANGED
|
File without changes
|
package/src/routes/bluesky.ts
CHANGED
|
File without changes
|
package/src/routes/codebeast.ts
CHANGED
|
File without changes
|
package/src/routes/content.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/routes/pages.ts
CHANGED
|
File without changes
|
package/src/schema-enums.ts
CHANGED
|
File without changes
|
package/src/task-intelligence.ts
CHANGED
|
File without changes
|
package/src/ui.ts
CHANGED
|
File without changes
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ListPagesResult,
|
|
3
|
+
PageSummary,
|
|
4
|
+
ReadPageResult,
|
|
5
|
+
SearchPagesResult,
|
|
6
|
+
WikiPage,
|
|
7
|
+
WriteInput,
|
|
8
|
+
WritePageResult,
|
|
9
|
+
} from './types.js';
|
|
10
|
+
|
|
11
|
+
export interface WikiClientEnv {
|
|
12
|
+
wikiBaseUrl?: string;
|
|
13
|
+
wikiBinding?: Fetcher;
|
|
14
|
+
wikiToken?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class WikiClientError extends Error {
|
|
18
|
+
constructor(
|
|
19
|
+
message: string,
|
|
20
|
+
public readonly status: number,
|
|
21
|
+
public readonly statusText: string,
|
|
22
|
+
public readonly details?: unknown,
|
|
23
|
+
) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'WikiClientError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function requireWikiToken(env: WikiClientEnv): string {
|
|
30
|
+
if (!env.wikiToken?.trim()) {
|
|
31
|
+
throw new WikiClientError('AEGIS_WIKI_TOKEN is not configured', 503, 'Service Unavailable');
|
|
32
|
+
}
|
|
33
|
+
return env.wikiToken;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function todayIsoDate(): string {
|
|
37
|
+
return new Date().toISOString().slice(0, 10);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function asObject(value: unknown): Record<string, unknown> | null {
|
|
41
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
42
|
+
? value as Record<string, unknown>
|
|
43
|
+
: null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function coerceBoolean(value: unknown): boolean | undefined {
|
|
47
|
+
if (typeof value === 'boolean') return value;
|
|
48
|
+
if (typeof value === 'number') return value !== 0;
|
|
49
|
+
if (value === undefined || value === null) return undefined;
|
|
50
|
+
if (typeof value === 'string') {
|
|
51
|
+
if (value === 'true' || value === '1') return true;
|
|
52
|
+
if (value === 'false' || value === '0') return false;
|
|
53
|
+
}
|
|
54
|
+
return Boolean(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Emdash stores body as portableText (an array of blocks). Agents want readable
|
|
58
|
+
// prose, so serialize back to markdown-ish text. Walks blocks, applies heading
|
|
59
|
+
// prefixes by style, drops marks/links — lossy but agent-readable.
|
|
60
|
+
function portableTextToMarkdown(value: unknown): string | undefined {
|
|
61
|
+
if (typeof value === 'string') return value;
|
|
62
|
+
if (!Array.isArray(value)) return undefined;
|
|
63
|
+
const lines: string[] = [];
|
|
64
|
+
for (const block of value) {
|
|
65
|
+
if (!block || typeof block !== 'object') continue;
|
|
66
|
+
const b = block as Record<string, unknown>;
|
|
67
|
+
const style = typeof b.style === 'string' ? b.style : 'normal';
|
|
68
|
+
const children = Array.isArray(b.children) ? b.children : [];
|
|
69
|
+
const text = children
|
|
70
|
+
.map(c => (c && typeof c === 'object' && typeof (c as Record<string, unknown>).text === 'string'
|
|
71
|
+
? String((c as Record<string, unknown>).text)
|
|
72
|
+
: ''))
|
|
73
|
+
.join('');
|
|
74
|
+
if (!text) { lines.push(''); continue; }
|
|
75
|
+
if (style === 'h1') lines.push(`# ${text}`);
|
|
76
|
+
else if (style === 'h2') lines.push(`## ${text}`);
|
|
77
|
+
else if (style === 'h3') lines.push(`### ${text}`);
|
|
78
|
+
else if (style === 'h4') lines.push(`#### ${text}`);
|
|
79
|
+
else if (style === 'h5') lines.push(`##### ${text}`);
|
|
80
|
+
else if (style === 'h6') lines.push(`###### ${text}`);
|
|
81
|
+
else if (style === 'blockquote') lines.push(`> ${text}`);
|
|
82
|
+
else lines.push(text);
|
|
83
|
+
lines.push('');
|
|
84
|
+
}
|
|
85
|
+
return lines.join('\n').trim();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Emdash response envelope: { data: { item: ContentItem, _rev?: string } }
|
|
89
|
+
// ContentItem shape: { id, type, slug, status, data: <frontmatter+body>, ... }
|
|
90
|
+
// Returns merged WikiPage with frontmatter spread + top-level id/slug/status/_rev.
|
|
91
|
+
function normalizePage(value: unknown): WikiPage | null {
|
|
92
|
+
const envelope = asObject(value);
|
|
93
|
+
if (!envelope) return null;
|
|
94
|
+
|
|
95
|
+
// Drill into the wrapper. Accept both { data: { item } } and a bare ContentItem
|
|
96
|
+
// (in case the response shape ever flattens). Walk both shapes safely.
|
|
97
|
+
const inner = asObject(envelope.data) ?? envelope;
|
|
98
|
+
const item = asObject(inner.item) ?? inner;
|
|
99
|
+
if (!item.slug && !item.id) return null;
|
|
100
|
+
|
|
101
|
+
const data = asObject(item.data) ?? {};
|
|
102
|
+
const canonical = coerceBoolean(data.canonical);
|
|
103
|
+
|
|
104
|
+
const merged: WikiPage = {
|
|
105
|
+
id: String(item.id ?? ''),
|
|
106
|
+
slug: String(item.slug ?? data.slug ?? ''),
|
|
107
|
+
status: typeof item.status === 'string' ? item.status : undefined,
|
|
108
|
+
revision_id: typeof inner._rev === 'string' ? inner._rev : undefined,
|
|
109
|
+
title: String(data.title ?? ''),
|
|
110
|
+
scope: String(data.scope ?? ''),
|
|
111
|
+
type: String(data.type ?? ''),
|
|
112
|
+
confidence: String(data.confidence ?? ''),
|
|
113
|
+
summary: typeof data.summary === 'string' ? data.summary : '',
|
|
114
|
+
body: portableTextToMarkdown(data.body),
|
|
115
|
+
canonical,
|
|
116
|
+
last_verified: typeof data.last_verified === 'string' ? data.last_verified : undefined,
|
|
117
|
+
sources: Array.isArray(data.sources) ? (data.sources as WikiPage['sources']) : undefined,
|
|
118
|
+
related: Array.isArray(data.related) ? (data.related as string[]) : undefined,
|
|
119
|
+
supersedes: Array.isArray(data.supersedes) ? (data.supersedes as string[]) : undefined,
|
|
120
|
+
owners: Array.isArray(data.owners) ? (data.owners as string[]) : undefined,
|
|
121
|
+
consumers: Array.isArray(data.consumers) ? (data.consumers as string[]) : undefined,
|
|
122
|
+
guarded_paths: Array.isArray(data.guarded_paths) ? (data.guarded_paths as string[]) : undefined,
|
|
123
|
+
updated_at: typeof item.updatedAt === 'string' ? item.updatedAt : '',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Preserve any additional fields from data that we didn't explicitly map.
|
|
127
|
+
for (const [k, v] of Object.entries(data)) {
|
|
128
|
+
if (!(k in merged)) merged[k] = v;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return merged;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Handles both shapes:
|
|
135
|
+
// - ContentItem (list endpoint): { id, slug, data: { title, scope, type, ... } }
|
|
136
|
+
// - SearchHit (search endpoint): { collection, id, slug, title, snippet, score }
|
|
137
|
+
// Search hits don't carry scope/type/confidence — those fields stay empty and the
|
|
138
|
+
// agent can call wiki_read for full details on any hit.
|
|
139
|
+
function normalizeContentItemToSummary(item: Record<string, unknown>): PageSummary {
|
|
140
|
+
const data = asObject(item.data) ?? {};
|
|
141
|
+
const summary: PageSummary = {
|
|
142
|
+
slug: String(item.slug ?? ''),
|
|
143
|
+
title: String(item.title ?? data.title ?? ''),
|
|
144
|
+
scope: String(item.scope ?? data.scope ?? ''),
|
|
145
|
+
type: typeof data.type === 'string' ? data.type : '', // top-level item.type is the collection name, not the wiki type
|
|
146
|
+
confidence: String(data.confidence ?? ''),
|
|
147
|
+
summary: typeof data.summary === 'string' ? data.summary : '',
|
|
148
|
+
updated_at: typeof item.updatedAt === 'string' ? item.updatedAt : '',
|
|
149
|
+
};
|
|
150
|
+
if (typeof item.snippet === 'string') summary.snippet = item.snippet;
|
|
151
|
+
return summary;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function normalizeSummaryArray(value: unknown): PageSummary[] {
|
|
155
|
+
const envelope = asObject(value);
|
|
156
|
+
if (!envelope) return [];
|
|
157
|
+
const inner = asObject(envelope.data) ?? envelope;
|
|
158
|
+
|
|
159
|
+
// Cursor-based list: { data: { items: [...] } }
|
|
160
|
+
// Search hits: { data: { results: [...] } }
|
|
161
|
+
const arr =
|
|
162
|
+
(Array.isArray(inner.items) && inner.items) ||
|
|
163
|
+
(Array.isArray(inner.results) && inner.results) ||
|
|
164
|
+
(Array.isArray(inner.pages) && inner.pages) ||
|
|
165
|
+
(Array.isArray(envelope.items) && envelope.items) ||
|
|
166
|
+
(Array.isArray(envelope.results) && envelope.results) ||
|
|
167
|
+
[];
|
|
168
|
+
|
|
169
|
+
return (arr as unknown[])
|
|
170
|
+
.map(item => asObject(item))
|
|
171
|
+
.filter((item): item is Record<string, unknown> => !!item)
|
|
172
|
+
.map(normalizeContentItemToSummary);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function normalizeWriteResult(value: unknown, fallback: { slug: string; id?: string }): WritePageResult {
|
|
176
|
+
const page = normalizePage(value);
|
|
177
|
+
const id = page?.id || fallback.id || '';
|
|
178
|
+
if (!id) {
|
|
179
|
+
throw new WikiClientError('Wiki write response did not include an id', 502, 'Bad Gateway', value);
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
slug: page?.slug || fallback.slug,
|
|
183
|
+
id,
|
|
184
|
+
revision_id: page?.revision_id,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function requestJson<T>(env: WikiClientEnv, path: string, init: RequestInit = {}): Promise<T> {
|
|
189
|
+
const token = requireWikiToken(env);
|
|
190
|
+
const headers = new Headers(init.headers);
|
|
191
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
192
|
+
if (init.body && !headers.has('Content-Type')) {
|
|
193
|
+
headers.set('Content-Type', 'application/json');
|
|
194
|
+
}
|
|
195
|
+
headers.set('Accept', 'application/json');
|
|
196
|
+
|
|
197
|
+
const base = env.wikiBaseUrl ?? '';
|
|
198
|
+
const request = new Request(`${base}${path}`, { ...init, headers });
|
|
199
|
+
|
|
200
|
+
let response: Response;
|
|
201
|
+
if (env.wikiBinding) {
|
|
202
|
+
response = await env.wikiBinding.fetch(request);
|
|
203
|
+
} else if (env.wikiBaseUrl) {
|
|
204
|
+
response = await fetch(request);
|
|
205
|
+
} else {
|
|
206
|
+
throw new WikiClientError(
|
|
207
|
+
'WikiClientEnv requires wikiBaseUrl or wikiBinding',
|
|
208
|
+
503,
|
|
209
|
+
'Service Unavailable',
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const text = await response.text();
|
|
214
|
+
let data: unknown = null;
|
|
215
|
+
if (text) {
|
|
216
|
+
try {
|
|
217
|
+
data = JSON.parse(text);
|
|
218
|
+
} catch {
|
|
219
|
+
data = text;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!response.ok) {
|
|
224
|
+
throw new WikiClientError(
|
|
225
|
+
`Wiki request failed: ${response.status} ${response.statusText}`,
|
|
226
|
+
response.status,
|
|
227
|
+
response.statusText,
|
|
228
|
+
data,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return data as T;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function readPage(env: WikiClientEnv, slug: string): Promise<ReadPageResult> {
|
|
236
|
+
try {
|
|
237
|
+
const data = await requestJson<unknown>(env, `/content/wiki/${encodeURIComponent(slug)}`);
|
|
238
|
+
return { page: normalizePage(data) };
|
|
239
|
+
} catch (err) {
|
|
240
|
+
if (err instanceof WikiClientError && err.status === 404) {
|
|
241
|
+
return { page: null };
|
|
242
|
+
}
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function searchPages(
|
|
248
|
+
env: WikiClientEnv,
|
|
249
|
+
query: string,
|
|
250
|
+
options: { limit?: number } = {},
|
|
251
|
+
): Promise<SearchPagesResult> {
|
|
252
|
+
const params = new URLSearchParams({ q: query, collections: 'wiki' });
|
|
253
|
+
params.set('limit', String(options.limit ?? 10));
|
|
254
|
+
|
|
255
|
+
const data = await requestJson<unknown>(env, `/search?${params.toString()}`);
|
|
256
|
+
const results = normalizeSummaryArray(data);
|
|
257
|
+
|
|
258
|
+
// EmDash search hits don't carry scope/type in the response — client-side
|
|
259
|
+
// filtering on those fields would silently discard all results. Use
|
|
260
|
+
// wiki_list for scope-scoped enumeration (aegis#575).
|
|
261
|
+
return { results };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Emdash's write endpoints land content in a draft revision. A separate
|
|
265
|
+
// POST /publish call is required to promote the draft to live — otherwise
|
|
266
|
+
// GET returns the previous live revision and the write is silently invisible.
|
|
267
|
+
// See aegis#475 for the forensic trace.
|
|
268
|
+
async function publishRevision(env: WikiClientEnv, id: string): Promise<void> {
|
|
269
|
+
await requestJson<unknown>(
|
|
270
|
+
env,
|
|
271
|
+
`/content/wiki/${encodeURIComponent(id)}/publish`,
|
|
272
|
+
{ method: 'POST', body: JSON.stringify({}) },
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function writePage(env: WikiClientEnv, input: WriteInput): Promise<WritePageResult> {
|
|
277
|
+
const { slug, ...rest } = input;
|
|
278
|
+
const dataPayload = {
|
|
279
|
+
canonical: false,
|
|
280
|
+
confidence: 'drifting' as const,
|
|
281
|
+
last_verified: todayIsoDate(),
|
|
282
|
+
sources: [],
|
|
283
|
+
related: [],
|
|
284
|
+
supersedes: [],
|
|
285
|
+
...rest,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const existing = await readPage(env, slug);
|
|
289
|
+
|
|
290
|
+
if (existing.page?.id) {
|
|
291
|
+
const updateBody = {
|
|
292
|
+
slug,
|
|
293
|
+
status: existing.page.status ?? 'published',
|
|
294
|
+
data: dataPayload,
|
|
295
|
+
...(existing.page.revision_id ? { _rev: existing.page.revision_id } : {}),
|
|
296
|
+
};
|
|
297
|
+
const data = await requestJson<unknown>(
|
|
298
|
+
env,
|
|
299
|
+
`/content/wiki/${encodeURIComponent(existing.page.id)}`,
|
|
300
|
+
{
|
|
301
|
+
method: 'PUT',
|
|
302
|
+
body: JSON.stringify(updateBody),
|
|
303
|
+
},
|
|
304
|
+
);
|
|
305
|
+
await publishRevision(env, existing.page.id);
|
|
306
|
+
return normalizeWriteResult(data, { slug, id: existing.page.id });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const createBody = {
|
|
310
|
+
slug,
|
|
311
|
+
status: 'published',
|
|
312
|
+
data: dataPayload,
|
|
313
|
+
};
|
|
314
|
+
const data = await requestJson<unknown>(env, '/content/wiki', {
|
|
315
|
+
method: 'POST',
|
|
316
|
+
body: JSON.stringify(createBody),
|
|
317
|
+
});
|
|
318
|
+
const result = normalizeWriteResult(data, { slug });
|
|
319
|
+
await publishRevision(env, result.id);
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function listPages(
|
|
324
|
+
env: WikiClientEnv,
|
|
325
|
+
options: { scope?: string; limit?: number; cursor?: string } = {},
|
|
326
|
+
): Promise<ListPagesResult> {
|
|
327
|
+
const params = new URLSearchParams();
|
|
328
|
+
params.set('limit', String(options.limit ?? 50));
|
|
329
|
+
if (options.cursor) params.set('cursor', options.cursor);
|
|
330
|
+
|
|
331
|
+
const raw = await requestJson<unknown>(env, `/content/wiki?${params.toString()}`);
|
|
332
|
+
|
|
333
|
+
let pages = normalizeSummaryArray(raw);
|
|
334
|
+
// Emdash list endpoint doesn't filter by inner data fields — scope is client-side.
|
|
335
|
+
if (options.scope) pages = pages.filter(p => p.scope === options.scope);
|
|
336
|
+
|
|
337
|
+
const envelope = asObject(raw);
|
|
338
|
+
const inner = asObject(envelope?.data) ?? envelope ?? {};
|
|
339
|
+
const nextCursor = typeof inner.nextCursor === 'string' ? inner.nextCursor : undefined;
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
pages,
|
|
343
|
+
total: pages.length,
|
|
344
|
+
nextCursor,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Second source of truth vs emdash seed.json (stackbilt-emdash/seed/seed.json).
|
|
2
|
+
// Intentional: this is the client-side MCP validator gate — narrower-than-server
|
|
3
|
+
// is fail-closed (daemon rejects, surfaces loudly). Keep in sync with seed.json
|
|
4
|
+
// when extending. Auto-generation from the seed schema is a separate epic.
|
|
5
|
+
export const WIKI_SCOPES = ['aegis', 'concepts', 'entities', 'decisions', 'wiki', 'dreams', 'contracts'] as const;
|
|
6
|
+
export const WIKI_TYPES = ['state', 'architecture', 'decision', 'concept', 'entity', 'agenda', 'synthesis'] as const;
|
|
7
|
+
export const WIKI_CONFIDENCES = ['stable', 'drifting', 'unverified', 'contested'] as const;
|
|
8
|
+
export const WIKI_STATUSES = ['experimental', 'stable', 'deprecated'] as const;
|
|
9
|
+
|
|
10
|
+
export type WikiScope = typeof WIKI_SCOPES[number];
|
|
11
|
+
export type WikiType = typeof WIKI_TYPES[number];
|
|
12
|
+
export type WikiConfidence = typeof WIKI_CONFIDENCES[number];
|
|
13
|
+
export type WikiStatus = typeof WIKI_STATUSES[number];
|
|
14
|
+
|
|
15
|
+
export interface WikiSource {
|
|
16
|
+
type: string;
|
|
17
|
+
ref: string;
|
|
18
|
+
verified_date: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface WriteInput {
|
|
22
|
+
slug: string;
|
|
23
|
+
scope: WikiScope;
|
|
24
|
+
type: WikiType;
|
|
25
|
+
title: string;
|
|
26
|
+
summary: string;
|
|
27
|
+
body: string;
|
|
28
|
+
canonical?: boolean;
|
|
29
|
+
confidence?: WikiConfidence;
|
|
30
|
+
last_verified?: string;
|
|
31
|
+
sources?: WikiSource[];
|
|
32
|
+
related?: string[];
|
|
33
|
+
supersedes?: string[];
|
|
34
|
+
// Contracts-scope frontmatter (Nexus Gate A, aegis#523).
|
|
35
|
+
// Required when scope === 'contracts'; enforced in mcp/handlers.ts.
|
|
36
|
+
// `contract_status` disambiguates from emdash's built-in `status` content-
|
|
37
|
+
// lifecycle column (draft/published/archived) which would collide.
|
|
38
|
+
owners?: string[];
|
|
39
|
+
consumers?: string[];
|
|
40
|
+
guarded_paths?: string[];
|
|
41
|
+
contract_status?: WikiStatus;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface PageSummary {
|
|
45
|
+
slug: string;
|
|
46
|
+
title: string;
|
|
47
|
+
scope: string;
|
|
48
|
+
type: string;
|
|
49
|
+
confidence: string;
|
|
50
|
+
summary: string;
|
|
51
|
+
updated_at: string;
|
|
52
|
+
snippet?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface WikiPage extends PageSummary {
|
|
56
|
+
id: string;
|
|
57
|
+
status?: string;
|
|
58
|
+
revision_id?: string;
|
|
59
|
+
body?: string;
|
|
60
|
+
canonical?: boolean;
|
|
61
|
+
last_verified?: string;
|
|
62
|
+
sources?: WikiSource[];
|
|
63
|
+
related?: string[];
|
|
64
|
+
supersedes?: string[];
|
|
65
|
+
// Contracts-scope frontmatter (Nexus Gate A, aegis#523).
|
|
66
|
+
owners?: string[];
|
|
67
|
+
consumers?: string[];
|
|
68
|
+
guarded_paths?: string[];
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ReadPageResult {
|
|
73
|
+
page: WikiPage | null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface SearchPagesResult {
|
|
77
|
+
results: PageSummary[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface WritePageResult {
|
|
81
|
+
slug: string;
|
|
82
|
+
id: string;
|
|
83
|
+
revision_id?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ListPagesResult {
|
|
87
|
+
pages: PageSummary[];
|
|
88
|
+
total: number;
|
|
89
|
+
nextCursor?: string;
|
|
90
|
+
}
|