@cleocode/core 2026.6.7 → 2026.6.8
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/dist/db/index.d.ts +5 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +5 -1
- package/dist/db/index.js.map +1 -1
- package/dist/docs/build-provenance-graph.d.ts +12 -0
- package/dist/docs/build-provenance-graph.d.ts.map +1 -1
- package/dist/docs/build-provenance-graph.js +52 -0
- package/dist/docs/build-provenance-graph.js.map +1 -1
- package/dist/docs/docs-read-model.d.ts +40 -0
- package/dist/docs/docs-read-model.d.ts.map +1 -1
- package/dist/docs/docs-read-model.js +29 -0
- package/dist/docs/docs-read-model.js.map +1 -1
- package/dist/docs/export-document.js +897 -730
- package/dist/docs/export-document.js.map +3 -3
- package/dist/docs/index.d.ts +4 -0
- package/dist/docs/index.d.ts.map +1 -1
- package/dist/docs/index.js +2 -0
- package/dist/docs/index.js.map +1 -1
- package/dist/docs/read-doc.d.ts +60 -0
- package/dist/docs/read-doc.d.ts.map +1 -0
- package/dist/docs/read-doc.js +188 -0
- package/dist/docs/read-doc.js.map +1 -0
- package/dist/docs/wikilinks.d.ts +119 -0
- package/dist/docs/wikilinks.d.ts.map +1 -0
- package/dist/docs/wikilinks.js +217 -0
- package/dist/docs/wikilinks.js.map +1 -0
- package/dist/llm/plugin-facade.js +941 -776
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/store/dual-scope-db.d.ts +83 -0
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +135 -6
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/abort-events.d.ts +116 -0
- package/dist/store/exodus/abort-events.d.ts.map +1 -0
- package/dist/store/exodus/abort-events.js +130 -0
- package/dist/store/exodus/abort-events.js.map +1 -0
- package/dist/store/exodus/index.d.ts +1 -0
- package/dist/store/exodus/index.d.ts.map +1 -1
- package/dist/store/exodus/index.js +1 -0
- package/dist/store/exodus/index.js.map +1 -1
- package/dist/store/repair-malformed-dbs.d.ts +87 -0
- package/dist/store/repair-malformed-dbs.d.ts.map +1 -0
- package/dist/store/repair-malformed-dbs.js +188 -0
- package/dist/store/repair-malformed-dbs.js.map +1 -0
- package/dist/store/schema/attachments.d.ts +133 -0
- package/dist/store/schema/attachments.d.ts.map +1 -1
- package/dist/store/schema/attachments.js +63 -0
- package/dist/store/schema/attachments.js.map +1 -1
- package/migrations/drizzle-tasks/20260605000001_t11826-docs-wikilinks/migration.sql +110 -0
- package/package.json +12 -12
package/dist/docs/index.d.ts
CHANGED
|
@@ -52,6 +52,10 @@ export type { ListProjectDocsOpts, ResolvedDoc } from './docs-read-model.js';
|
|
|
52
52
|
export { createDocsReadModel, DocsReadModel } from './docs-read-model.js';
|
|
53
53
|
export type { ProvisionResult, PublishPrError, PublishPrOptions, PublishPrResult, PublishPrRunners, PublishPrSuccess, } from './publish-pr.js';
|
|
54
54
|
export { branchForSlug, buildPublishFrontmatter, defaultPublishPrBody, defaultRun, execMsg, KNOWN_DOC_TYPES, knownDocTypesForProject, parseGhPrUrl, pickRunner, provisionPublishPrWorktree, publishDirForType, publishDocsAsPr, publishPrError, stripExistingFrontmatter, teardownPublishPrWorktree, tempWorktreeDirForSlug, validatePublishSlug, } from './publish-pr.js';
|
|
55
|
+
export type { ReadDocOptions } from './read-doc.js';
|
|
56
|
+
export { DocNotFoundError, readDoc } from './read-doc.js';
|
|
55
57
|
export type { VersionAuditResult } from './version-ssot.js';
|
|
56
58
|
export { auditVersionFields, compareCleoVersions, getCanonicalCleoVersion, resolveVersion, VERSION_SSOT_MIGRATION_SQL, versionInRange, } from './version-ssot.js';
|
|
59
|
+
export type { RebuildDocsWikilinksOptions, RebuildDocsWikilinksResult, WikilinkEdge, } from './wikilinks.js';
|
|
60
|
+
export { deriveWikilinkEdges, getDocsWikilinks, rebuildDocsWikilinks } from './wikilinks.js';
|
|
57
61
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/docs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/docs/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/docs/index.ts"],"names":[],"mappings":"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"}
|
package/dist/docs/index.js
CHANGED
|
@@ -38,5 +38,7 @@ export { countAuditEntriesForSlug, DOCS_AUDIT_FILE, readAuditLog, verifyAuditTra
|
|
|
38
38
|
export { checkBlobFilesystem, checkDocsConsistency, } from './docs-inconsistency-detector.js';
|
|
39
39
|
export { createDocsReadModel, DocsReadModel } from './docs-read-model.js';
|
|
40
40
|
export { branchForSlug, buildPublishFrontmatter, defaultPublishPrBody, defaultRun, execMsg, KNOWN_DOC_TYPES, knownDocTypesForProject, parseGhPrUrl, pickRunner, provisionPublishPrWorktree, publishDirForType, publishDocsAsPr, publishPrError, stripExistingFrontmatter, teardownPublishPrWorktree, tempWorktreeDirForSlug, validatePublishSlug, } from './publish-pr.js';
|
|
41
|
+
export { DocNotFoundError, readDoc } from './read-doc.js';
|
|
41
42
|
export { auditVersionFields, compareCleoVersions, getCanonicalCleoVersion, resolveVersion, VERSION_SSOT_MIGRATION_SQL, versionInRange, } from './version-ssot.js';
|
|
43
|
+
export { deriveWikilinkEdges, getDocsWikilinks, rebuildDocsWikilinks } from './wikilinks.js';
|
|
42
44
|
//# sourceMappingURL=index.js.map
|
package/dist/docs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/docs/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AASH,OAAO,EACL,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,uBAAuB,EACvB,8BAA8B,EAC9B,0BAA0B,GAC3B,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAsB1D,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,8BAA8B,EAC9B,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,SAAS,EACT,WAAW,EACX,QAAQ,EACR,iBAAiB,EACjB,oBAAoB,EACpB,UAAU,EACV,UAAU,EACV,WAAW,GACZ,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAQtD,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAO/B,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,YAAY,GACb,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,+EAA+E;AAE/E,8EAA8E;AAC9E,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,iBAAiB,EACjB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,uCAAuC,CAAC;AAQ/C,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,GACd,MAAM,iCAAiC,CAAC;AAMzC,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,yBAAyB,EACzB,kBAAkB,EAClB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,YAAY,EACZ,cAAc,EACd,uBAAuB,EACvB,iBAAiB,EACjB,OAAO,EACP,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,+EAA+E;AAE/E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAMpD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AASjC,gFAAgF;AAChF,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAQzB,OAAO,EACL,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAS1E,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,oBAAoB,EACpB,UAAU,EACV,OAAO,EACP,eAAe,EACf,uBAAuB,EACvB,YAAY,EACZ,UAAU,EACV,0BAA0B,EAC1B,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,cAAc,EACd,0BAA0B,EAC1B,cAAc,GACf,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/docs/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AASH,OAAO,EACL,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,uBAAuB,EACvB,8BAA8B,EAC9B,0BAA0B,GAC3B,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAsB1D,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,8BAA8B,EAC9B,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,SAAS,EACT,WAAW,EACX,QAAQ,EACR,iBAAiB,EACjB,oBAAoB,EACpB,UAAU,EACV,UAAU,EACV,WAAW,GACZ,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAQtD,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAO/B,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,YAAY,GACb,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,+EAA+E;AAE/E,8EAA8E;AAC9E,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,iBAAiB,EACjB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,uCAAuC,CAAC;AAQ/C,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,GACd,MAAM,iCAAiC,CAAC;AAMzC,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,yBAAyB,EACzB,kBAAkB,EAClB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,YAAY,EACZ,cAAc,EACd,uBAAuB,EACvB,iBAAiB,EACjB,OAAO,EACP,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,+EAA+E;AAE/E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAMpD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AASjC,gFAAgF;AAChF,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAQzB,OAAO,EACL,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAS1E,OAAO,EACL,aAAa,EACb,uBAAuB,EACvB,oBAAoB,EACpB,UAAU,EACV,OAAO,EACP,eAAe,EACf,uBAAuB,EACvB,YAAY,EACZ,UAAU,EACV,0BAA0B,EAC1B,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG1D,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,cAAc,EACd,0BAA0B,EAC1B,cAAc,GACf,MAAM,mBAAmB,CAAC;AAO3B,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `docs.read` core-SDK implementation — render a single CLEO doc (body + full
|
|
3
|
+
* provenance frontmatter) directly from `cleo.db`, the SOLE doc authority
|
|
4
|
+
* (saga T11778).
|
|
5
|
+
*
|
|
6
|
+
* This is the live read API the Obsidian plugin (T11827) and `cleo docs view`
|
|
7
|
+
* call. It is NOT a static export: every call reads the current DB state. The
|
|
8
|
+
* body is surfaced as UTF-8 text when decodable, else base64 (T11825 AC2), so
|
|
9
|
+
* binary blobs (images/PDFs) render without a second round-trip.
|
|
10
|
+
*
|
|
11
|
+
* Frontmatter is read straight from the `attachments` provenance columns
|
|
12
|
+
* (`slug`, `doc_version`, `owner_version`, `supersedes`, `superseded_by`,
|
|
13
|
+
* `topics`, `related_tasks`) — the same columns `docs_wikilinks` derives from —
|
|
14
|
+
* so the response is self-describing for an external consumer.
|
|
15
|
+
*
|
|
16
|
+
* @task T11825 (Epic T11781 / Saga T11778)
|
|
17
|
+
* @adr ADR-078 — Docs SSoT as provenance graph
|
|
18
|
+
* @see DocReadResponse — packages/contracts/src/docs/read.ts
|
|
19
|
+
* @see createDocsReadModel — body fetch + base64 plumbing reuse
|
|
20
|
+
*/
|
|
21
|
+
import type { DocReadResponse } from '@cleocode/contracts';
|
|
22
|
+
/**
|
|
23
|
+
* Options for {@link readDoc}.
|
|
24
|
+
*
|
|
25
|
+
* @task T11825
|
|
26
|
+
*/
|
|
27
|
+
export interface ReadDocOptions {
|
|
28
|
+
/** Project root for DB resolution. Defaults to {@link getProjectRoot}(). */
|
|
29
|
+
readonly projectRoot?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Error raised when {@link readDoc}'s slug cannot be resolved to a doc.
|
|
33
|
+
*
|
|
34
|
+
* @task T11825
|
|
35
|
+
*/
|
|
36
|
+
export declare class DocNotFoundError extends Error {
|
|
37
|
+
constructor(slug: string);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read a single doc by slug, returning its body + full provenance frontmatter.
|
|
41
|
+
*
|
|
42
|
+
* Resolves the supersedes / superseded-by FK targets to their slugs (the
|
|
43
|
+
* external-consumer-friendly addressing) and decodes the body to UTF-8 when
|
|
44
|
+
* possible, falling back to base64 for binary content.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const doc = await readDoc('adr-078-docs-provenance');
|
|
49
|
+
* console.log(doc.frontmatter.docVersion, doc.frontmatter.topics);
|
|
50
|
+
* if (doc.body.encoding === 'utf-8') console.log(doc.body.text);
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @param slug - The exact doc slug (case-sensitive).
|
|
54
|
+
* @param opts - Optional project-root override.
|
|
55
|
+
* @returns The typed {@link DocReadResponse}.
|
|
56
|
+
* @throws {DocNotFoundError} when no attachment carries the slug.
|
|
57
|
+
* @task T11825
|
|
58
|
+
*/
|
|
59
|
+
export declare function readDoc(slug: string, opts?: ReadDocOptions): Promise<DocReadResponse>;
|
|
60
|
+
//# sourceMappingURL=read-doc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-doc.d.ts","sourceRoot":"","sources":["../../src/docs/read-doc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAA2B,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAOpF;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,IAAI,EAAE,MAAM;CAIzB;AAmBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,eAAe,CAAC,CAwD/F"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `docs.read` core-SDK implementation — render a single CLEO doc (body + full
|
|
3
|
+
* provenance frontmatter) directly from `cleo.db`, the SOLE doc authority
|
|
4
|
+
* (saga T11778).
|
|
5
|
+
*
|
|
6
|
+
* This is the live read API the Obsidian plugin (T11827) and `cleo docs view`
|
|
7
|
+
* call. It is NOT a static export: every call reads the current DB state. The
|
|
8
|
+
* body is surfaced as UTF-8 text when decodable, else base64 (T11825 AC2), so
|
|
9
|
+
* binary blobs (images/PDFs) render without a second round-trip.
|
|
10
|
+
*
|
|
11
|
+
* Frontmatter is read straight from the `attachments` provenance columns
|
|
12
|
+
* (`slug`, `doc_version`, `owner_version`, `supersedes`, `superseded_by`,
|
|
13
|
+
* `topics`, `related_tasks`) — the same columns `docs_wikilinks` derives from —
|
|
14
|
+
* so the response is self-describing for an external consumer.
|
|
15
|
+
*
|
|
16
|
+
* @task T11825 (Epic T11781 / Saga T11778)
|
|
17
|
+
* @adr ADR-078 — Docs SSoT as provenance graph
|
|
18
|
+
* @see DocReadResponse — packages/contracts/src/docs/read.ts
|
|
19
|
+
* @see createDocsReadModel — body fetch + base64 plumbing reuse
|
|
20
|
+
*/
|
|
21
|
+
import { Buffer } from 'node:buffer';
|
|
22
|
+
import { eq } from 'drizzle-orm';
|
|
23
|
+
import { getProjectRoot } from '../paths.js';
|
|
24
|
+
import { createAttachmentStore } from '../store/attachment-store.js';
|
|
25
|
+
import { getDb } from '../store/sqlite.js';
|
|
26
|
+
import { attachments } from '../store/tasks-schema.js';
|
|
27
|
+
/**
|
|
28
|
+
* Error raised when {@link readDoc}'s slug cannot be resolved to a doc.
|
|
29
|
+
*
|
|
30
|
+
* @task T11825
|
|
31
|
+
*/
|
|
32
|
+
export class DocNotFoundError extends Error {
|
|
33
|
+
constructor(slug) {
|
|
34
|
+
super(`Doc not found: no attachment carries slug '${slug}'`);
|
|
35
|
+
this.name = 'DocNotFoundError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Read a single doc by slug, returning its body + full provenance frontmatter.
|
|
40
|
+
*
|
|
41
|
+
* Resolves the supersedes / superseded-by FK targets to their slugs (the
|
|
42
|
+
* external-consumer-friendly addressing) and decodes the body to UTF-8 when
|
|
43
|
+
* possible, falling back to base64 for binary content.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const doc = await readDoc('adr-078-docs-provenance');
|
|
48
|
+
* console.log(doc.frontmatter.docVersion, doc.frontmatter.topics);
|
|
49
|
+
* if (doc.body.encoding === 'utf-8') console.log(doc.body.text);
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @param slug - The exact doc slug (case-sensitive).
|
|
53
|
+
* @param opts - Optional project-root override.
|
|
54
|
+
* @returns The typed {@link DocReadResponse}.
|
|
55
|
+
* @throws {DocNotFoundError} when no attachment carries the slug.
|
|
56
|
+
* @task T11825
|
|
57
|
+
*/
|
|
58
|
+
export async function readDoc(slug, opts = {}) {
|
|
59
|
+
const projectRoot = opts.projectRoot ?? getProjectRoot();
|
|
60
|
+
const db = await getDb(projectRoot);
|
|
61
|
+
const row = await db
|
|
62
|
+
.select({
|
|
63
|
+
id: attachments.id,
|
|
64
|
+
sha256: attachments.sha256,
|
|
65
|
+
slug: attachments.slug,
|
|
66
|
+
type: attachments.type,
|
|
67
|
+
summary: attachments.summary,
|
|
68
|
+
lifecycleStatus: attachments.lifecycleStatus,
|
|
69
|
+
supersedes: attachments.supersedes,
|
|
70
|
+
supersededBy: attachments.supersededBy,
|
|
71
|
+
topics: attachments.topics,
|
|
72
|
+
relatedTasks: attachments.relatedTasks,
|
|
73
|
+
ownerVersion: attachments.ownerVersion,
|
|
74
|
+
docVersion: attachments.docVersion,
|
|
75
|
+
createdAt: attachments.createdAt,
|
|
76
|
+
})
|
|
77
|
+
.from(attachments)
|
|
78
|
+
.where(eq(attachments.slug, slug))
|
|
79
|
+
.get();
|
|
80
|
+
if (!row)
|
|
81
|
+
throw new DocNotFoundError(slug);
|
|
82
|
+
const frontmatterRow = row;
|
|
83
|
+
// Resolve supersession FK ids → slugs for external addressing.
|
|
84
|
+
const [supersedesSlug, supersededBySlug] = await Promise.all([
|
|
85
|
+
resolveSlugById(db, frontmatterRow.supersedes),
|
|
86
|
+
resolveSlugById(db, frontmatterRow.supersededBy),
|
|
87
|
+
]);
|
|
88
|
+
// Fetch the RAW body bytes (not a decoded string) so binary blobs survive the
|
|
89
|
+
// base64 path with full fidelity — `TextDecoder` would lossily replace
|
|
90
|
+
// invalid sequences with U+FFFD.
|
|
91
|
+
const { bytes, mimeType } = await fetchRawBody(frontmatterRow.sha256, projectRoot);
|
|
92
|
+
const frontmatter = {
|
|
93
|
+
slug: frontmatterRow.slug ?? slug,
|
|
94
|
+
kind: frontmatterRow.type,
|
|
95
|
+
title: frontmatterRow.slug ?? slug,
|
|
96
|
+
summary: frontmatterRow.summary,
|
|
97
|
+
lifecycleStatus: frontmatterRow.lifecycleStatus,
|
|
98
|
+
docVersion: frontmatterRow.docVersion,
|
|
99
|
+
ownerVersion: frontmatterRow.ownerVersion,
|
|
100
|
+
supersedes: supersedesSlug,
|
|
101
|
+
supersededBy: supersededBySlug,
|
|
102
|
+
topics: parseStringArray(frontmatterRow.topics),
|
|
103
|
+
relatedTasks: parseStringArray(frontmatterRow.relatedTasks),
|
|
104
|
+
sha256: frontmatterRow.sha256,
|
|
105
|
+
createdAt: frontmatterRow.createdAt,
|
|
106
|
+
};
|
|
107
|
+
const body = encodeBody(bytes, mimeType);
|
|
108
|
+
return { frontmatter, body };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Read the raw body bytes for a doc by content hash via the content-addressed
|
|
112
|
+
* attachment store. Returns an empty buffer when the metadata row exists but
|
|
113
|
+
* the blob was purged from disk.
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
async function fetchRawBody(sha256, projectRoot) {
|
|
118
|
+
const store = createAttachmentStore();
|
|
119
|
+
try {
|
|
120
|
+
const result = await store.get(sha256, projectRoot);
|
|
121
|
+
if (result) {
|
|
122
|
+
const mime = result.metadata.attachment.mime;
|
|
123
|
+
return { bytes: result.bytes, mimeType: typeof mime === 'string' ? mime : null };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Attachment store unavailable or blob purged — fall through to empty body.
|
|
128
|
+
}
|
|
129
|
+
return { bytes: Buffer.alloc(0), mimeType: null };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Resolve an attachment id to its slug, or null when the id is absent /
|
|
133
|
+
* unresolved / slug-less.
|
|
134
|
+
*
|
|
135
|
+
* @internal
|
|
136
|
+
*/
|
|
137
|
+
async function resolveSlugById(db, id) {
|
|
138
|
+
if (!id)
|
|
139
|
+
return null;
|
|
140
|
+
const row = await db
|
|
141
|
+
.select({ slug: attachments.slug })
|
|
142
|
+
.from(attachments)
|
|
143
|
+
.where(eq(attachments.id, id))
|
|
144
|
+
.get();
|
|
145
|
+
return row?.slug ?? null;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Encode a doc body from its raw bytes. UTF-8-decodable content (the common
|
|
149
|
+
* markdown case) is surfaced as `text`; binary content (images/PDFs/files) is
|
|
150
|
+
* surfaced as `base64` so an external consumer can render it (T11825 AC2).
|
|
151
|
+
*
|
|
152
|
+
* UTF-8 validity is detected by a lossless decode→re-encode round-trip: invalid
|
|
153
|
+
* sequences decode to U+FFFD and fail to re-encode to the original bytes.
|
|
154
|
+
*
|
|
155
|
+
* @internal
|
|
156
|
+
*/
|
|
157
|
+
function encodeBody(bytes, mimeType) {
|
|
158
|
+
const text = bytes.toString('utf-8');
|
|
159
|
+
const isUtf8 = Buffer.from(text, 'utf-8').equals(bytes);
|
|
160
|
+
if (isUtf8) {
|
|
161
|
+
return { encoding: 'utf-8', text, sizeBytes: bytes.length, mimeType };
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
encoding: 'base64',
|
|
165
|
+
base64: bytes.toString('base64'),
|
|
166
|
+
sizeBytes: bytes.length,
|
|
167
|
+
mimeType,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Parse a JSON-array-of-strings column, tolerating null / malformed values.
|
|
172
|
+
*
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
function parseStringArray(raw) {
|
|
176
|
+
if (!raw)
|
|
177
|
+
return [];
|
|
178
|
+
try {
|
|
179
|
+
const parsed = JSON.parse(raw);
|
|
180
|
+
if (!Array.isArray(parsed))
|
|
181
|
+
return [];
|
|
182
|
+
return parsed.filter((v) => typeof v === 'string' && v.length > 0);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=read-doc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-doc.js","sourceRoot":"","sources":["../../src/docs/read-doc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `docs_wikilinks` — derive + query the slug-addressed docs edge table.
|
|
3
|
+
*
|
|
4
|
+
* Per the ratified Docs-SSoT model (saga T11778) `cleo.db` is the SOLE doc
|
|
5
|
+
* authority and `docs_wikilinks` is a **DERIVED, non-authoritative** edge table
|
|
6
|
+
* reconstructed from three provenance columns already on `attachments`:
|
|
7
|
+
*
|
|
8
|
+
* - `supersedes` / `superseded_by` → doc→doc supersession edges
|
|
9
|
+
* - `related_tasks` → doc→T#### task edges (JSON array)
|
|
10
|
+
* - `topics` → doc↔doc shared-topic edges (JSON array)
|
|
11
|
+
*
|
|
12
|
+
* No markdown body `[[link]]` parsing is performed (T11826 AC4) — the edges
|
|
13
|
+
* derive purely from structured columns. The derivation is **idempotent**: it
|
|
14
|
+
* truncates and rebuilds the whole table, so callers may re-run it after any
|
|
15
|
+
* `docs add` / `supersede` / `docs update` write to keep the graph fresh.
|
|
16
|
+
*
|
|
17
|
+
* This module is the runtime twin of the SQL backfill in
|
|
18
|
+
* `migrations/drizzle-tasks/20260605000001_t11826-docs-wikilinks/migration.sql`
|
|
19
|
+
* — the migration seeds the table on schema upgrade, this function rebuilds it
|
|
20
|
+
* on demand.
|
|
21
|
+
*
|
|
22
|
+
* @task T11826 (Epic T11781 / Saga T11778)
|
|
23
|
+
* @adr ADR-078 — Docs SSoT as provenance graph
|
|
24
|
+
* @see build-provenance-graph.ts — the on-the-fly BFS this table makes O(edges)
|
|
25
|
+
*/
|
|
26
|
+
import { type DocsWikilinkRelation } from '../store/schema/attachments.js';
|
|
27
|
+
/**
|
|
28
|
+
* A single slug-addressed wikilink edge.
|
|
29
|
+
*
|
|
30
|
+
* @task T11826
|
|
31
|
+
*/
|
|
32
|
+
export interface WikilinkEdge {
|
|
33
|
+
/** Source doc slug (always a doc). */
|
|
34
|
+
readonly fromSlug: string;
|
|
35
|
+
/** Target slug — a doc slug, or a `T####` task id when {@link toIsTask}. */
|
|
36
|
+
readonly toSlug: string;
|
|
37
|
+
/** Which provenance column produced this edge. */
|
|
38
|
+
readonly relation: DocsWikilinkRelation;
|
|
39
|
+
/** True when {@link toSlug} is a task id (`related-task` edges). */
|
|
40
|
+
readonly toIsTask: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Options shared by {@link rebuildDocsWikilinks}.
|
|
44
|
+
*
|
|
45
|
+
* @task T11826
|
|
46
|
+
*/
|
|
47
|
+
export interface RebuildDocsWikilinksOptions {
|
|
48
|
+
/** Project root for DB resolution. Defaults to {@link getProjectRoot}(). */
|
|
49
|
+
readonly projectRoot?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Outcome of a {@link rebuildDocsWikilinks} call.
|
|
53
|
+
*
|
|
54
|
+
* @task T11826
|
|
55
|
+
*/
|
|
56
|
+
export interface RebuildDocsWikilinksResult {
|
|
57
|
+
/** Number of edges in the table after the rebuild. */
|
|
58
|
+
readonly edgeCount: number;
|
|
59
|
+
/** Per-relation edge counts. */
|
|
60
|
+
readonly byRelation: Readonly<Record<DocsWikilinkRelation, number>>;
|
|
61
|
+
}
|
|
62
|
+
/** Narrow row shape read from `attachments` during derivation. */
|
|
63
|
+
interface DerivationRow {
|
|
64
|
+
readonly slug: string;
|
|
65
|
+
readonly supersedesSlug: string | null;
|
|
66
|
+
readonly supersededBySlug: string | null;
|
|
67
|
+
readonly relatedTasks: string | null;
|
|
68
|
+
readonly topics: string | null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Idempotently rebuild the `docs_wikilinks` edge table from the provenance
|
|
72
|
+
* columns on `attachments`.
|
|
73
|
+
*
|
|
74
|
+
* The whole table is truncated and re-derived inside a single transaction, so
|
|
75
|
+
* the function is safe to call after any doc write and always converges to the
|
|
76
|
+
* same edge set for a given `attachments` state.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* const { edgeCount, byRelation } = await rebuildDocsWikilinks();
|
|
81
|
+
* console.log(`derived ${edgeCount} edges (${byRelation['topic']} topic links)`);
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @param opts - Optional project-root override.
|
|
85
|
+
* @returns Edge totals after the rebuild.
|
|
86
|
+
* @task T11826
|
|
87
|
+
*/
|
|
88
|
+
export declare function rebuildDocsWikilinks(opts?: RebuildDocsWikilinksOptions): Promise<RebuildDocsWikilinksResult>;
|
|
89
|
+
/**
|
|
90
|
+
* Pure derivation: turn slugged `attachments` rows into the full edge set.
|
|
91
|
+
*
|
|
92
|
+
* Exposed for unit testing the derivation rules without a database. The output
|
|
93
|
+
* is deduplicated on the (`fromSlug`, `toSlug`, `relation`) composite key.
|
|
94
|
+
*
|
|
95
|
+
* @param rows - Slugged attachment rows with their provenance columns.
|
|
96
|
+
* @returns The derived wikilink edges.
|
|
97
|
+
* @task T11826
|
|
98
|
+
*/
|
|
99
|
+
export declare function deriveWikilinkEdges(rows: readonly DerivationRow[]): WikilinkEdge[];
|
|
100
|
+
/**
|
|
101
|
+
* Read all wikilink edges incident to a doc slug — **bidirectional** by default.
|
|
102
|
+
*
|
|
103
|
+
* Returns both outbound edges (`from_slug = slug`) and inbound backlinks
|
|
104
|
+
* (`to_slug = slug`). This is the query the Obsidian plugin (T11827) renders to
|
|
105
|
+
* show a doc's neighborhood, and what `cleo docs graph` hydrates for persisted
|
|
106
|
+
* backlinks.
|
|
107
|
+
*
|
|
108
|
+
* @param slug - The doc slug to fetch edges for.
|
|
109
|
+
* @param opts.direction - `'both'` (default), `'out'`, or `'in'`.
|
|
110
|
+
* @param opts.projectRoot - Project root override.
|
|
111
|
+
* @returns The incident edges.
|
|
112
|
+
* @task T11826
|
|
113
|
+
*/
|
|
114
|
+
export declare function getDocsWikilinks(slug: string, opts?: {
|
|
115
|
+
direction?: 'both' | 'out' | 'in';
|
|
116
|
+
projectRoot?: string;
|
|
117
|
+
}): Promise<WikilinkEdge[]>;
|
|
118
|
+
export {};
|
|
119
|
+
//# sourceMappingURL=wikilinks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wikilinks.d.ts","sourceRoot":"","sources":["../../src/docs/wikilinks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,EAAE,KAAK,oBAAoB,EAAiB,MAAM,gCAAgC,CAAC;AAI1F;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACzC,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,CAAC;CACrE;AAED,kEAAkE;AAClE,UAAU,aAAa;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,GAAE,2BAAgC,GACrC,OAAO,CAAC,0BAA0B,CAAC,CAoErC;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,GAAG,YAAY,EAAE,CAoDlF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACrE,OAAO,CAAC,YAAY,EAAE,CAAC,CAmBzB"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `docs_wikilinks` — derive + query the slug-addressed docs edge table.
|
|
3
|
+
*
|
|
4
|
+
* Per the ratified Docs-SSoT model (saga T11778) `cleo.db` is the SOLE doc
|
|
5
|
+
* authority and `docs_wikilinks` is a **DERIVED, non-authoritative** edge table
|
|
6
|
+
* reconstructed from three provenance columns already on `attachments`:
|
|
7
|
+
*
|
|
8
|
+
* - `supersedes` / `superseded_by` → doc→doc supersession edges
|
|
9
|
+
* - `related_tasks` → doc→T#### task edges (JSON array)
|
|
10
|
+
* - `topics` → doc↔doc shared-topic edges (JSON array)
|
|
11
|
+
*
|
|
12
|
+
* No markdown body `[[link]]` parsing is performed (T11826 AC4) — the edges
|
|
13
|
+
* derive purely from structured columns. The derivation is **idempotent**: it
|
|
14
|
+
* truncates and rebuilds the whole table, so callers may re-run it after any
|
|
15
|
+
* `docs add` / `supersede` / `docs update` write to keep the graph fresh.
|
|
16
|
+
*
|
|
17
|
+
* This module is the runtime twin of the SQL backfill in
|
|
18
|
+
* `migrations/drizzle-tasks/20260605000001_t11826-docs-wikilinks/migration.sql`
|
|
19
|
+
* — the migration seeds the table on schema upgrade, this function rebuilds it
|
|
20
|
+
* on demand.
|
|
21
|
+
*
|
|
22
|
+
* @task T11826 (Epic T11781 / Saga T11778)
|
|
23
|
+
* @adr ADR-078 — Docs SSoT as provenance graph
|
|
24
|
+
* @see build-provenance-graph.ts — the on-the-fly BFS this table makes O(edges)
|
|
25
|
+
*/
|
|
26
|
+
import { eq, or } from 'drizzle-orm';
|
|
27
|
+
import { getProjectRoot } from '../paths.js';
|
|
28
|
+
import { docsWikilinks } from '../store/schema/attachments.js';
|
|
29
|
+
import { getDb } from '../store/sqlite.js';
|
|
30
|
+
import { attachments } from '../store/tasks-schema.js';
|
|
31
|
+
/**
|
|
32
|
+
* Idempotently rebuild the `docs_wikilinks` edge table from the provenance
|
|
33
|
+
* columns on `attachments`.
|
|
34
|
+
*
|
|
35
|
+
* The whole table is truncated and re-derived inside a single transaction, so
|
|
36
|
+
* the function is safe to call after any doc write and always converges to the
|
|
37
|
+
* same edge set for a given `attachments` state.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const { edgeCount, byRelation } = await rebuildDocsWikilinks();
|
|
42
|
+
* console.log(`derived ${edgeCount} edges (${byRelation['topic']} topic links)`);
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @param opts - Optional project-root override.
|
|
46
|
+
* @returns Edge totals after the rebuild.
|
|
47
|
+
* @task T11826
|
|
48
|
+
*/
|
|
49
|
+
export async function rebuildDocsWikilinks(opts = {}) {
|
|
50
|
+
const projectRoot = opts.projectRoot ?? getProjectRoot();
|
|
51
|
+
const db = await getDb(projectRoot);
|
|
52
|
+
// Read every attachment's id, slug and provenance columns in one pass, then
|
|
53
|
+
// resolve the supersession FK ids → slugs in JS via an id→slug map. (A
|
|
54
|
+
// correlated SQL subquery aliasing the same table is fragile across SQLite
|
|
55
|
+
// versions, so the resolution is done in memory.)
|
|
56
|
+
const allRows = await db
|
|
57
|
+
.select({
|
|
58
|
+
id: attachments.id,
|
|
59
|
+
slug: attachments.slug,
|
|
60
|
+
supersedes: attachments.supersedes,
|
|
61
|
+
supersededBy: attachments.supersededBy,
|
|
62
|
+
relatedTasks: attachments.relatedTasks,
|
|
63
|
+
topics: attachments.topics,
|
|
64
|
+
})
|
|
65
|
+
.from(attachments)
|
|
66
|
+
.all();
|
|
67
|
+
const slugById = new Map();
|
|
68
|
+
for (const r of allRows) {
|
|
69
|
+
if (r.slug)
|
|
70
|
+
slugById.set(r.id, r.slug);
|
|
71
|
+
}
|
|
72
|
+
const slugged = allRows
|
|
73
|
+
.filter((r) => typeof r.slug === 'string')
|
|
74
|
+
.map((r) => ({
|
|
75
|
+
slug: r.slug,
|
|
76
|
+
supersedesSlug: r.supersedes ? (slugById.get(r.supersedes) ?? null) : null,
|
|
77
|
+
supersededBySlug: r.supersededBy ? (slugById.get(r.supersededBy) ?? null) : null,
|
|
78
|
+
relatedTasks: r.relatedTasks,
|
|
79
|
+
topics: r.topics,
|
|
80
|
+
}));
|
|
81
|
+
const edges = deriveWikilinkEdges(slugged);
|
|
82
|
+
const nowIso = new Date().toISOString();
|
|
83
|
+
// The node:sqlite driver is synchronous — drizzle's transaction callback must
|
|
84
|
+
// be sync (an async body throws a DrizzleTypeError at build time).
|
|
85
|
+
db.transaction((tx) => {
|
|
86
|
+
tx.delete(docsWikilinks).run();
|
|
87
|
+
if (edges.length === 0)
|
|
88
|
+
return;
|
|
89
|
+
// Batch insert; INSERT OR IGNORE semantics via onConflictDoNothing keep the
|
|
90
|
+
// composite PK idempotent even if deriveWikilinkEdges emitted a duplicate.
|
|
91
|
+
tx.insert(docsWikilinks)
|
|
92
|
+
.values(edges.map((e) => ({
|
|
93
|
+
fromSlug: e.fromSlug,
|
|
94
|
+
toSlug: e.toSlug,
|
|
95
|
+
relation: e.relation,
|
|
96
|
+
toIsTask: e.toIsTask,
|
|
97
|
+
derivedAt: nowIso,
|
|
98
|
+
})))
|
|
99
|
+
.onConflictDoNothing()
|
|
100
|
+
.run();
|
|
101
|
+
});
|
|
102
|
+
const byRelation = {
|
|
103
|
+
supersedes: 0,
|
|
104
|
+
'superseded-by': 0,
|
|
105
|
+
'related-task': 0,
|
|
106
|
+
topic: 0,
|
|
107
|
+
};
|
|
108
|
+
for (const e of edges)
|
|
109
|
+
byRelation[e.relation] += 1;
|
|
110
|
+
return { edgeCount: edges.length, byRelation };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Pure derivation: turn slugged `attachments` rows into the full edge set.
|
|
114
|
+
*
|
|
115
|
+
* Exposed for unit testing the derivation rules without a database. The output
|
|
116
|
+
* is deduplicated on the (`fromSlug`, `toSlug`, `relation`) composite key.
|
|
117
|
+
*
|
|
118
|
+
* @param rows - Slugged attachment rows with their provenance columns.
|
|
119
|
+
* @returns The derived wikilink edges.
|
|
120
|
+
* @task T11826
|
|
121
|
+
*/
|
|
122
|
+
export function deriveWikilinkEdges(rows) {
|
|
123
|
+
const seen = new Set();
|
|
124
|
+
const out = [];
|
|
125
|
+
const push = (fromSlug, toSlug, relation, toIsTask) => {
|
|
126
|
+
const key = `${fromSlug}|${toSlug}|${relation}`;
|
|
127
|
+
if (seen.has(key))
|
|
128
|
+
return;
|
|
129
|
+
seen.add(key);
|
|
130
|
+
out.push({ fromSlug, toSlug, relation, toIsTask });
|
|
131
|
+
};
|
|
132
|
+
// Topic membership index: topic slug → set of doc slugs carrying it.
|
|
133
|
+
const topicMembers = new Map();
|
|
134
|
+
for (const row of rows) {
|
|
135
|
+
// supersedes / superseded-by — doc→doc.
|
|
136
|
+
if (row.supersedesSlug)
|
|
137
|
+
push(row.slug, row.supersedesSlug, 'supersedes', false);
|
|
138
|
+
if (row.supersededBySlug)
|
|
139
|
+
push(row.slug, row.supersededBySlug, 'superseded-by', false);
|
|
140
|
+
// related-task — doc→T####.
|
|
141
|
+
for (const taskId of parseStringArray(row.relatedTasks)) {
|
|
142
|
+
if (/^T\d+$/.test(taskId))
|
|
143
|
+
push(row.slug, taskId, 'related-task', true);
|
|
144
|
+
}
|
|
145
|
+
// Accumulate topic membership for the symmetric pass below.
|
|
146
|
+
for (const topic of parseStringArray(row.topics)) {
|
|
147
|
+
let members = topicMembers.get(topic);
|
|
148
|
+
if (!members) {
|
|
149
|
+
members = new Set();
|
|
150
|
+
topicMembers.set(topic, members);
|
|
151
|
+
}
|
|
152
|
+
members.add(row.slug);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// topic — symmetric doc↔doc edges for co-members of any topic.
|
|
156
|
+
for (const members of topicMembers.values()) {
|
|
157
|
+
const slugs = [...members];
|
|
158
|
+
for (let i = 0; i < slugs.length; i++) {
|
|
159
|
+
for (let j = 0; j < slugs.length; j++) {
|
|
160
|
+
if (i === j)
|
|
161
|
+
continue;
|
|
162
|
+
push(slugs[i], slugs[j], 'topic', false);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Read all wikilink edges incident to a doc slug — **bidirectional** by default.
|
|
170
|
+
*
|
|
171
|
+
* Returns both outbound edges (`from_slug = slug`) and inbound backlinks
|
|
172
|
+
* (`to_slug = slug`). This is the query the Obsidian plugin (T11827) renders to
|
|
173
|
+
* show a doc's neighborhood, and what `cleo docs graph` hydrates for persisted
|
|
174
|
+
* backlinks.
|
|
175
|
+
*
|
|
176
|
+
* @param slug - The doc slug to fetch edges for.
|
|
177
|
+
* @param opts.direction - `'both'` (default), `'out'`, or `'in'`.
|
|
178
|
+
* @param opts.projectRoot - Project root override.
|
|
179
|
+
* @returns The incident edges.
|
|
180
|
+
* @task T11826
|
|
181
|
+
*/
|
|
182
|
+
export async function getDocsWikilinks(slug, opts = {}) {
|
|
183
|
+
const direction = opts.direction ?? 'both';
|
|
184
|
+
const projectRoot = opts.projectRoot ?? getProjectRoot();
|
|
185
|
+
const db = await getDb(projectRoot);
|
|
186
|
+
const predicate = direction === 'out'
|
|
187
|
+
? eq(docsWikilinks.fromSlug, slug)
|
|
188
|
+
: direction === 'in'
|
|
189
|
+
? eq(docsWikilinks.toSlug, slug)
|
|
190
|
+
: or(eq(docsWikilinks.fromSlug, slug), eq(docsWikilinks.toSlug, slug));
|
|
191
|
+
const rows = await db.select().from(docsWikilinks).where(predicate).all();
|
|
192
|
+
return rows.map((r) => ({
|
|
193
|
+
fromSlug: r.fromSlug,
|
|
194
|
+
toSlug: r.toSlug,
|
|
195
|
+
relation: r.relation,
|
|
196
|
+
toIsTask: r.toIsTask,
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Parse a JSON-array-of-strings column, tolerating null / malformed values.
|
|
201
|
+
*
|
|
202
|
+
* @internal
|
|
203
|
+
*/
|
|
204
|
+
function parseStringArray(raw) {
|
|
205
|
+
if (!raw)
|
|
206
|
+
return [];
|
|
207
|
+
try {
|
|
208
|
+
const parsed = JSON.parse(raw);
|
|
209
|
+
if (!Array.isArray(parsed))
|
|
210
|
+
return [];
|
|
211
|
+
return parsed.filter((v) => typeof v === 'string' && v.length > 0);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=wikilinks.js.map
|