@hraness/kb 0.17.1
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/DISCLOSURE +43 -0
- package/LICENSE +21 -0
- package/README.md +545 -0
- package/dist/agent-context.js +35 -0
- package/dist/agent-guide-audit.js +18 -0
- package/dist/attachments.js +19 -0
- package/dist/authoring.js +33 -0
- package/dist/benchmark.js +19 -0
- package/dist/capture.js +49 -0
- package/dist/cli.js +3646 -0
- package/dist/clip/acquire.js +57 -0
- package/dist/clip/args.js +21 -0
- package/dist/clip/bounded-byte-buffer.js +9 -0
- package/dist/clip/browser-profiles.js +17 -0
- package/dist/clip/bundle-reader.js +11 -0
- package/dist/clip/cli.js +27 -0
- package/dist/clip/cookies.js +21 -0
- package/dist/clip/defuddle-worker.js +84 -0
- package/dist/clip/doctor.js +32 -0
- package/dist/clip/jobs.js +784 -0
- package/dist/clip/network-proxy.js +9 -0
- package/dist/clip/network.js +28 -0
- package/dist/clip/persist.js +30 -0
- package/dist/clip/refresh.js +11 -0
- package/dist/clip/terminal.js +11 -0
- package/dist/evaluation-builder.js +12238 -0
- package/dist/evaluation-kb.js +26 -0
- package/dist/evaluation.js +35 -0
- package/dist/git.js +39 -0
- package/dist/graph.js +39 -0
- package/dist/index-01jj6rbv.js +1103 -0
- package/dist/index-06c9ctr6.js +738 -0
- package/dist/index-0kavxzqj.js +2480 -0
- package/dist/index-1gwbassd.js +743 -0
- package/dist/index-1n418kb9.js +827 -0
- package/dist/index-1vrd1rmn.js +56 -0
- package/dist/index-1xxnjn0d.js +171 -0
- package/dist/index-3rm7cz6h.js +373 -0
- package/dist/index-3v2z4f0q.js +328 -0
- package/dist/index-48pz4jpc.js +392 -0
- package/dist/index-4j3tt0c3.js +234 -0
- package/dist/index-5n05se68.js +307 -0
- package/dist/index-5vwpzb5a.js +764 -0
- package/dist/index-6g2pv9d2.js +370 -0
- package/dist/index-7fzc32gf.js +712 -0
- package/dist/index-84x0vjjp.js +432 -0
- package/dist/index-adx6khj5.js +488 -0
- package/dist/index-b88v3vtm.js +805 -0
- package/dist/index-cv6fh7z5.js +462 -0
- package/dist/index-cxfrakt7.js +1067 -0
- package/dist/index-d13v9ckt.js +150 -0
- package/dist/index-dycjbhw2.js +1943 -0
- package/dist/index-dyqwejk5.js +531 -0
- package/dist/index-e5fbsywq.js +658 -0
- package/dist/index-f984hw45.js +927 -0
- package/dist/index-gh719d91.js +63 -0
- package/dist/index-hgve9rh2.js +2046 -0
- package/dist/index-hya40gb2.js +474 -0
- package/dist/index-j4zgmzjr.js +152 -0
- package/dist/index-jsmvyyvf.js +1166 -0
- package/dist/index-kh0pwvdd.js +1974 -0
- package/dist/index-mqx4nd6v.js +119 -0
- package/dist/index-mxxxytys.js +1254 -0
- package/dist/index-n5dd7r0v.js +711 -0
- package/dist/index-npg9z1a4.js +431 -0
- package/dist/index-pj501bh1.js +140 -0
- package/dist/index-qry4vhxk.js +1041 -0
- package/dist/index-s2gw5aw9.js +199 -0
- package/dist/index-vvdj6kca.js +70 -0
- package/dist/index-w2zc0vwa.js +571 -0
- package/dist/index-x3fthpsc.js +585 -0
- package/dist/index-zr53sf63.js +44 -0
- package/dist/index-zxdy5pby.js +2728 -0
- package/dist/index-zzhgcwyt.js +966 -0
- package/dist/index.js +453 -0
- package/dist/navigation.js +13 -0
- package/dist/pdf.js +85 -0
- package/dist/percolate.js +24 -0
- package/dist/portfolio.js +77 -0
- package/dist/query.js +29 -0
- package/dist/repository-memory.js +60 -0
- package/dist/sdk.js +36 -0
- package/dist/search-rules.js +34 -0
- package/dist/search.js +22 -0
- package/dist/semantic.js +39 -0
- package/dist/source-inbox.js +17 -0
- package/dist/untrusted-content.js +32 -0
- package/dist/url-intelligence.js +74 -0
- package/dist/workflow.js +23 -0
- package/dist/workflows/decision-context.js +20 -0
- package/dist/workflows/explain-change.js +8 -0
- package/dist/workflows/index.js +28 -0
- package/dist/workflows/plan-radar.js +10 -0
- package/package.json +409 -0
- package/skills/kb/AGENTS.md +19 -0
- package/skills/kb/SKILL.md +82 -0
- package/skills/kb/agents/openai.yaml +4 -0
- package/skills/kb/references/pdf-review.md +51 -0
- package/skills/kb/references/percolate.md +144 -0
- package/skills/kb/references/plan-structure.md +105 -0
- package/skills/kb/references/plan.md +115 -0
- package/skills/kb/references/query.md +231 -0
- package/skills/kb/references/refresh.md +133 -0
- package/skills/kb/references/save-pdf.md +125 -0
- package/skills/kb/references/save-url.md +178 -0
- package/skills/kb/references/url-authentication.md +75 -0
- package/skills/kb/references/url-platforms.md +25 -0
- package/src/agent-context.ts +1241 -0
- package/src/agent-guide-audit.ts +750 -0
- package/src/attachments.ts +723 -0
- package/src/authoring.ts +1725 -0
- package/src/benchmark.ts +362 -0
- package/src/capture.ts +68 -0
- package/src/cli.ts +3660 -0
- package/src/clip/acquire.ts +1273 -0
- package/src/clip/archive-today.ts +402 -0
- package/src/clip/args.ts +423 -0
- package/src/clip/assets.ts +262 -0
- package/src/clip/bounded-byte-buffer.ts +75 -0
- package/src/clip/browser-profiles.ts +372 -0
- package/src/clip/bundle-reader.ts +608 -0
- package/src/clip/capture.ts +1054 -0
- package/src/clip/cli.ts +181 -0
- package/src/clip/cookies.ts +512 -0
- package/src/clip/doctor.ts +1110 -0
- package/src/clip/extract.ts +868 -0
- package/src/clip/jobs.ts +959 -0
- package/src/clip/lib.ts +861 -0
- package/src/clip/media.ts +1474 -0
- package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
- package/src/clip/metadata-search-tool/Cargo.toml +18 -0
- package/src/clip/metadata-search-tool/runner.ts +685 -0
- package/src/clip/metadata-search-tool/src/main.rs +389 -0
- package/src/clip/metadata-search.ts +687 -0
- package/src/clip/network-proxy.ts +627 -0
- package/src/clip/network.ts +950 -0
- package/src/clip/package-root.ts +54 -0
- package/src/clip/persist.ts +1472 -0
- package/src/clip/platforms.ts +1824 -0
- package/src/clip/refresh.ts +197 -0
- package/src/clip/structured.ts +432 -0
- package/src/clip/terminal.ts +197 -0
- package/src/clip/url-intelligence.ts +581 -0
- package/src/clip/url-metadata-backfill.ts +616 -0
- package/src/clip/url-metadata-cli.ts +251 -0
- package/src/clip/url-metadata.ts +1397 -0
- package/src/evaluation-analysis-v2.ts +1440 -0
- package/src/evaluation-builder.ts +2539 -0
- package/src/evaluation-corpus-authoring.ts +2052 -0
- package/src/evaluation-evidence.ts +1147 -0
- package/src/evaluation-execution-v2.ts +426 -0
- package/src/evaluation-implementation.ts +131 -0
- package/src/evaluation-kb-closure.ts +1430 -0
- package/src/evaluation-kb-runner-v2.ts +735 -0
- package/src/evaluation-kb-v2.ts +2567 -0
- package/src/evaluation-kb.ts +1018 -0
- package/src/evaluation-measurement-v2.ts +1462 -0
- package/src/evaluation-packing-v2.ts +452 -0
- package/src/evaluation-redaction.ts +28 -0
- package/src/evaluation-v2.ts +3857 -0
- package/src/evaluation.ts +1220 -0
- package/src/git.ts +1276 -0
- package/src/graph.ts +1487 -0
- package/src/index.ts +20 -0
- package/src/init.ts +123 -0
- package/src/navigation.ts +277 -0
- package/src/note-lock.ts +529 -0
- package/src/pdf/args.ts +202 -0
- package/src/pdf/capture.ts +298 -0
- package/src/pdf/cli.ts +159 -0
- package/src/pdf/extract.ts +303 -0
- package/src/pdf/layout.ts +618 -0
- package/src/pdf/markdown.ts +154 -0
- package/src/pdf/model.ts +234 -0
- package/src/pdf/ocr.ts +190 -0
- package/src/pdf/persist.ts +314 -0
- package/src/pdf/source.ts +135 -0
- package/src/pdf/tools.ts +146 -0
- package/src/pdf.ts +92 -0
- package/src/percolate.ts +877 -0
- package/src/portfolio-audit.ts +574 -0
- package/src/portfolio-identity.ts +162 -0
- package/src/portfolio-registry.ts +473 -0
- package/src/portfolio.ts +765 -0
- package/src/query.ts +646 -0
- package/src/repository-memory.ts +1150 -0
- package/src/sdk.ts +1452 -0
- package/src/search-rules.ts +712 -0
- package/src/search.ts +710 -0
- package/src/semantic-runtime.ts +1139 -0
- package/src/semantic.ts +2480 -0
- package/src/source-inbox.ts +232 -0
- package/src/untrusted-content.ts +401 -0
- package/src/url-intelligence.ts +104 -0
- package/src/vault.ts +636 -0
- package/src/workflow.ts +653 -0
- package/src/workflows/decision-context.ts +81 -0
- package/src/workflows/explain-change.ts +73 -0
- package/src/workflows/index.ts +3 -0
- package/src/workflows/plan-radar.ts +101 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
MAX_ATTACHMENT_REFERENCES,
|
|
5
|
+
validateMarkdownAttachments,
|
|
6
|
+
type AttachmentIssue,
|
|
7
|
+
} from "./attachments.js";
|
|
8
|
+
import {
|
|
9
|
+
indexGitHistory,
|
|
10
|
+
type GitHistoryDependencies,
|
|
11
|
+
} from "./git.js";
|
|
12
|
+
import {
|
|
13
|
+
documentIdState,
|
|
14
|
+
portfolioDocumentIdentity,
|
|
15
|
+
type PortfolioDocumentIdentity,
|
|
16
|
+
type VaultKey,
|
|
17
|
+
} from "./portfolio-identity.js";
|
|
18
|
+
import {
|
|
19
|
+
loadPortfolioRegistry,
|
|
20
|
+
snapshotPortfolioRegistry,
|
|
21
|
+
resolvePortfolioVault,
|
|
22
|
+
selectAuthorizedVaults,
|
|
23
|
+
validateResolvedPortfolioVaults,
|
|
24
|
+
type PortfolioAuthorityGroup,
|
|
25
|
+
type PortfolioPathDependencies,
|
|
26
|
+
type PortfolioRegistryFileDependencies,
|
|
27
|
+
type PortfolioRegistryV1,
|
|
28
|
+
type PortfolioVaultEntry,
|
|
29
|
+
type ResolvedPortfolioVault,
|
|
30
|
+
} from "./portfolio-registry.js";
|
|
31
|
+
import {
|
|
32
|
+
scanVault,
|
|
33
|
+
type ScanVaultOptions,
|
|
34
|
+
type VaultSnapshot,
|
|
35
|
+
} from "./vault.js";
|
|
36
|
+
|
|
37
|
+
export const DEFAULT_PORTFOLIO_AUDIT_ISSUES = 500;
|
|
38
|
+
export const MAX_PORTFOLIO_AUDIT_ISSUES = 5_000;
|
|
39
|
+
|
|
40
|
+
export type PortfolioAuditSeverity = "advisory" | "error" | "warning";
|
|
41
|
+
|
|
42
|
+
export type PortfolioAuditIssueCode =
|
|
43
|
+
| "ambiguous-link"
|
|
44
|
+
| "attachment"
|
|
45
|
+
| "authority-unresolved"
|
|
46
|
+
| "broken-link"
|
|
47
|
+
| "catalog-stale"
|
|
48
|
+
| "duplicate-content"
|
|
49
|
+
| "duplicate-document-id"
|
|
50
|
+
| "external-relation-unavailable"
|
|
51
|
+
| "git-unavailable"
|
|
52
|
+
| "invalid-document-id"
|
|
53
|
+
| "missing-document-id"
|
|
54
|
+
| "relation"
|
|
55
|
+
| "root-overlap"
|
|
56
|
+
| "scan-unavailable"
|
|
57
|
+
| "vault-unavailable";
|
|
58
|
+
|
|
59
|
+
export type PortfolioAuditVault = {
|
|
60
|
+
readonly owner: string;
|
|
61
|
+
readonly id: string;
|
|
62
|
+
readonly key: VaultKey;
|
|
63
|
+
readonly role: PortfolioVaultEntry["role"];
|
|
64
|
+
readonly visibility: PortfolioVaultEntry["visibility"];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type PortfolioAuditReference = {
|
|
68
|
+
readonly vault: PortfolioAuditVault;
|
|
69
|
+
readonly path: string;
|
|
70
|
+
readonly identity: PortfolioDocumentIdentity;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type PortfolioAuditIssue = {
|
|
74
|
+
readonly code: PortfolioAuditIssueCode;
|
|
75
|
+
readonly severity: PortfolioAuditSeverity;
|
|
76
|
+
readonly message: string;
|
|
77
|
+
readonly vault?: PortfolioAuditVault;
|
|
78
|
+
readonly path?: string;
|
|
79
|
+
readonly line?: number;
|
|
80
|
+
readonly protected?: boolean;
|
|
81
|
+
readonly related?: readonly PortfolioAuditReference[];
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type PortfolioVaultAuditSummary = {
|
|
85
|
+
readonly vault: PortfolioAuditVault;
|
|
86
|
+
readonly status: "audited" | "unavailable";
|
|
87
|
+
readonly notes: number;
|
|
88
|
+
readonly stableDocuments: number;
|
|
89
|
+
readonly legacyDocuments: number;
|
|
90
|
+
readonly index: VaultSnapshot["index"] | null;
|
|
91
|
+
readonly head: string | null;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type PortfolioAuthorityAuditState = {
|
|
95
|
+
readonly id: string;
|
|
96
|
+
readonly state: PortfolioAuthorityGroup["state"];
|
|
97
|
+
readonly protected: boolean;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type PortfolioAuditReport = {
|
|
101
|
+
readonly partial: boolean;
|
|
102
|
+
readonly truncated: boolean;
|
|
103
|
+
readonly selectedVaults: number;
|
|
104
|
+
readonly auditedVaults: number;
|
|
105
|
+
readonly unavailableVaults: number;
|
|
106
|
+
readonly notes: number;
|
|
107
|
+
readonly stableDocuments: number;
|
|
108
|
+
readonly legacyDocuments: number;
|
|
109
|
+
readonly counts: Readonly<Record<PortfolioAuditSeverity, number>>;
|
|
110
|
+
readonly vaults: readonly PortfolioVaultAuditSummary[];
|
|
111
|
+
readonly authority: readonly PortfolioAuthorityAuditState[];
|
|
112
|
+
readonly issues: readonly PortfolioAuditIssue[];
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type AuditKnowledgePortfolioOptions = {
|
|
116
|
+
readonly registryPath: string;
|
|
117
|
+
/** Same immutable snapshot used to derive authorizedVaults, when applicable. */
|
|
118
|
+
readonly registry?: PortfolioRegistryV1;
|
|
119
|
+
readonly workspaceRoot: string;
|
|
120
|
+
readonly authorizedVaults: readonly VaultKey[];
|
|
121
|
+
readonly maxIssues?: number;
|
|
122
|
+
readonly maxAttachmentReferences?: number;
|
|
123
|
+
readonly scan?: Omit<ScanVaultOptions, "catalogMode" | "mentionScope">;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export type PortfolioAuditDependencies = PortfolioRegistryFileDependencies & PortfolioPathDependencies & {
|
|
127
|
+
readonly loadPortfolioRegistry?: typeof loadPortfolioRegistry;
|
|
128
|
+
readonly resolvePortfolioVault?: typeof resolvePortfolioVault;
|
|
129
|
+
readonly scanVault?: typeof scanVault;
|
|
130
|
+
readonly validateMarkdownAttachments?: typeof validateMarkdownAttachments;
|
|
131
|
+
readonly indexGitHistory?: typeof indexGitHistory;
|
|
132
|
+
readonly git?: GitHistoryDependencies;
|
|
133
|
+
readonly sha256?: (content: string) => string;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
type DuplicateCandidate = PortfolioAuditReference & {
|
|
137
|
+
readonly hash: string;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
type ExternalRelationCandidate = {
|
|
141
|
+
readonly vault: PortfolioAuditVault;
|
|
142
|
+
readonly path: string;
|
|
143
|
+
readonly line: number;
|
|
144
|
+
readonly predicate: string;
|
|
145
|
+
readonly target: string;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
function checkedLimit(
|
|
149
|
+
value: number | undefined,
|
|
150
|
+
fallback: number,
|
|
151
|
+
maximum: number,
|
|
152
|
+
label: string,
|
|
153
|
+
): number {
|
|
154
|
+
const checked = value ?? fallback;
|
|
155
|
+
if (!Number.isSafeInteger(checked) || checked < 1 || checked > maximum) {
|
|
156
|
+
throw new RangeError(`${label} must be an integer from 1 through ${maximum}.`);
|
|
157
|
+
}
|
|
158
|
+
return checked;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function errorMessage(error: unknown): string {
|
|
162
|
+
return error instanceof Error ? error.message : String(error);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function descriptor(entry: PortfolioVaultEntry): PortfolioAuditVault {
|
|
166
|
+
return Object.freeze({
|
|
167
|
+
owner: entry.owner,
|
|
168
|
+
id: entry.id,
|
|
169
|
+
key: entry.key,
|
|
170
|
+
role: entry.role,
|
|
171
|
+
visibility: entry.visibility,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function defaultSha256(content: string): string {
|
|
176
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function attachmentMessage(issue: AttachmentIssue): string {
|
|
180
|
+
return `${issue.kind}: ${issue.message}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function activeRole(role: PortfolioVaultEntry["role"]): boolean {
|
|
184
|
+
return role === "portfolio" || role === "repository";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function protectedDuplicate(
|
|
188
|
+
references: readonly PortfolioAuditReference[],
|
|
189
|
+
groups: readonly PortfolioAuthorityGroup[],
|
|
190
|
+
): boolean {
|
|
191
|
+
const keys = new Set(references.map(({ vault }) => vault.key));
|
|
192
|
+
return groups.some((group) =>
|
|
193
|
+
group.protected === true
|
|
194
|
+
&& keys.size > 1
|
|
195
|
+
&& [...keys].every((key) => group.members.includes(key)));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Audit selected authored vaults without repairing, deduplicating, or choosing an authority. */
|
|
199
|
+
export async function auditKnowledgePortfolio(
|
|
200
|
+
options: AuditKnowledgePortfolioOptions,
|
|
201
|
+
dependencies: PortfolioAuditDependencies = {},
|
|
202
|
+
): Promise<PortfolioAuditReport> {
|
|
203
|
+
const maximumIssues = checkedLimit(
|
|
204
|
+
options.maxIssues,
|
|
205
|
+
DEFAULT_PORTFOLIO_AUDIT_ISSUES,
|
|
206
|
+
MAX_PORTFOLIO_AUDIT_ISSUES,
|
|
207
|
+
"Portfolio audit issue limit",
|
|
208
|
+
);
|
|
209
|
+
const maximumAttachments = checkedLimit(
|
|
210
|
+
options.maxAttachmentReferences,
|
|
211
|
+
MAX_ATTACHMENT_REFERENCES,
|
|
212
|
+
MAX_ATTACHMENT_REFERENCES,
|
|
213
|
+
"Portfolio attachment reference limit",
|
|
214
|
+
);
|
|
215
|
+
const registry = snapshotPortfolioRegistry(options.registry ?? await (
|
|
216
|
+
dependencies.loadPortfolioRegistry ?? loadPortfolioRegistry
|
|
217
|
+
)(options.registryPath, dependencies));
|
|
218
|
+
const selected = selectAuthorizedVaults(registry, options.authorizedVaults);
|
|
219
|
+
const selectedKeys = new Set(selected.map(({ key }) => key));
|
|
220
|
+
const selectedAuthority = registry.authorityGroups.filter(({ members }) =>
|
|
221
|
+
members.every((member) => selectedKeys.has(member)));
|
|
222
|
+
const issues: PortfolioAuditIssue[] = [];
|
|
223
|
+
const counts: Record<PortfolioAuditSeverity, number> = {
|
|
224
|
+
advisory: 0,
|
|
225
|
+
error: 0,
|
|
226
|
+
warning: 0,
|
|
227
|
+
};
|
|
228
|
+
const severityRank: Readonly<Record<PortfolioAuditSeverity, number>> = {
|
|
229
|
+
advisory: 0,
|
|
230
|
+
warning: 1,
|
|
231
|
+
error: 2,
|
|
232
|
+
};
|
|
233
|
+
let truncated = false;
|
|
234
|
+
const addIssue = (issue: PortfolioAuditIssue): void => {
|
|
235
|
+
counts[issue.severity] += 1;
|
|
236
|
+
if (issues.length >= maximumIssues) {
|
|
237
|
+
truncated = true;
|
|
238
|
+
let weakestIndex = 0;
|
|
239
|
+
for (let index = 1; index < issues.length; index += 1) {
|
|
240
|
+
const candidate = issues[index];
|
|
241
|
+
const weakest = issues[weakestIndex];
|
|
242
|
+
if (
|
|
243
|
+
candidate !== undefined
|
|
244
|
+
&& weakest !== undefined
|
|
245
|
+
&& severityRank[candidate.severity] < severityRank[weakest.severity]
|
|
246
|
+
) {
|
|
247
|
+
weakestIndex = index;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const weakest = issues[weakestIndex];
|
|
251
|
+
if (weakest !== undefined && severityRank[issue.severity] > severityRank[weakest.severity]) {
|
|
252
|
+
issues[weakestIndex] = Object.freeze({
|
|
253
|
+
...issue,
|
|
254
|
+
...(issue.related === undefined ? {} : { related: Object.freeze([...issue.related]) }),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
issues.push(Object.freeze({
|
|
260
|
+
...issue,
|
|
261
|
+
...(issue.related === undefined ? {} : { related: Object.freeze([...issue.related]) }),
|
|
262
|
+
}));
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
for (const group of selectedAuthority) {
|
|
266
|
+
if (group.state !== "unresolved") continue;
|
|
267
|
+
addIssue({
|
|
268
|
+
code: "authority-unresolved",
|
|
269
|
+
severity: "warning",
|
|
270
|
+
message: `Authority group ${JSON.stringify(group.id)} remains unresolved across explicitly selected members.`,
|
|
271
|
+
...(group.protected === undefined ? {} : { protected: group.protected }),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const resolved: ResolvedPortfolioVault[] = [];
|
|
276
|
+
const summaries: PortfolioVaultAuditSummary[] = [];
|
|
277
|
+
for (const entry of selected) {
|
|
278
|
+
try {
|
|
279
|
+
resolved.push(await (dependencies.resolvePortfolioVault ?? resolvePortfolioVault)(
|
|
280
|
+
entry,
|
|
281
|
+
options.workspaceRoot,
|
|
282
|
+
dependencies,
|
|
283
|
+
));
|
|
284
|
+
} catch (error) {
|
|
285
|
+
const vault = descriptor(entry);
|
|
286
|
+
addIssue({
|
|
287
|
+
code: "vault-unavailable",
|
|
288
|
+
severity: "error",
|
|
289
|
+
vault,
|
|
290
|
+
message: errorMessage(error),
|
|
291
|
+
});
|
|
292
|
+
summaries.push(Object.freeze({
|
|
293
|
+
vault,
|
|
294
|
+
status: "unavailable",
|
|
295
|
+
notes: 0,
|
|
296
|
+
stableDocuments: 0,
|
|
297
|
+
legacyDocuments: 0,
|
|
298
|
+
index: null,
|
|
299
|
+
head: null,
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
let auditable: readonly ResolvedPortfolioVault[] = resolved;
|
|
304
|
+
try {
|
|
305
|
+
auditable = validateResolvedPortfolioVaults(resolved);
|
|
306
|
+
} catch (error) {
|
|
307
|
+
addIssue({
|
|
308
|
+
code: "root-overlap",
|
|
309
|
+
severity: "error",
|
|
310
|
+
message: errorMessage(error),
|
|
311
|
+
});
|
|
312
|
+
for (const resolvedVault of resolved) {
|
|
313
|
+
summaries.push(Object.freeze({
|
|
314
|
+
vault: descriptor(resolvedVault.entry),
|
|
315
|
+
status: "unavailable",
|
|
316
|
+
notes: 0,
|
|
317
|
+
stableDocuments: 0,
|
|
318
|
+
legacyDocuments: 0,
|
|
319
|
+
index: null,
|
|
320
|
+
head: null,
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
auditable = [];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const duplicateCandidates: DuplicateCandidate[] = [];
|
|
327
|
+
const availableStableUris = new Set<string>();
|
|
328
|
+
const externalRelationCandidates: ExternalRelationCandidate[] = [];
|
|
329
|
+
for (const resolvedVault of auditable) {
|
|
330
|
+
const vault = descriptor(resolvedVault.entry);
|
|
331
|
+
let snapshot: VaultSnapshot;
|
|
332
|
+
try {
|
|
333
|
+
snapshot = await (dependencies.scanVault ?? scanVault)(resolvedVault.root, {
|
|
334
|
+
...(options.scan ?? {}),
|
|
335
|
+
mentionScope: false,
|
|
336
|
+
});
|
|
337
|
+
} catch (error) {
|
|
338
|
+
addIssue({
|
|
339
|
+
code: "scan-unavailable",
|
|
340
|
+
severity: "error",
|
|
341
|
+
vault,
|
|
342
|
+
message: errorMessage(error),
|
|
343
|
+
});
|
|
344
|
+
summaries.push(Object.freeze({
|
|
345
|
+
vault,
|
|
346
|
+
status: "unavailable",
|
|
347
|
+
notes: 0,
|
|
348
|
+
stableDocuments: 0,
|
|
349
|
+
legacyDocuments: 0,
|
|
350
|
+
index: null,
|
|
351
|
+
head: null,
|
|
352
|
+
}));
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (snapshot.index === "stale") {
|
|
356
|
+
addIssue({
|
|
357
|
+
code: "catalog-stale",
|
|
358
|
+
severity: "warning",
|
|
359
|
+
vault,
|
|
360
|
+
message: "The managed vault catalog is stale.",
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
let stableDocuments = 0;
|
|
365
|
+
let legacyDocuments = 0;
|
|
366
|
+
const pathsByDocumentId = new Map<string, string[]>();
|
|
367
|
+
const hash = dependencies.sha256 ?? defaultSha256;
|
|
368
|
+
for (const note of snapshot.notes) {
|
|
369
|
+
const state = documentIdState(note.metadata);
|
|
370
|
+
const identity = portfolioDocumentIdentity(vault, note.path, note.metadata);
|
|
371
|
+
if (state.kind === "valid") {
|
|
372
|
+
stableDocuments += 1;
|
|
373
|
+
if (identity.kind === "stable") availableStableUris.add(identity.uri);
|
|
374
|
+
const paths = pathsByDocumentId.get(state.documentId) ?? [];
|
|
375
|
+
paths.push(note.path);
|
|
376
|
+
pathsByDocumentId.set(state.documentId, paths);
|
|
377
|
+
} else {
|
|
378
|
+
legacyDocuments += 1;
|
|
379
|
+
addIssue(state.kind === "missing"
|
|
380
|
+
? {
|
|
381
|
+
code: "missing-document-id",
|
|
382
|
+
severity: activeRole(resolvedVault.entry.role) ? "warning" : "advisory",
|
|
383
|
+
vault,
|
|
384
|
+
path: note.path,
|
|
385
|
+
message: "Authored note has no stable document_id and is available only by legacy path.",
|
|
386
|
+
}
|
|
387
|
+
: {
|
|
388
|
+
code: "invalid-document-id",
|
|
389
|
+
severity: "error",
|
|
390
|
+
vault,
|
|
391
|
+
path: note.path,
|
|
392
|
+
message: "Authored note has an invalid document_id and is available only by legacy path.",
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
duplicateCandidates.push(Object.freeze({
|
|
396
|
+
vault,
|
|
397
|
+
path: note.path,
|
|
398
|
+
identity,
|
|
399
|
+
hash: hash(note.content),
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
for (const [documentId, paths] of pathsByDocumentId) {
|
|
403
|
+
if (paths.length < 2) continue;
|
|
404
|
+
addIssue({
|
|
405
|
+
code: "duplicate-document-id",
|
|
406
|
+
severity: "error",
|
|
407
|
+
vault,
|
|
408
|
+
message: `document_id ${JSON.stringify(documentId)} is authored by multiple notes in this vault.`,
|
|
409
|
+
related: paths.toSorted().map((path) => ({
|
|
410
|
+
vault,
|
|
411
|
+
path,
|
|
412
|
+
identity: portfolioDocumentIdentity(
|
|
413
|
+
vault,
|
|
414
|
+
path,
|
|
415
|
+
{ document_id: documentId },
|
|
416
|
+
),
|
|
417
|
+
})),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
for (const issue of snapshot.analysis.issues) {
|
|
421
|
+
addIssue({
|
|
422
|
+
code: issue.kind === "broken" ? "broken-link" : "ambiguous-link",
|
|
423
|
+
severity: issue.kind === "broken" ? "error" : "warning",
|
|
424
|
+
vault,
|
|
425
|
+
path: issue.source,
|
|
426
|
+
line: issue.line,
|
|
427
|
+
message: issue.kind === "broken"
|
|
428
|
+
? `Broken link target ${JSON.stringify(issue.target)}.`
|
|
429
|
+
: `Ambiguous link target ${JSON.stringify(issue.target)}.`,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
for (const issue of snapshot.analysis.relationIssues) {
|
|
433
|
+
addIssue({
|
|
434
|
+
code: "relation",
|
|
435
|
+
severity: "error",
|
|
436
|
+
vault,
|
|
437
|
+
path: issue.source,
|
|
438
|
+
line: issue.line,
|
|
439
|
+
message: issue.kind === "malformed"
|
|
440
|
+
? issue.message
|
|
441
|
+
: `${issue.kind} relation target ${JSON.stringify(issue.target)} for ${JSON.stringify(issue.predicate)}.`,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
for (const relation of snapshot.analysis.externalAuthoredRelations) {
|
|
445
|
+
externalRelationCandidates.push(Object.freeze({
|
|
446
|
+
vault,
|
|
447
|
+
path: relation.provenance.source,
|
|
448
|
+
line: relation.provenance.line,
|
|
449
|
+
predicate: relation.predicate,
|
|
450
|
+
target: relation.target,
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
const attachmentReport = await (
|
|
455
|
+
dependencies.validateMarkdownAttachments ?? validateMarkdownAttachments
|
|
456
|
+
)({
|
|
457
|
+
root: snapshot.root,
|
|
458
|
+
documents: snapshot.notes.map(({ path, content }) => ({ path, content })),
|
|
459
|
+
maxReferences: maximumAttachments,
|
|
460
|
+
});
|
|
461
|
+
for (const issue of attachmentReport.issues) {
|
|
462
|
+
addIssue({
|
|
463
|
+
code: "attachment",
|
|
464
|
+
severity: "error",
|
|
465
|
+
vault,
|
|
466
|
+
path: issue.source,
|
|
467
|
+
line: issue.line,
|
|
468
|
+
message: attachmentMessage(issue),
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
if (attachmentReport.truncated) truncated = true;
|
|
472
|
+
} catch (error) {
|
|
473
|
+
addIssue({
|
|
474
|
+
code: "attachment",
|
|
475
|
+
severity: "error",
|
|
476
|
+
vault,
|
|
477
|
+
message: `Attachment audit failed: ${errorMessage(error)}`,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
let head: string | null = null;
|
|
482
|
+
try {
|
|
483
|
+
const indexed = await (dependencies.indexGitHistory ?? indexGitHistory)(
|
|
484
|
+
{
|
|
485
|
+
repository: resolvedVault.repositoryRoot,
|
|
486
|
+
root: resolvedVault.root,
|
|
487
|
+
notes: [],
|
|
488
|
+
maxCommits: 1,
|
|
489
|
+
},
|
|
490
|
+
dependencies.git,
|
|
491
|
+
);
|
|
492
|
+
if (indexed.status === "ready") head = indexed.head;
|
|
493
|
+
else {
|
|
494
|
+
addIssue({
|
|
495
|
+
code: "git-unavailable",
|
|
496
|
+
severity: "warning",
|
|
497
|
+
vault,
|
|
498
|
+
message: indexed.reason,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
} catch (error) {
|
|
502
|
+
addIssue({
|
|
503
|
+
code: "git-unavailable",
|
|
504
|
+
severity: "warning",
|
|
505
|
+
vault,
|
|
506
|
+
message: errorMessage(error),
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
summaries.push(Object.freeze({
|
|
510
|
+
vault,
|
|
511
|
+
status: "audited",
|
|
512
|
+
notes: snapshot.notes.length,
|
|
513
|
+
stableDocuments,
|
|
514
|
+
legacyDocuments,
|
|
515
|
+
index: snapshot.index,
|
|
516
|
+
head,
|
|
517
|
+
}));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
for (const relation of externalRelationCandidates) {
|
|
521
|
+
if (availableStableUris.has(relation.target)) continue;
|
|
522
|
+
addIssue({
|
|
523
|
+
code: "external-relation-unavailable",
|
|
524
|
+
severity: "error",
|
|
525
|
+
vault: relation.vault,
|
|
526
|
+
path: relation.path,
|
|
527
|
+
line: relation.line,
|
|
528
|
+
message: `Cross-vault relation target ${JSON.stringify(relation.target)} for ${JSON.stringify(relation.predicate)} is not available among the explicitly selected, successfully audited vaults.`,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const candidatesByHash = new Map<string, PortfolioAuditReference[]>();
|
|
533
|
+
for (const candidate of duplicateCandidates) {
|
|
534
|
+
const group = candidatesByHash.get(candidate.hash) ?? [];
|
|
535
|
+
group.push({ vault: candidate.vault, path: candidate.path, identity: candidate.identity });
|
|
536
|
+
candidatesByHash.set(candidate.hash, group);
|
|
537
|
+
}
|
|
538
|
+
for (const [hash, references] of candidatesByHash) {
|
|
539
|
+
if (references.length < 2) continue;
|
|
540
|
+
const related = references.toSorted((left, right) =>
|
|
541
|
+
left.vault.key.localeCompare(right.vault.key) || left.path.localeCompare(right.path));
|
|
542
|
+
const isProtected = protectedDuplicate(related, selectedAuthority);
|
|
543
|
+
addIssue({
|
|
544
|
+
code: "duplicate-content",
|
|
545
|
+
severity: related.some(({ vault }) => activeRole(vault.role)) ? "warning" : "advisory",
|
|
546
|
+
message: `Exact authored content SHA-256 ${hash} occurs in multiple selected notes; no authority was inferred.`,
|
|
547
|
+
...(isProtected ? { protected: true } : {}),
|
|
548
|
+
related,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const orderedSummaries = summaries.toSorted((left, right) =>
|
|
553
|
+
left.vault.key.localeCompare(right.vault.key));
|
|
554
|
+
const audited = orderedSummaries.filter(({ status }) => status === "audited");
|
|
555
|
+
const unavailable = orderedSummaries.filter(({ status }) => status === "unavailable");
|
|
556
|
+
return Object.freeze({
|
|
557
|
+
partial: unavailable.length > 0 || truncated,
|
|
558
|
+
truncated,
|
|
559
|
+
selectedVaults: selected.length,
|
|
560
|
+
auditedVaults: audited.length,
|
|
561
|
+
unavailableVaults: unavailable.length,
|
|
562
|
+
notes: audited.reduce((count, summary) => count + summary.notes, 0),
|
|
563
|
+
stableDocuments: audited.reduce((count, summary) => count + summary.stableDocuments, 0),
|
|
564
|
+
legacyDocuments: audited.reduce((count, summary) => count + summary.legacyDocuments, 0),
|
|
565
|
+
counts: Object.freeze(counts),
|
|
566
|
+
vaults: Object.freeze(orderedSummaries),
|
|
567
|
+
authority: Object.freeze(selectedAuthority.map((group) => Object.freeze({
|
|
568
|
+
id: group.id,
|
|
569
|
+
state: group.state,
|
|
570
|
+
protected: group.protected ?? false,
|
|
571
|
+
}))),
|
|
572
|
+
issues: Object.freeze(issues),
|
|
573
|
+
});
|
|
574
|
+
}
|