@phyzess/pappra 0.0.0-stage → 0.1.0-beta.2
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/LICENSE +21 -0
- package/README.md +80 -2
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/assets/integrations/skills/pappra/SKILL.md +68 -0
- package/assets/integrations/skills/pappra/references/hosts.md +30 -0
- package/assets/integrations/skills/pappra/references/lifecycle.md +37 -0
- package/assets/integrations/skills/pappra/references/operations.md +14 -0
- package/assets/manifest.json +4 -0
- package/dist/app/collaboration.d.ts +170 -0
- package/dist/app/collaboration.js +1650 -0
- package/dist/app/doctor.d.ts +9 -0
- package/dist/app/doctor.js +1030 -0
- package/dist/app/init.d.ts +143 -0
- package/dist/app/init.js +1111 -0
- package/dist/cli/commands/code.d.ts +16 -0
- package/dist/cli/commands/code.js +127 -0
- package/dist/cli/commands/collaboration.d.ts +13 -0
- package/dist/cli/commands/collaboration.js +186 -0
- package/dist/cli/commands/continuity.d.ts +29 -0
- package/dist/cli/commands/continuity.js +133 -0
- package/dist/cli/commands/docs.d.ts +11 -0
- package/dist/cli/commands/docs.js +133 -0
- package/dist/cli/commands/doctor.d.ts +14 -0
- package/dist/cli/commands/doctor.js +57 -0
- package/dist/cli/commands/host.d.ts +13 -0
- package/dist/cli/commands/host.js +125 -0
- package/dist/cli/commands/init.d.ts +51 -0
- package/dist/cli/commands/init.js +668 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +10 -0
- package/dist/cli/output.d.ts +4 -0
- package/dist/cli/output.js +1 -0
- package/dist/cli/run.d.ts +2 -0
- package/dist/cli/run.js +597 -0
- package/dist/code/budget.d.ts +14 -0
- package/dist/code/budget.js +39 -0
- package/dist/code/exec.d.ts +23 -0
- package/dist/code/exec.js +133 -0
- package/dist/code/identity.d.ts +13 -0
- package/dist/code/identity.js +214 -0
- package/dist/code/index.d.ts +6 -0
- package/dist/code/index.js +3 -0
- package/dist/code/providers/codegraph.d.ts +59 -0
- package/dist/code/providers/codegraph.js +567 -0
- package/dist/code/registration.d.ts +9 -0
- package/dist/code/registration.js +293 -0
- package/dist/code/service.d.ts +53 -0
- package/dist/code/service.js +633 -0
- package/dist/code/text.d.ts +3 -0
- package/dist/code/text.js +202 -0
- package/dist/collab/admission.d.ts +450 -0
- package/dist/collab/admission.js +797 -0
- package/dist/collab/canonical.d.ts +26 -0
- package/dist/collab/canonical.js +136 -0
- package/dist/collab/contracts.d.ts +73 -0
- package/dist/collab/contracts.js +76 -0
- package/dist/collab/envelope.d.ts +52 -0
- package/dist/collab/envelope.js +241 -0
- package/dist/collab/fingerprint.d.ts +7 -0
- package/dist/collab/fingerprint.js +58 -0
- package/dist/collab/handoff.d.ts +350 -0
- package/dist/collab/handoff.js +513 -0
- package/dist/collab/index.d.ts +9 -0
- package/dist/collab/index.js +8 -0
- package/dist/collab/lifecycle.d.ts +449 -0
- package/dist/collab/lifecycle.js +534 -0
- package/dist/collab/messages.d.ts +148 -0
- package/dist/collab/messages.js +583 -0
- package/dist/collab/notify.d.ts +47 -0
- package/dist/collab/notify.js +140 -0
- package/dist/collab/preflight.d.ts +31 -0
- package/dist/collab/preflight.js +271 -0
- package/dist/collab/resources.d.ts +119 -0
- package/dist/collab/resources.js +451 -0
- package/dist/collab/store.d.ts +143 -0
- package/dist/collab/store.js +606 -0
- package/dist/collab/types.d.ts +354 -0
- package/dist/collab/types.js +1 -0
- package/dist/collab/validation.d.ts +10 -0
- package/dist/collab/validation.js +303 -0
- package/dist/continuity/context.d.ts +24 -0
- package/dist/continuity/context.js +179 -0
- package/dist/continuity/index.d.ts +3 -0
- package/dist/continuity/index.js +1 -0
- package/dist/continuity/paths.d.ts +37 -0
- package/dist/continuity/paths.js +258 -0
- package/dist/continuity/service.d.ts +59 -0
- package/dist/continuity/service.js +1258 -0
- package/dist/hosts/claude/native.d.ts +79 -0
- package/dist/hosts/claude/native.js +853 -0
- package/dist/hosts/codex/native.d.ts +345 -0
- package/dist/hosts/codex/native.js +1528 -0
- package/dist/hosts/codex/protocol.d.ts +51 -0
- package/dist/hosts/codex/protocol.js +42 -0
- package/dist/hosts/detect.d.ts +28 -0
- package/dist/hosts/detect.js +73 -0
- package/dist/hosts/index.d.ts +10 -0
- package/dist/hosts/index.js +10 -0
- package/dist/hosts/integration/bundle.d.ts +16 -0
- package/dist/hosts/integration/bundle.js +81 -0
- package/dist/hosts/integration/hook-entry.d.ts +1 -0
- package/dist/hosts/integration/hook-entry.js +68 -0
- package/dist/hosts/integration/hook.d.ts +32 -0
- package/dist/hosts/integration/hook.js +781 -0
- package/dist/hosts/integration/pi-extension-runtime.d.ts +18 -0
- package/dist/hosts/integration/pi-extension-runtime.js +89 -0
- package/dist/hosts/integration/pi-template.d.ts +3 -0
- package/dist/hosts/integration/pi-template.js +5 -0
- package/dist/hosts/integration/service.d.ts +41 -0
- package/dist/hosts/integration/service.js +1381 -0
- package/dist/hosts/integration/support.d.ts +36 -0
- package/dist/hosts/integration/support.js +41 -0
- package/dist/hosts/integration/templates.d.ts +22 -0
- package/dist/hosts/integration/templates.js +70 -0
- package/dist/hosts/pi/protocol.d.ts +22 -0
- package/dist/hosts/pi/protocol.js +28 -0
- package/dist/hosts/pi/query-result.d.ts +22 -0
- package/dist/hosts/pi/query-result.js +710 -0
- package/dist/hosts/pi/rpc.d.ts +937 -0
- package/dist/hosts/pi/rpc.js +1621 -0
- package/dist/hosts/pilot.d.ts +485 -0
- package/dist/hosts/pilot.js +593 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/infra/fs.d.ts +27 -0
- package/dist/infra/fs.js +130 -0
- package/dist/infra/git.d.ts +60 -0
- package/dist/infra/git.js +358 -0
- package/dist/infra/owned-process-group.d.ts +173 -0
- package/dist/infra/owned-process-group.js +571 -0
- package/dist/install/apply.d.ts +65 -0
- package/dist/install/apply.js +663 -0
- package/dist/install/index.d.ts +5 -0
- package/dist/install/index.js +5 -0
- package/dist/install/ownership.d.ts +85 -0
- package/dist/install/ownership.js +430 -0
- package/dist/install/plan.d.ts +39 -0
- package/dist/install/plan.js +97 -0
- package/dist/install/rollback.d.ts +18 -0
- package/dist/install/rollback.js +544 -0
- package/dist/install/runtime.d.ts +76 -0
- package/dist/install/runtime.js +1145 -0
- package/dist/knowledge/catalog.d.ts +47 -0
- package/dist/knowledge/catalog.js +308 -0
- package/dist/knowledge/context.d.ts +28 -0
- package/dist/knowledge/context.js +61 -0
- package/dist/knowledge/index.d.ts +3 -0
- package/dist/knowledge/index.js +1 -0
- package/dist/knowledge/journal.d.ts +16 -0
- package/dist/knowledge/journal.js +109 -0
- package/dist/knowledge/lifecycle.d.ts +8 -0
- package/dist/knowledge/lifecycle.js +1001 -0
- package/dist/knowledge/paths.d.ts +37 -0
- package/dist/knowledge/paths.js +236 -0
- package/dist/knowledge/refs.d.ts +36 -0
- package/dist/knowledge/refs.js +267 -0
- package/dist/knowledge/service.d.ts +21 -0
- package/dist/knowledge/service.js +393 -0
- package/dist/knowledge/types.d.ts +171 -0
- package/dist/knowledge/types.js +1 -0
- package/dist/knowledge/write.d.ts +8 -0
- package/dist/knowledge/write.js +1223 -0
- package/dist/package-info.d.ts +6 -0
- package/dist/package-info.js +19 -0
- package/dist/schemas/code.d.ts +377 -0
- package/dist/schemas/code.js +145 -0
- package/dist/schemas/collaboration.d.ts +115 -0
- package/dist/schemas/collaboration.js +106 -0
- package/dist/schemas/continuity.d.ts +315 -0
- package/dist/schemas/continuity.js +199 -0
- package/dist/schemas/diagnostics.d.ts +28 -0
- package/dist/schemas/diagnostics.js +27 -0
- package/dist/schemas/doctor.d.ts +157 -0
- package/dist/schemas/doctor.js +75 -0
- package/dist/schemas/document.d.ts +644 -0
- package/dist/schemas/document.js +296 -0
- package/dist/schemas/errors.d.ts +177 -0
- package/dist/schemas/errors.js +176 -0
- package/dist/schemas/evidence.d.ts +24 -0
- package/dist/schemas/evidence.js +16 -0
- package/dist/schemas/host.d.ts +436 -0
- package/dist/schemas/host.js +201 -0
- package/dist/schemas/index.d.ts +15 -0
- package/dist/schemas/index.js +15 -0
- package/dist/schemas/init.d.ts +121 -0
- package/dist/schemas/init.js +94 -0
- package/dist/schemas/install.d.ts +213 -0
- package/dist/schemas/install.js +139 -0
- package/dist/schemas/machine.d.ts +43 -0
- package/dist/schemas/machine.js +96 -0
- package/dist/schemas/outcome.d.ts +23 -0
- package/dist/schemas/outcome.js +17 -0
- package/dist/schemas/paths.d.ts +19 -0
- package/dist/schemas/paths.js +63 -0
- package/dist/schemas/workspace.d.ts +120 -0
- package/dist/schemas/workspace.js +201 -0
- package/dist/workspace/config-file.d.ts +36 -0
- package/dist/workspace/config-file.js +237 -0
- package/dist/workspace/config.d.ts +28 -0
- package/dist/workspace/config.js +79 -0
- package/dist/workspace/discover.d.ts +34 -0
- package/dist/workspace/discover.js +195 -0
- package/dist/workspace/documents.d.ts +21 -0
- package/dist/workspace/documents.js +395 -0
- package/dist/workspace/ignore.d.ts +10 -0
- package/dist/workspace/ignore.js +48 -0
- package/dist/workspace/index.d.ts +7 -0
- package/dist/workspace/index.js +3 -0
- package/dist/workspace/repositories.d.ts +24 -0
- package/dist/workspace/repositories.js +411 -0
- package/dist/workspace/resolve.d.ts +24 -0
- package/dist/workspace/resolve.js +268 -0
- package/dist/workspace/types.d.ts +71 -0
- package/dist/workspace/types.js +1 -0
- package/dist/workspace/validation.d.ts +18 -0
- package/dist/workspace/validation.js +197 -0
- package/package.json +105 -4
- package/skills/pappra/SKILL.md +68 -0
- package/skills/pappra/references/hosts.md +30 -0
- package/skills/pappra/references/lifecycle.md +37 -0
- package/skills/pappra/references/operations.md +14 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { createDiagnostic } from '../schemas/diagnostics.js';
|
|
2
|
+
import { diagnosticCodes } from '../schemas/errors.js';
|
|
3
|
+
import { loadKnowledgeContext } from './context.js';
|
|
4
|
+
import { loadKnowledgeCatalogs } from './catalog.js';
|
|
5
|
+
import { inspectFreshness, findRecord } from './refs.js';
|
|
6
|
+
import { resolveKnowledgeLocation, readKnowledgeFile } from './paths.js';
|
|
7
|
+
import { applyWrite, proposeWrite, registerDocument } from './write.js';
|
|
8
|
+
import { archiveDocument, deleteExplicit, previewDelete, shareDocument, supersedeDocument, } from './lifecycle.js';
|
|
9
|
+
const MAX_DOCUMENT_BYTES = 8 * 1024 * 1024;
|
|
10
|
+
function outcomeDiagnostic(failure, source) {
|
|
11
|
+
return createDiagnostic({
|
|
12
|
+
code: failure.code,
|
|
13
|
+
severity: 'error',
|
|
14
|
+
message: failure.message,
|
|
15
|
+
source,
|
|
16
|
+
hint: failure.nextAction,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function contextFailure(diagnostics) {
|
|
20
|
+
return { ok: false, diagnostics };
|
|
21
|
+
}
|
|
22
|
+
async function getContext(root) {
|
|
23
|
+
const loaded = await loadKnowledgeContext(root);
|
|
24
|
+
if (!loaded.ok || loaded.context === null) {
|
|
25
|
+
return contextFailure(loaded.diagnostics);
|
|
26
|
+
}
|
|
27
|
+
return { ok: true, context: loaded.context };
|
|
28
|
+
}
|
|
29
|
+
function supersessionDiagnostics(records) {
|
|
30
|
+
const diagnostics = [];
|
|
31
|
+
const byId = new Map(records.map((record) => [record.documentId, record]));
|
|
32
|
+
for (const record of records) {
|
|
33
|
+
if (record.supersededBy === null)
|
|
34
|
+
continue;
|
|
35
|
+
const seen = new Set([record.documentId]);
|
|
36
|
+
let current = record;
|
|
37
|
+
while (current !== null && current.supersededBy !== null) {
|
|
38
|
+
if (seen.has(current.supersededBy) &&
|
|
39
|
+
current.documentId !== record.documentId) {
|
|
40
|
+
diagnostics.push(createDiagnostic({
|
|
41
|
+
code: diagnosticCodes.document_supersede_invalid,
|
|
42
|
+
severity: 'error',
|
|
43
|
+
message: `Supersession chain contains a cycle at ${current.supersededBy}.`,
|
|
44
|
+
source: 'knowledge catalog',
|
|
45
|
+
hint: 'Repair the chain manually; Pappra does not rewrite historical decisions.',
|
|
46
|
+
}));
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
seen.add(current.supersededBy);
|
|
50
|
+
const next = byId.get(current.supersededBy);
|
|
51
|
+
if (next === undefined) {
|
|
52
|
+
diagnostics.push(createDiagnostic({
|
|
53
|
+
code: diagnosticCodes.document_supersede_invalid,
|
|
54
|
+
severity: 'error',
|
|
55
|
+
message: `Supersession target ${current.supersededBy} is missing.`,
|
|
56
|
+
source: 'knowledge catalog',
|
|
57
|
+
hint: 'Keep superseded documents until their replacement is registered.',
|
|
58
|
+
}));
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
if (current.visibility === 'shareable' && next.visibility === 'private') {
|
|
62
|
+
diagnostics.push(createDiagnostic({
|
|
63
|
+
code: diagnosticCodes.document_visibility_invalid,
|
|
64
|
+
severity: 'error',
|
|
65
|
+
message: 'A shareable supersession record points to a private document.',
|
|
66
|
+
source: 'knowledge catalog',
|
|
67
|
+
hint: 'Keep shared supersession chains within the shared catalog.',
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
current = next;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return diagnostics;
|
|
74
|
+
}
|
|
75
|
+
function filteredDiagnostics(context, catalogs, includePrivate) {
|
|
76
|
+
const diagnostics = [...context.diagnostics, ...catalogs.diagnostics];
|
|
77
|
+
if (includePrivate)
|
|
78
|
+
return diagnostics;
|
|
79
|
+
const privatePrefix = `${context.root}/.pappra/local/knowledge`;
|
|
80
|
+
return diagnostics.filter((entry) => entry.source !== privatePrefix &&
|
|
81
|
+
!entry.source.startsWith(`${privatePrefix}/`));
|
|
82
|
+
}
|
|
83
|
+
export class KnowledgeService {
|
|
84
|
+
root;
|
|
85
|
+
#options;
|
|
86
|
+
constructor(root, options = {}) {
|
|
87
|
+
this.root = root;
|
|
88
|
+
this.#options = options;
|
|
89
|
+
}
|
|
90
|
+
async register(input) {
|
|
91
|
+
const loaded = await getContext(this.root);
|
|
92
|
+
if (!loaded.ok) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
status: 'failed',
|
|
96
|
+
record: null,
|
|
97
|
+
diagnostics: [...loaded.diagnostics],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return registerDocument(loaded.context, input);
|
|
101
|
+
}
|
|
102
|
+
async read(documentId) {
|
|
103
|
+
const loaded = await getContext(this.root);
|
|
104
|
+
if (!loaded.ok) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
status: 'failed',
|
|
108
|
+
record: null,
|
|
109
|
+
content: null,
|
|
110
|
+
text: null,
|
|
111
|
+
digest: null,
|
|
112
|
+
freshness: null,
|
|
113
|
+
diagnostics: [...loaded.diagnostics],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const context = loaded.context;
|
|
117
|
+
const catalogs = await loadKnowledgeCatalogs(context);
|
|
118
|
+
const diagnostics = filteredDiagnostics(context, catalogs, true);
|
|
119
|
+
if (!catalogs.ok) {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
status: 'conflict',
|
|
123
|
+
record: null,
|
|
124
|
+
content: null,
|
|
125
|
+
text: null,
|
|
126
|
+
digest: null,
|
|
127
|
+
freshness: null,
|
|
128
|
+
diagnostics,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const record = findRecord(catalogs, documentId);
|
|
132
|
+
if (record === null) {
|
|
133
|
+
return {
|
|
134
|
+
ok: false,
|
|
135
|
+
status: 'not-found',
|
|
136
|
+
record: null,
|
|
137
|
+
content: null,
|
|
138
|
+
text: null,
|
|
139
|
+
digest: null,
|
|
140
|
+
freshness: null,
|
|
141
|
+
diagnostics,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const resolved = await resolveKnowledgeLocation(context, record.location);
|
|
145
|
+
if (!resolved.ok) {
|
|
146
|
+
diagnostics.push(outcomeDiagnostic(resolved, context.configPath));
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
status: 'failed',
|
|
150
|
+
record,
|
|
151
|
+
content: null,
|
|
152
|
+
text: null,
|
|
153
|
+
digest: null,
|
|
154
|
+
freshness: null,
|
|
155
|
+
diagnostics,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const file = await readKnowledgeFile(resolved.value.absolutePath, MAX_DOCUMENT_BYTES);
|
|
159
|
+
if (!file.ok) {
|
|
160
|
+
diagnostics.push(outcomeDiagnostic(file, resolved.value.absolutePath));
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
status: 'failed',
|
|
164
|
+
record,
|
|
165
|
+
content: null,
|
|
166
|
+
text: null,
|
|
167
|
+
digest: null,
|
|
168
|
+
freshness: null,
|
|
169
|
+
diagnostics,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
let text = null;
|
|
173
|
+
try {
|
|
174
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(file.value.bytes);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
text = null;
|
|
178
|
+
}
|
|
179
|
+
const freshness = await inspectFreshness(context, catalogs, record, MAX_DOCUMENT_BYTES);
|
|
180
|
+
return {
|
|
181
|
+
ok: true,
|
|
182
|
+
status: 'ok',
|
|
183
|
+
record,
|
|
184
|
+
content: file.value.bytes,
|
|
185
|
+
text,
|
|
186
|
+
digest: file.value.digest,
|
|
187
|
+
freshness,
|
|
188
|
+
diagnostics,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async list(options = {}) {
|
|
192
|
+
const includePrivate = options.includePrivate === true;
|
|
193
|
+
const includeArchived = options.includeArchived === true;
|
|
194
|
+
const loaded = await getContext(this.root);
|
|
195
|
+
if (!loaded.ok) {
|
|
196
|
+
return {
|
|
197
|
+
ok: false,
|
|
198
|
+
records: [],
|
|
199
|
+
diagnostics: [...loaded.diagnostics],
|
|
200
|
+
catalogDigest: '',
|
|
201
|
+
includePrivate,
|
|
202
|
+
includeArchived,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const context = loaded.context;
|
|
206
|
+
const catalogs = await loadKnowledgeCatalogs(context);
|
|
207
|
+
const diagnostics = filteredDiagnostics(context, catalogs, includePrivate);
|
|
208
|
+
const records = [...catalogs.records.values()]
|
|
209
|
+
.filter((record) => (includePrivate || record.visibility === 'shareable') &&
|
|
210
|
+
(includeArchived || record.lifecycle === 'active'))
|
|
211
|
+
.sort((left, right) => left.documentId.localeCompare(right.documentId));
|
|
212
|
+
return {
|
|
213
|
+
ok: catalogs.ok,
|
|
214
|
+
records,
|
|
215
|
+
diagnostics,
|
|
216
|
+
catalogDigest: catalogs.catalogDigest,
|
|
217
|
+
includePrivate,
|
|
218
|
+
includeArchived,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
async check(options = {}) {
|
|
222
|
+
const includePrivate = options.includePrivate === true;
|
|
223
|
+
const loaded = await getContext(this.root);
|
|
224
|
+
if (!loaded.ok) {
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
checkedAt: new Date().toISOString(),
|
|
228
|
+
catalogDigest: '',
|
|
229
|
+
entries: [],
|
|
230
|
+
diagnostics: [...loaded.diagnostics],
|
|
231
|
+
summary: { total: 0, current: 0, needsReview: 0, unknown: 0 },
|
|
232
|
+
coverage: {
|
|
233
|
+
catalogs: includePrivate ? 'shared-and-private' : 'shared',
|
|
234
|
+
pathReferences: 'unchecked',
|
|
235
|
+
repositoryContent: 'not-scanned',
|
|
236
|
+
taskExecution: 'unknown',
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const context = loaded.context;
|
|
241
|
+
const catalogs = await loadKnowledgeCatalogs(context);
|
|
242
|
+
const diagnostics = filteredDiagnostics(context, catalogs, includePrivate);
|
|
243
|
+
const selected = [...catalogs.records.values()].filter((record) => includePrivate || record.visibility === 'shareable');
|
|
244
|
+
diagnostics.push(...supersessionDiagnostics(selected));
|
|
245
|
+
const entries = [];
|
|
246
|
+
const canonicalPaths = new Map();
|
|
247
|
+
for (const record of selected) {
|
|
248
|
+
const resolved = await resolveKnowledgeLocation(context, record.location);
|
|
249
|
+
let fileDigest = null;
|
|
250
|
+
if (resolved.ok) {
|
|
251
|
+
const canonicalKey = process.platform === 'win32'
|
|
252
|
+
? resolved.value.canonicalPath.toLowerCase()
|
|
253
|
+
: resolved.value.canonicalPath;
|
|
254
|
+
const existing = canonicalPaths.get(canonicalKey);
|
|
255
|
+
if (existing !== undefined && existing !== record.documentId) {
|
|
256
|
+
diagnostics.push(createDiagnostic({
|
|
257
|
+
code: diagnosticCodes.document_location_conflict,
|
|
258
|
+
severity: 'error',
|
|
259
|
+
message: `Canonical document path is registered by both ${existing} and ${record.documentId}.`,
|
|
260
|
+
source: resolved.value.absolutePath,
|
|
261
|
+
hint: 'Keep one documentId per real file; representation aliases are a location conflict.',
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
canonicalPaths.set(canonicalKey, record.documentId);
|
|
266
|
+
}
|
|
267
|
+
const file = await readKnowledgeFile(resolved.value.absolutePath, MAX_DOCUMENT_BYTES);
|
|
268
|
+
if (file.ok)
|
|
269
|
+
fileDigest = file.value.digest;
|
|
270
|
+
else
|
|
271
|
+
diagnostics.push(outcomeDiagnostic(file, resolved.value.absolutePath));
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
diagnostics.push(outcomeDiagnostic(resolved, context.configPath));
|
|
275
|
+
}
|
|
276
|
+
const freshness = await inspectFreshness(context, catalogs, record, MAX_DOCUMENT_BYTES);
|
|
277
|
+
entries.push({
|
|
278
|
+
documentId: record.documentId,
|
|
279
|
+
scope: record.visibility === 'shareable' ? 'shared' : 'private',
|
|
280
|
+
lifecycle: record.lifecycle,
|
|
281
|
+
maturity: record.maturity,
|
|
282
|
+
fileDigest,
|
|
283
|
+
recordDigest: record.digest,
|
|
284
|
+
freshness: freshness.freshness,
|
|
285
|
+
reasons: freshness.reasons,
|
|
286
|
+
sourceChecks: freshness.sourceChecks,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const current = entries.filter((entry) => entry.freshness === 'current').length;
|
|
290
|
+
const needsReview = entries.filter((entry) => entry.freshness === 'needs-review').length;
|
|
291
|
+
const unknown = entries.filter((entry) => entry.freshness === 'unknown').length;
|
|
292
|
+
const ok = diagnostics.every((entry) => entry.severity !== 'error') &&
|
|
293
|
+
current === entries.length;
|
|
294
|
+
return {
|
|
295
|
+
ok,
|
|
296
|
+
checkedAt: new Date().toISOString(),
|
|
297
|
+
catalogDigest: catalogs.catalogDigest,
|
|
298
|
+
entries,
|
|
299
|
+
diagnostics,
|
|
300
|
+
summary: {
|
|
301
|
+
total: entries.length,
|
|
302
|
+
current,
|
|
303
|
+
needsReview,
|
|
304
|
+
unknown,
|
|
305
|
+
},
|
|
306
|
+
coverage: {
|
|
307
|
+
catalogs: includePrivate ? 'shared-and-private' : 'shared',
|
|
308
|
+
pathReferences: 'unchecked',
|
|
309
|
+
repositoryContent: 'not-scanned',
|
|
310
|
+
taskExecution: 'unknown',
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
async proposeWrite(input) {
|
|
315
|
+
const loaded = await getContext(this.root);
|
|
316
|
+
if (!loaded.ok) {
|
|
317
|
+
return {
|
|
318
|
+
ok: false,
|
|
319
|
+
status: 'failed',
|
|
320
|
+
proposal: null,
|
|
321
|
+
content: null,
|
|
322
|
+
diagnostics: [...loaded.diagnostics],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return proposeWrite(loaded.context, input);
|
|
326
|
+
}
|
|
327
|
+
async applyWrite(input) {
|
|
328
|
+
return applyWrite(input, this.root, this.#options);
|
|
329
|
+
}
|
|
330
|
+
async archive(documentId) {
|
|
331
|
+
const loaded = await getContext(this.root);
|
|
332
|
+
if (!loaded.ok) {
|
|
333
|
+
return {
|
|
334
|
+
ok: false,
|
|
335
|
+
status: 'failed',
|
|
336
|
+
record: null,
|
|
337
|
+
diagnostics: [...loaded.diagnostics],
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return archiveDocument(loaded.context, documentId);
|
|
341
|
+
}
|
|
342
|
+
async supersede(input) {
|
|
343
|
+
const loaded = await getContext(this.root);
|
|
344
|
+
if (!loaded.ok) {
|
|
345
|
+
return {
|
|
346
|
+
ok: false,
|
|
347
|
+
status: 'failed',
|
|
348
|
+
record: null,
|
|
349
|
+
diagnostics: [...loaded.diagnostics],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return supersedeDocument(loaded.context, input);
|
|
353
|
+
}
|
|
354
|
+
async share(documentId) {
|
|
355
|
+
const loaded = await getContext(this.root);
|
|
356
|
+
if (!loaded.ok) {
|
|
357
|
+
return {
|
|
358
|
+
ok: false,
|
|
359
|
+
status: 'failed',
|
|
360
|
+
record: null,
|
|
361
|
+
diagnostics: [...loaded.diagnostics],
|
|
362
|
+
residualPaths: [],
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return shareDocument(loaded.context, documentId);
|
|
366
|
+
}
|
|
367
|
+
async previewDelete(input) {
|
|
368
|
+
const loaded = await getContext(this.root);
|
|
369
|
+
if (!loaded.ok) {
|
|
370
|
+
return {
|
|
371
|
+
ok: false,
|
|
372
|
+
status: 'failed',
|
|
373
|
+
plan: null,
|
|
374
|
+
diagnostics: [...loaded.diagnostics],
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return previewDelete(loaded.context, input);
|
|
378
|
+
}
|
|
379
|
+
async deleteExplicit(input) {
|
|
380
|
+
const loaded = await getContext(this.root);
|
|
381
|
+
if (!loaded.ok) {
|
|
382
|
+
return {
|
|
383
|
+
ok: false,
|
|
384
|
+
status: 'failed',
|
|
385
|
+
documentId: null,
|
|
386
|
+
diagnostics: [...loaded.diagnostics],
|
|
387
|
+
residualPaths: [],
|
|
388
|
+
journalPath: null,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return deleteExplicit(loaded.context, input.plan);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { Diagnostic } from '../schemas/diagnostics.js';
|
|
2
|
+
import type { DocumentCatalog, DocumentKind, DocumentLocation, DocumentMaturity, DocumentOwner, DocumentRecord, DocumentSource, DocumentVisibility, DocumentWriteProposal, KnowledgeDeletePlan } from '../schemas/document.js';
|
|
3
|
+
import type { FreshnessReason, FreshnessReport, SourceCheck } from './refs.js';
|
|
4
|
+
export interface RegisterDocumentInput {
|
|
5
|
+
readonly location: DocumentLocation;
|
|
6
|
+
readonly kind: DocumentKind;
|
|
7
|
+
readonly maturity: DocumentMaturity;
|
|
8
|
+
readonly owner: DocumentOwner;
|
|
9
|
+
readonly taskId?: string | null;
|
|
10
|
+
readonly sources?: readonly DocumentSource[];
|
|
11
|
+
readonly lastReviewedAt?: string | null;
|
|
12
|
+
readonly visibility?: DocumentVisibility;
|
|
13
|
+
/** Advanced/migration use only; the service never derives IDs from paths. */
|
|
14
|
+
readonly documentId?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface RegisterDocumentResult {
|
|
17
|
+
readonly ok: boolean;
|
|
18
|
+
readonly status: 'registered' | 'existing' | 'conflict' | 'failed';
|
|
19
|
+
readonly record: DocumentRecord | null;
|
|
20
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
21
|
+
}
|
|
22
|
+
export interface ReadDocumentResult {
|
|
23
|
+
readonly ok: boolean;
|
|
24
|
+
readonly status: 'ok' | 'not-found' | 'conflict' | 'failed';
|
|
25
|
+
readonly record: DocumentRecord | null;
|
|
26
|
+
readonly content: Buffer | null;
|
|
27
|
+
readonly text: string | null;
|
|
28
|
+
readonly digest: string | null;
|
|
29
|
+
readonly freshness: FreshnessReport | null;
|
|
30
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
31
|
+
}
|
|
32
|
+
export interface ListDocumentsOptions {
|
|
33
|
+
readonly includePrivate?: boolean;
|
|
34
|
+
readonly includeArchived?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface ListDocumentsResult {
|
|
37
|
+
readonly ok: boolean;
|
|
38
|
+
readonly records: readonly DocumentRecord[];
|
|
39
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
40
|
+
readonly catalogDigest: string;
|
|
41
|
+
readonly includePrivate: boolean;
|
|
42
|
+
readonly includeArchived: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface DocumentCheckEntry {
|
|
45
|
+
readonly documentId: string;
|
|
46
|
+
readonly scope: 'shared' | 'private';
|
|
47
|
+
readonly lifecycle: DocumentRecord['lifecycle'];
|
|
48
|
+
readonly maturity: DocumentRecord['maturity'];
|
|
49
|
+
readonly fileDigest: string | null;
|
|
50
|
+
readonly recordDigest: string;
|
|
51
|
+
readonly freshness: FreshnessReport['freshness'];
|
|
52
|
+
readonly reasons: readonly FreshnessReason[];
|
|
53
|
+
readonly sourceChecks: readonly SourceCheck[];
|
|
54
|
+
}
|
|
55
|
+
export interface DocumentCheckResult {
|
|
56
|
+
readonly ok: boolean;
|
|
57
|
+
readonly checkedAt: string;
|
|
58
|
+
readonly catalogDigest: string;
|
|
59
|
+
readonly entries: readonly DocumentCheckEntry[];
|
|
60
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
61
|
+
readonly summary: {
|
|
62
|
+
readonly total: number;
|
|
63
|
+
readonly current: number;
|
|
64
|
+
readonly needsReview: number;
|
|
65
|
+
readonly unknown: number;
|
|
66
|
+
};
|
|
67
|
+
readonly coverage: {
|
|
68
|
+
readonly catalogs: 'shared' | 'shared-and-private';
|
|
69
|
+
readonly pathReferences: 'unchecked';
|
|
70
|
+
readonly repositoryContent: 'not-scanned';
|
|
71
|
+
readonly taskExecution: 'unknown';
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export interface CreateDocumentWriteInput {
|
|
75
|
+
readonly mode: 'create';
|
|
76
|
+
readonly location: DocumentLocation;
|
|
77
|
+
readonly kind: DocumentKind;
|
|
78
|
+
readonly maturity: DocumentMaturity;
|
|
79
|
+
readonly owner: DocumentOwner;
|
|
80
|
+
readonly taskId?: string | null;
|
|
81
|
+
readonly sources?: readonly DocumentSource[];
|
|
82
|
+
readonly lastReviewedAt?: string | null;
|
|
83
|
+
readonly visibility?: DocumentVisibility;
|
|
84
|
+
readonly documentId?: string;
|
|
85
|
+
readonly content: Uint8Array | string;
|
|
86
|
+
readonly expectedDigest: null;
|
|
87
|
+
}
|
|
88
|
+
export interface UpdateDocumentWriteInput {
|
|
89
|
+
readonly mode: 'update';
|
|
90
|
+
readonly documentId: string;
|
|
91
|
+
readonly content: Uint8Array | string;
|
|
92
|
+
readonly expectedDigest: string;
|
|
93
|
+
}
|
|
94
|
+
export type ProposeWriteInput = CreateDocumentWriteInput | UpdateDocumentWriteInput;
|
|
95
|
+
export interface ProposeWriteResult {
|
|
96
|
+
readonly ok: boolean;
|
|
97
|
+
readonly status: 'proposed' | 'conflict' | 'stale' | 'failed';
|
|
98
|
+
readonly proposal: DocumentWriteProposal | null;
|
|
99
|
+
readonly content: Buffer | null;
|
|
100
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
101
|
+
}
|
|
102
|
+
export interface KnowledgeServiceOptions {
|
|
103
|
+
/** Test/fault-injection seam; the CLI never supplies these hooks. */
|
|
104
|
+
readonly beforeFileSync?: (path: string) => Promise<void> | void;
|
|
105
|
+
readonly failFileSync?: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface ApplyWriteInput {
|
|
108
|
+
readonly proposal: DocumentWriteProposal;
|
|
109
|
+
readonly content: Uint8Array | string;
|
|
110
|
+
}
|
|
111
|
+
export interface ApplyWriteResult {
|
|
112
|
+
readonly ok: boolean;
|
|
113
|
+
readonly status: 'applied' | 'conflict' | 'stale' | 'locked' | 'partial' | 'failed';
|
|
114
|
+
readonly record: DocumentRecord | null;
|
|
115
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
116
|
+
readonly residualPaths: readonly string[];
|
|
117
|
+
readonly journalPath: string | null;
|
|
118
|
+
}
|
|
119
|
+
export interface ArchiveDocumentResult {
|
|
120
|
+
readonly ok: boolean;
|
|
121
|
+
readonly status: 'archived' | 'unchanged' | 'not-found' | 'conflict' | 'locked' | 'failed';
|
|
122
|
+
readonly record: DocumentRecord | null;
|
|
123
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
124
|
+
}
|
|
125
|
+
export interface SupersedeDocumentInput {
|
|
126
|
+
readonly documentId: string;
|
|
127
|
+
readonly supersededBy: string;
|
|
128
|
+
}
|
|
129
|
+
export interface SupersedeDocumentResult {
|
|
130
|
+
readonly ok: boolean;
|
|
131
|
+
readonly status: 'superseded' | 'unchanged' | 'not-found' | 'conflict' | 'locked' | 'failed';
|
|
132
|
+
readonly record: DocumentRecord | null;
|
|
133
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
134
|
+
}
|
|
135
|
+
export interface ShareDocumentResult {
|
|
136
|
+
readonly ok: boolean;
|
|
137
|
+
readonly status: 'shared' | 'unchanged' | 'not-found' | 'conflict' | 'locked' | 'partial' | 'failed';
|
|
138
|
+
readonly record: DocumentRecord | null;
|
|
139
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
140
|
+
readonly residualPaths: readonly string[];
|
|
141
|
+
}
|
|
142
|
+
export interface PreviewDeleteInput {
|
|
143
|
+
readonly documentId: string;
|
|
144
|
+
readonly removeFile: boolean;
|
|
145
|
+
}
|
|
146
|
+
export interface PreviewDeleteResult {
|
|
147
|
+
readonly ok: boolean;
|
|
148
|
+
readonly status: 'ready' | 'blocked' | 'not-found' | 'stale' | 'failed';
|
|
149
|
+
readonly plan: KnowledgeDeletePlan | null;
|
|
150
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
151
|
+
}
|
|
152
|
+
export interface DeleteExplicitInput {
|
|
153
|
+
readonly plan: KnowledgeDeletePlan;
|
|
154
|
+
}
|
|
155
|
+
export interface DeleteExplicitResult {
|
|
156
|
+
readonly ok: boolean;
|
|
157
|
+
readonly status: 'deleted' | 'unregistered' | 'blocked' | 'stale' | 'locked' | 'partial' | 'failed';
|
|
158
|
+
readonly documentId: string | null;
|
|
159
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
160
|
+
readonly residualPaths: readonly string[];
|
|
161
|
+
readonly journalPath: string | null;
|
|
162
|
+
}
|
|
163
|
+
export interface CatalogInspection {
|
|
164
|
+
readonly scope: 'shared' | 'private';
|
|
165
|
+
readonly path: string;
|
|
166
|
+
readonly exists: boolean;
|
|
167
|
+
readonly valid: boolean;
|
|
168
|
+
readonly recordCount: number;
|
|
169
|
+
readonly catalog: DocumentCatalog | null;
|
|
170
|
+
}
|
|
171
|
+
export type { FreshnessReport };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type DocumentWriteProposal } from '../schemas/document.js';
|
|
2
|
+
import type { KnowledgeContext } from './context.js';
|
|
3
|
+
import type { ApplyWriteInput, ApplyWriteResult, KnowledgeServiceOptions, ProposeWriteInput, ProposeWriteResult, RegisterDocumentInput, RegisterDocumentResult } from './types.js';
|
|
4
|
+
export declare function defaultVisibility(context: KnowledgeContext): 'private' | 'shareable';
|
|
5
|
+
export declare function registerDocument(context: KnowledgeContext, input: RegisterDocumentInput): Promise<RegisterDocumentResult>;
|
|
6
|
+
export declare function proposeWrite(context: KnowledgeContext, input: ProposeWriteInput): Promise<ProposeWriteResult>;
|
|
7
|
+
export declare function applyWrite(input: ApplyWriteInput, root: string, options?: KnowledgeServiceOptions): Promise<ApplyWriteResult>;
|
|
8
|
+
export declare function applyWriteAt(inputContext: KnowledgeContext, proposal: DocumentWriteProposal, content: Buffer, options?: KnowledgeServiceOptions): Promise<ApplyWriteResult>;
|