@cleocode/core 2026.10.12 → 2026.10.13-rc.ci.682
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/build-provenance.json +2 -2
- package/dist/cloud/nexus-auth.d.ts.map +1 -1
- package/dist/cloud/nexus-auth.js +11 -2
- package/dist/cloud/nexus-auth.js.map +1 -1
- package/dist/cloud/nexus-cloud-status.d.ts.map +1 -1
- package/dist/cloud/nexus-cloud-status.js +18 -5
- package/dist/cloud/nexus-cloud-status.js.map +1 -1
- package/dist/cloud/nexus-credentials.d.ts +3 -2
- package/dist/cloud/nexus-credentials.d.ts.map +1 -1
- package/dist/cloud/nexus-credentials.js +58 -14
- package/dist/cloud/nexus-credentials.js.map +1 -1
- package/dist/cloud/nexus-logout.d.ts.map +1 -1
- package/dist/cloud/nexus-logout.js +12 -2
- package/dist/cloud/nexus-logout.js.map +1 -1
- package/dist/cloud/nexus-vault-state.d.ts.map +1 -1
- package/dist/cloud/nexus-vault-state.js +6 -26
- package/dist/cloud/nexus-vault-state.js.map +1 -1
- package/dist/config/registry.d.ts +21 -0
- package/dist/config/registry.d.ts.map +1 -1
- package/dist/config/registry.js +41 -0
- package/dist/config/registry.js.map +1 -1
- package/dist/dispatch/mvi-projection.d.ts.map +1 -1
- package/dist/dispatch/mvi-projection.js +2 -0
- package/dist/dispatch/mvi-projection.js.map +1 -1
- package/dist/docs/docs-ops.d.ts +15 -14
- package/dist/docs/docs-ops.d.ts.map +1 -1
- package/dist/docs/docs-ops.js +32 -96
- package/dist/docs/docs-ops.js.map +1 -1
- package/dist/docs/docs-read-model.d.ts +19 -10
- package/dist/docs/docs-read-model.d.ts.map +1 -1
- package/dist/docs/docs-read-model.js +31 -47
- package/dist/docs/docs-read-model.js.map +1 -1
- package/dist/docs/docs-update.d.ts +51 -1
- package/dist/docs/docs-update.d.ts.map +1 -1
- package/dist/docs/docs-update.js +235 -13
- package/dist/docs/docs-update.js.map +1 -1
- package/dist/docs/doctor.d.ts +26 -4
- package/dist/docs/doctor.d.ts.map +1 -1
- package/dist/docs/doctor.js +260 -102
- package/dist/docs/doctor.js.map +1 -1
- package/dist/docs/fetch-result.d.ts +30 -10
- package/dist/docs/fetch-result.d.ts.map +1 -1
- package/dist/docs/fetch-result.js +100 -15
- package/dist/docs/fetch-result.js.map +1 -1
- package/dist/docs/index.d.ts +1 -0
- package/dist/docs/index.d.ts.map +1 -1
- package/dist/docs/index.js +1 -0
- package/dist/docs/index.js.map +1 -1
- package/dist/docs/legacy-doc-migration.d.ts +182 -0
- package/dist/docs/legacy-doc-migration.d.ts.map +1 -0
- package/dist/docs/legacy-doc-migration.js +480 -0
- package/dist/docs/legacy-doc-migration.js.map +1 -0
- package/dist/docs/publications-store.d.ts +67 -0
- package/dist/docs/publications-store.d.ts.map +1 -0
- package/dist/docs/publications-store.js +193 -0
- package/dist/docs/publications-store.js.map +1 -0
- package/dist/docs/publish-pr.d.ts.map +1 -1
- package/dist/docs/publish-pr.js +31 -18
- package/dist/docs/publish-pr.js.map +1 -1
- package/dist/docs/section-leases.d.ts +233 -0
- package/dist/docs/section-leases.d.ts.map +1 -0
- package/dist/docs/section-leases.js +344 -0
- package/dist/docs/section-leases.js.map +1 -0
- package/dist/docs/section-merge.d.ts +159 -0
- package/dist/docs/section-merge.d.ts.map +1 -0
- package/dist/docs/section-merge.js +215 -0
- package/dist/docs/section-merge.js.map +1 -0
- package/dist/docs/section-write-path.d.ts +191 -0
- package/dist/docs/section-write-path.d.ts.map +1 -0
- package/dist/docs/section-write-path.js +423 -0
- package/dist/docs/section-write-path.js.map +1 -0
- package/dist/docs/stats.d.ts +56 -0
- package/dist/docs/stats.d.ts.map +1 -0
- package/dist/docs/stats.js +119 -0
- package/dist/docs/stats.js.map +1 -0
- package/dist/gc/cleanup.d.ts +1 -1
- package/dist/gc/cleanup.d.ts.map +1 -1
- package/dist/gc/cleanup.js +10 -1
- package/dist/gc/cleanup.js.map +1 -1
- package/dist/gc/janitor.js +1 -1
- package/dist/gc/janitor.js.map +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.d.ts +34 -0
- package/dist/memory/pipeline-manifest-sqlite.d.ts.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.js +114 -0
- package/dist/memory/pipeline-manifest-sqlite.js.map +1 -1
- package/dist/nexus/analyze-orchestrator.d.ts +5 -1
- package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
- package/dist/nexus/analyze-orchestrator.js +25 -13
- package/dist/nexus/analyze-orchestrator.js.map +1 -1
- package/dist/nexus/assessment-store.d.ts +8 -4
- package/dist/nexus/assessment-store.d.ts.map +1 -1
- package/dist/nexus/assessment-store.js +18 -27
- package/dist/nexus/assessment-store.js.map +1 -1
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/orchestration/index.js +6 -0
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/lean-change.d.ts +104 -5
- package/dist/orchestration/lean-change.d.ts.map +1 -1
- package/dist/orchestration/lean-change.js +162 -6
- package/dist/orchestration/lean-change.js.map +1 -1
- package/dist/orchestration/spawn-prompt.d.ts +6 -1
- package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
- package/dist/orchestration/spawn-prompt.js +3 -1
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/orchestration/spawn.d.ts.map +1 -1
- package/dist/orchestration/spawn.js +6 -0
- package/dist/orchestration/spawn.js.map +1 -1
- package/dist/release/open.d.ts.map +1 -1
- package/dist/release/open.js +6 -2
- package/dist/release/open.js.map +1 -1
- package/dist/release/plan.d.ts.map +1 -1
- package/dist/release/plan.js +33 -2
- package/dist/release/plan.js.map +1 -1
- package/dist/resources/admission-ledger.d.ts +5 -0
- package/dist/resources/admission-ledger.d.ts.map +1 -1
- package/dist/resources/admission-ledger.js +1 -1
- package/dist/resources/admission-ledger.js.map +1 -1
- package/dist/resources/run-admission.d.ts.map +1 -1
- package/dist/resources/run-admission.js +4 -1
- package/dist/resources/run-admission.js.map +1 -1
- package/dist/resources/run-governed.d.ts +29 -4
- package/dist/resources/run-governed.d.ts.map +1 -1
- package/dist/resources/run-governed.js +113 -73
- package/dist/resources/run-governed.js.map +1 -1
- package/dist/sentient/worktree-dispatch.d.ts +12 -1
- package/dist/sentient/worktree-dispatch.d.ts.map +1 -1
- package/dist/sentient/worktree-dispatch.js +19 -0
- package/dist/sentient/worktree-dispatch.js.map +1 -1
- package/dist/spawn/branch-lock.d.ts +11 -1
- package/dist/spawn/branch-lock.d.ts.map +1 -1
- package/dist/spawn/branch-lock.js +54 -4
- package/dist/spawn/branch-lock.js.map +1 -1
- package/dist/sticky/archive.d.ts +1 -0
- package/dist/sticky/archive.d.ts.map +1 -1
- package/dist/sticky/archive.js +13 -0
- package/dist/sticky/archive.js.map +1 -1
- package/dist/sticky/engine-ops.d.ts.map +1 -1
- package/dist/sticky/engine-ops.js +7 -0
- package/dist/sticky/engine-ops.js.map +1 -1
- package/dist/store/attachment-repair.d.ts +10 -4
- package/dist/store/attachment-repair.d.ts.map +1 -1
- package/dist/store/attachment-repair.js +52 -25
- package/dist/store/attachment-repair.js.map +1 -1
- package/dist/store/attachment-store.d.ts.map +1 -1
- package/dist/store/attachment-store.js +27 -18
- package/dist/store/attachment-store.js.map +1 -1
- package/dist/store/blob-keep.d.ts +83 -0
- package/dist/store/blob-keep.d.ts.map +1 -1
- package/dist/store/blob-keep.js +139 -0
- package/dist/store/blob-keep.js.map +1 -1
- package/dist/store/blob-ops.d.ts +24 -0
- package/dist/store/blob-ops.d.ts.map +1 -1
- package/dist/store/blob-ops.js +47 -0
- package/dist/store/blob-ops.js.map +1 -1
- package/dist/store/conduit-doc-history.d.ts +209 -0
- package/dist/store/conduit-doc-history.d.ts.map +1 -1
- package/dist/store/conduit-doc-history.js +315 -3
- package/dist/store/conduit-doc-history.js.map +1 -1
- package/dist/store/decision-adr-links.d.ts +11 -0
- package/dist/store/decision-adr-links.d.ts.map +1 -1
- package/dist/store/decision-adr-links.js +20 -1
- package/dist/store/decision-adr-links.js.map +1 -1
- package/dist/store/display-id-alias.d.ts +8 -2
- package/dist/store/display-id-alias.d.ts.map +1 -1
- package/dist/store/display-id-alias.js +8 -3
- package/dist/store/display-id-alias.js.map +1 -1
- package/dist/store/lock.d.ts +18 -0
- package/dist/store/lock.d.ts.map +1 -1
- package/dist/store/lock.js +41 -0
- package/dist/store/lock.js.map +1 -1
- package/dist/store/row-identity-registry.d.ts.map +1 -1
- package/dist/store/row-identity-registry.js +17 -0
- package/dist/store/row-identity-registry.js.map +1 -1
- package/dist/store/schema/cleo-project/conduit.d.ts +16 -0
- package/dist/store/schema/cleo-project/conduit.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/conduit.js +8 -0
- package/dist/store/schema/cleo-project/conduit.js.map +1 -1
- package/dist/store/schema/cleo-project/docs.d.ts +294 -0
- package/dist/store/schema/cleo-project/docs.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/docs.js +82 -0
- package/dist/store/schema/cleo-project/docs.js.map +1 -1
- package/dist/store/sync/hlc.d.ts +9 -0
- package/dist/store/sync/hlc.d.ts.map +1 -1
- package/dist/store/sync/hlc.js +9 -0
- package/dist/store/sync/hlc.js.map +1 -1
- package/dist/store/sync/merge/engine.d.ts.map +1 -1
- package/dist/store/sync/merge/engine.js +4 -2
- package/dist/store/sync/merge/engine.js.map +1 -1
- package/dist/store/sync/merge/rules.d.ts +12 -1
- package/dist/store/sync/merge/rules.d.ts.map +1 -1
- package/dist/store/sync/merge/rules.js +76 -1
- package/dist/store/sync/merge/rules.js.map +1 -1
- package/dist/store/sync/replica-registry.d.ts +6 -2
- package/dist/store/sync/replica-registry.d.ts.map +1 -1
- package/dist/store/sync/replica-registry.js +21 -9
- package/dist/store/sync/replica-registry.js.map +1 -1
- package/dist/store/sync/timestamp-canon.d.ts.map +1 -1
- package/dist/store/sync/timestamp-canon.js +6 -0
- package/dist/store/sync/timestamp-canon.js.map +1 -1
- package/dist/store/table-classification.d.ts +58 -0
- package/dist/store/table-classification.d.ts.map +1 -1
- package/dist/store/table-classification.js +72 -0
- package/dist/store/table-classification.js.map +1 -1
- package/dist/store/twin-collapse.d.ts.map +1 -1
- package/dist/store/twin-collapse.js +4 -1
- package/dist/store/twin-collapse.js.map +1 -1
- package/dist/store/vault-manifest.d.ts +10 -1
- package/dist/store/vault-manifest.d.ts.map +1 -1
- package/dist/store/vault-manifest.js +59 -1
- package/dist/store/vault-manifest.js.map +1 -1
- package/dist/tasks/affected-scope.d.ts +11 -0
- package/dist/tasks/affected-scope.d.ts.map +1 -1
- package/dist/tasks/affected-scope.js +30 -11
- package/dist/tasks/affected-scope.js.map +1 -1
- package/dist/tasks/cancel-ops.d.ts.map +1 -1
- package/dist/tasks/cancel-ops.js +7 -2
- package/dist/tasks/cancel-ops.js.map +1 -1
- package/dist/tasks/change-set.d.ts +22 -0
- package/dist/tasks/change-set.d.ts.map +1 -1
- package/dist/tasks/change-set.js +119 -2
- package/dist/tasks/change-set.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +7 -0
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/done-plan.d.ts +5 -0
- package/dist/tasks/done-plan.d.ts.map +1 -1
- package/dist/tasks/done-plan.js +54 -19
- package/dist/tasks/done-plan.js.map +1 -1
- package/dist/tasks/engine-wrap.d.ts +4 -0
- package/dist/tasks/engine-wrap.d.ts.map +1 -1
- package/dist/tasks/engine-wrap.js +20 -4
- package/dist/tasks/engine-wrap.js.map +1 -1
- package/dist/tasks/evidence-paths.d.ts +11 -0
- package/dist/tasks/evidence-paths.d.ts.map +1 -1
- package/dist/tasks/evidence-paths.js +12 -0
- package/dist/tasks/evidence-paths.js.map +1 -1
- package/dist/tools/fs.d.ts +7 -3
- package/dist/tools/fs.d.ts.map +1 -1
- package/dist/tools/fs.js +23 -8
- package/dist/tools/fs.js.map +1 -1
- package/dist/worktree/list.d.ts.map +1 -1
- package/dist/worktree/list.js +9 -0
- package/dist/worktree/list.js.map +1 -1
- package/dist/worktree/prune.d.ts.map +1 -1
- package/dist/worktree/prune.js +52 -1
- package/dist/worktree/prune.js.map +1 -1
- package/migrations/drizzle-cleo-project/20261013000000_t13360-migrated-doc-import/migration.sql +61 -0
- package/migrations/drizzle-cleo-project/20261014000000_t13362-docs-publications-ledger/migration.sql +45 -0
- package/package.json +13 -13
- package/templates/CLEO-INJECTION.md +1 -1
- package/templates/CLEO-REFERENCE.md +2 -2
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docs update write path through the section CRDT merge layer (T13355 ·
|
|
3
|
+
* spec `cleodocs-rearch-design` §2 option b · D11165).
|
|
4
|
+
*
|
|
5
|
+
* Every `cleo docs update` carrying new inline text is projected through
|
|
6
|
+
* this module after the legacy whole-row write lands (`docs-update.ts`):
|
|
7
|
+
*
|
|
8
|
+
* 1. Resolve the doc's immutable uid, adopting a legacy doc into the
|
|
9
|
+
* conduit family on first sight ({@link ensureConduitDoc}).
|
|
10
|
+
* 2. Split the new text into sections and resolve the previous head's
|
|
11
|
+
* section texts from its version blob — the single-writer vs.
|
|
12
|
+
* concurrent discriminator (T13589).
|
|
13
|
+
* 3. Apply each section's edit to its local Loro state
|
|
14
|
+
* (`section-merge.ts`): while the stored state equals the head's text
|
|
15
|
+
* the input projects EXACTLY (re-seed; replaces and deletions apply
|
|
16
|
+
* verbatim — the primitives can only append, so re-seeding is the
|
|
17
|
+
* honest encoding); a concurrent edit that is a pure append composes
|
|
18
|
+
* onto the stored state so both survive (T13585); a concurrent
|
|
19
|
+
* NON-append edit is never fabricated into a merge — it is landed as
|
|
20
|
+
* its own version row and recorded as a head conflict (T13595).
|
|
21
|
+
* 4. Read the MERGED text back out of the merged Loro states — never out
|
|
22
|
+
* of the input — and write its content-addressed blob so the version
|
|
23
|
+
* row's `storage_key` resolves.
|
|
24
|
+
* 5. Append the merged text as a DAG version row
|
|
25
|
+
* (`appendConduitAttachmentVersion`, parent = the previous head's
|
|
26
|
+
* version) — history is append-only.
|
|
27
|
+
* 6. Project the merged text into the current-version row via
|
|
28
|
+
* {@link upsertConduitDocHead}: the max-HLC head wins and a losing
|
|
29
|
+
* concurrent head is recorded as a conflict, never silently shadowed
|
|
30
|
+
* (typed rule `docs.head.max-hlc`).
|
|
31
|
+
* 7. Rebase the local section states onto the landed version and rebuild
|
|
32
|
+
* the derived contributors rollup.
|
|
33
|
+
*
|
|
34
|
+
* Steps 1–7 run inside ONE `BEGIN IMMEDIATE` transaction
|
|
35
|
+
* ({@link withConduitDocWriteTransaction}, T13583): a failure after the
|
|
36
|
+
* version append rolls everything back, so the next update cannot fork
|
|
37
|
+
* onto a half-landed projection. The body uses only sync-driver accessors
|
|
38
|
+
* and sync `node:fs` calls, so no real I/O yield can admit another writer
|
|
39
|
+
* on the connection mid-transaction (the documented constraint).
|
|
40
|
+
*
|
|
41
|
+
* Loro owns LOCAL-ONLY section state; the syncing rows are owned by the
|
|
42
|
+
* Stage C journal merge engine — one merge authority per table (§2).
|
|
43
|
+
*
|
|
44
|
+
* @module
|
|
45
|
+
* @task T13355
|
|
46
|
+
* @epic T13340
|
|
47
|
+
* @saga T13339
|
|
48
|
+
*/
|
|
49
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
50
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
51
|
+
import { dirname, join } from 'node:path';
|
|
52
|
+
import { getCleoDirAbsolute } from '../paths.js';
|
|
53
|
+
import { appendConduitAttachmentVersion, ensureConduitDoc, getConduitAttachmentVersion, getConduitDocHead, readSectionCrdtState, rebaseSectionCrdtStates, rebuildConduitAttachmentContributors, resolveConduitDocUid, upsertConduitDocHead, withConduitDocWriteTransaction, writeSectionCrdtState, } from '../store/conduit-doc-history.js';
|
|
54
|
+
import { resolveBoundSession } from '../store/session-store.js';
|
|
55
|
+
import { compareHlc, encodeHlc, genesisHlc, parseHlc, tick } from '../store/sync/hlc.js';
|
|
56
|
+
import { listSectionLeases, SectionLeaseManager, } from './section-leases.js';
|
|
57
|
+
import { applySectionTextEdit, joinSections, sectionId, sectionStateText, splitDocIntoSections, } from './section-merge.js';
|
|
58
|
+
/**
|
|
59
|
+
* TTL granted by the write path's lease acquisitions (seconds). Inside
|
|
60
|
+
* llmtxt's 1–300 s range; long enough to cover an edit burst, short enough
|
|
61
|
+
* that a dead session's lease dies quickly (AC4: lease state never outlives
|
|
62
|
+
* its session without renew).
|
|
63
|
+
*/
|
|
64
|
+
export const SECTION_LEASE_TTL_SECONDS = 60;
|
|
65
|
+
/**
|
|
66
|
+
* This device's docs-writer HLC state. Module-level (not the journal's
|
|
67
|
+
* persisted `_sync_clock`): the docs projection's HLC orders the
|
|
68
|
+
* current-version row only, is seeded from the stored head on open, and a
|
|
69
|
+
* reset across processes is safe because the head comparison is (phys, ctr,
|
|
70
|
+
* replica) — a fresher wall clock always wins.
|
|
71
|
+
*/
|
|
72
|
+
let writerReplica = null;
|
|
73
|
+
let lastIssued = null;
|
|
74
|
+
/** The local writer's replica id (one random UUID per process). */
|
|
75
|
+
function localWriterReplica() {
|
|
76
|
+
writerReplica ??= randomUUID();
|
|
77
|
+
return writerReplica;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Mint the next local head HLC, strictly above the stored head when one
|
|
81
|
+
* exists (so a local write always advances the max-HLC projection).
|
|
82
|
+
*
|
|
83
|
+
* @param headHlc - The stored head's HLC, or `null` when no projection yet.
|
|
84
|
+
* @param wallMs - The physical candidate (defaults to `Date.now()`).
|
|
85
|
+
* @returns The encoded HLC.
|
|
86
|
+
*/
|
|
87
|
+
function mintHeadHlc(headHlc, wallMs = Date.now()) {
|
|
88
|
+
let base = lastIssued ?? genesisHlc(localWriterReplica());
|
|
89
|
+
if (headHlc !== null) {
|
|
90
|
+
try {
|
|
91
|
+
const head = parseHlc(headHlc);
|
|
92
|
+
if (compareHlc(head, base) > 0)
|
|
93
|
+
base = { ...head, replica: localWriterReplica() };
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// A stored head this process cannot parse is ignored: the mint below
|
|
97
|
+
// still lands at the wall clock, ahead of any plausible stored head.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
lastIssued = tick(base, wallMs);
|
|
101
|
+
return encodeHlc(lastIssued);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Rough token count for the version row (whitespace split; display only).
|
|
105
|
+
*
|
|
106
|
+
* @param text - The text to count.
|
|
107
|
+
* @returns The token count.
|
|
108
|
+
*/
|
|
109
|
+
function countTokens(text) {
|
|
110
|
+
return text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve the on-disk path of a version row's content-addressed blob.
|
|
114
|
+
*
|
|
115
|
+
* @param projectRoot - Absolute path to the CLEO project root.
|
|
116
|
+
* @param storageKey - The version's `storage_key` (`<2>/<rest>.md`).
|
|
117
|
+
* @returns The absolute blob path.
|
|
118
|
+
*/
|
|
119
|
+
function versionBlobPath(projectRoot, storageKey) {
|
|
120
|
+
return join(getCleoDirAbsolute(projectRoot), 'attachments', 'sha256', storageKey);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Read the previous head version's merged text and split it into sections.
|
|
124
|
+
*
|
|
125
|
+
* This is the single-writer vs. concurrent discriminator (T13589): a
|
|
126
|
+
* section whose stored state text equals the head's section text holds no
|
|
127
|
+
* unlanded concurrent edit. Sync `node:fs` only — the caller runs inside
|
|
128
|
+
* the write transaction, which must not yield to real I/O.
|
|
129
|
+
*
|
|
130
|
+
* @param projectRoot - Absolute path to the CLEO project root.
|
|
131
|
+
* @param head - The current head row.
|
|
132
|
+
* @returns The head's sections in document order, or `null` when the head's
|
|
133
|
+
* version or its blob is not resolvable locally (callers then take the
|
|
134
|
+
* single-writer path — see {@link applySectionTextEdit}).
|
|
135
|
+
*/
|
|
136
|
+
async function readHeadSectionTexts(projectRoot, head) {
|
|
137
|
+
const version = await getConduitAttachmentVersion(projectRoot, head.headVersionUid);
|
|
138
|
+
if (!version?.storageKey)
|
|
139
|
+
return null;
|
|
140
|
+
try {
|
|
141
|
+
const text = readFileSync(versionBlobPath(projectRoot, version.storageKey), 'utf-8');
|
|
142
|
+
return splitDocIntoSections(text);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Write the merged text's content-addressed blob so the version row's
|
|
150
|
+
* `storage_key` resolves. Sync `node:fs` only (transaction constraint, see
|
|
151
|
+
* {@link withConduitDocWriteTransaction}); content-addressed, so an existing
|
|
152
|
+
* file is reused and an orphan from a rolled-back transaction is harmless.
|
|
153
|
+
*
|
|
154
|
+
* @param projectRoot - Absolute path to the CLEO project root.
|
|
155
|
+
* @param storageKey - The version's `storage_key`.
|
|
156
|
+
* @param text - The merged text the key addresses.
|
|
157
|
+
*/
|
|
158
|
+
function writeMergedBlob(projectRoot, storageKey, text) {
|
|
159
|
+
const path = versionBlobPath(projectRoot, storageKey);
|
|
160
|
+
if (existsSync(path))
|
|
161
|
+
return;
|
|
162
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
163
|
+
writeFileSync(path, text, 'utf-8');
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Project one docs update through the section CRDT merge layer.
|
|
167
|
+
*
|
|
168
|
+
* The whole projection — adopt, section-state writes, version append, head
|
|
169
|
+
* upsert, rebase, contributors rebuild — is one transaction
|
|
170
|
+
* ({@link withConduitDocWriteTransaction}); the merged text is read back
|
|
171
|
+
* from the merged Loro states, so concurrent same-base section edits both
|
|
172
|
+
* survive into the version row and the head projection.
|
|
173
|
+
*
|
|
174
|
+
* Lease coordination (T13356, ADVISORY): before the transaction, the
|
|
175
|
+
* projection resolves the caller's session (input, else the bound session,
|
|
176
|
+
* gate 16) and acquires/renews a lease for every section the edit touches.
|
|
177
|
+
* A section leased by another live session yields a {@link SectionLeaseNotice}
|
|
178
|
+
* — the 409 equivalent as data. The edit PROCEEDS regardless: "409 is a
|
|
179
|
+
* social signal, not a hard lock" (llmtxt T285); the CRDT layer guarantees
|
|
180
|
+
* nothing is lost when coordination fails. Leases acquired for a projection
|
|
181
|
+
* that then rolls back are released, so a failed edit leaves no stale claim.
|
|
182
|
+
*
|
|
183
|
+
* @param projectRoot - Absolute path to the CLEO project root.
|
|
184
|
+
* @param input - The update facts.
|
|
185
|
+
* @returns The merge outcome.
|
|
186
|
+
*/
|
|
187
|
+
export async function projectDocUpdateThroughSections(projectRoot, input) {
|
|
188
|
+
const existingUid = await resolveConduitDocUid(projectRoot, input.slug);
|
|
189
|
+
const contentBuffer = Buffer.from(input.content, 'utf-8');
|
|
190
|
+
// T13356 — advisory lease coordination, before the transaction. Unbound
|
|
191
|
+
// callers (no input session, no bound session) edit uncoordinated: leases
|
|
192
|
+
// are per-session state and there is nothing to acquire under.
|
|
193
|
+
const boundSessionId = input.sessionId ?? (await resolveBoundSession(projectRoot))?.session.id ?? null;
|
|
194
|
+
const leaseNotices = [];
|
|
195
|
+
const acquiredThisRun = [];
|
|
196
|
+
if (boundSessionId !== null) {
|
|
197
|
+
const leaseManager = new SectionLeaseManager(projectRoot, {
|
|
198
|
+
sessionId: boundSessionId,
|
|
199
|
+
holder: input.authorAgentId,
|
|
200
|
+
});
|
|
201
|
+
const docUidForLeases = existingUid ?? input.slug;
|
|
202
|
+
for (const [index, text] of splitDocIntoSections(input.content).entries()) {
|
|
203
|
+
const id = sectionId(index);
|
|
204
|
+
const stored = existingUid !== null ? await readSectionCrdtState(projectRoot, docUidForLeases, id) : null;
|
|
205
|
+
// Only sections the edit actually touches are coordinated: unchanged
|
|
206
|
+
// sections carry no edit, so claiming them would only block others.
|
|
207
|
+
const touches = stored === null || sectionStateText(stored.snapshot) !== text;
|
|
208
|
+
if (!touches)
|
|
209
|
+
continue;
|
|
210
|
+
const acquired = await leaseManager.acquire(input.slug, id, SECTION_LEASE_TTL_SECONDS, input.changeSummary ?? 'section edit');
|
|
211
|
+
if (acquired.ok) {
|
|
212
|
+
acquiredThisRun.push(id);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (acquired.conflict.sessionId === boundSessionId) {
|
|
216
|
+
// Our own lease: continued edits renew it (T13356 AC2).
|
|
217
|
+
const renewed = await leaseManager.renew(input.slug, id, SECTION_LEASE_TTL_SECONDS);
|
|
218
|
+
if (!renewed.ok) {
|
|
219
|
+
// Expired between the probes: re-acquire (takeover of our own
|
|
220
|
+
// stale lease). A foreign live lease here is reported below.
|
|
221
|
+
const retry = await leaseManager.acquire(input.slug, id, SECTION_LEASE_TTL_SECONDS, input.changeSummary ?? 'section edit');
|
|
222
|
+
if (retry.ok) {
|
|
223
|
+
acquiredThisRun.push(id);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
leaseNotices.push(noticeOf(id, retry.conflict));
|
|
227
|
+
}
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
leaseNotices.push(noticeOf(id, acquired.conflict));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
return await withConduitDocWriteTransaction(projectRoot, async () => {
|
|
235
|
+
const { uid: docUid, adopted } = existingUid !== null
|
|
236
|
+
? { uid: existingUid, adopted: false }
|
|
237
|
+
: await ensureConduitDoc(projectRoot, {
|
|
238
|
+
slug: input.slug,
|
|
239
|
+
authorAgentId: input.authorAgentId,
|
|
240
|
+
content: contentBuffer,
|
|
241
|
+
});
|
|
242
|
+
// Resolve the previous head's section texts FIRST — the default
|
|
243
|
+
// single-writer vs. concurrent discriminator for every section below
|
|
244
|
+
// (T13589). A caller-declared base (T13674) REPLACES it: the incoming
|
|
245
|
+
// edit is then judged against the base it was authored from, so a
|
|
246
|
+
// stale base composes (append) or conflicts (non-append) instead of
|
|
247
|
+
// re-seeding over a concurrent writer's landed edit.
|
|
248
|
+
const previousHead = await getConduitDocHead(projectRoot, docUid);
|
|
249
|
+
const headSections = previousHead !== null ? await readHeadSectionTexts(projectRoot, previousHead) : null;
|
|
250
|
+
const referenceSections = input.baseSections !== undefined && input.baseSections !== null
|
|
251
|
+
? input.baseSections
|
|
252
|
+
: headSections;
|
|
253
|
+
// Apply each section's edit onto its stored Loro state and read the
|
|
254
|
+
// MERGED text back out of the state — never out of the input (T13585).
|
|
255
|
+
const sections = splitDocIntoSections(input.content);
|
|
256
|
+
const mergedSections = [];
|
|
257
|
+
const sectionsModified = [];
|
|
258
|
+
const conflictedSections = [];
|
|
259
|
+
for (const [index, text] of sections.entries()) {
|
|
260
|
+
const id = sectionId(index);
|
|
261
|
+
const stored = await readSectionCrdtState(projectRoot, docUid, id);
|
|
262
|
+
const { snapshot, changed, conflicted } = applySectionTextEdit(stored?.snapshot ?? null, text, referenceSections?.[index] ?? null);
|
|
263
|
+
if (changed || stored === null) {
|
|
264
|
+
await writeSectionCrdtState(projectRoot, {
|
|
265
|
+
attachmentUid: docUid,
|
|
266
|
+
sectionId: id,
|
|
267
|
+
snapshot,
|
|
268
|
+
baseVersionUid: stored?.baseVersionUid ?? null,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (changed)
|
|
272
|
+
sectionsModified.push(id);
|
|
273
|
+
if (conflicted)
|
|
274
|
+
conflictedSections.push(id);
|
|
275
|
+
mergedSections.push(sectionStateText(snapshot));
|
|
276
|
+
}
|
|
277
|
+
const mergedText = joinSections(mergedSections);
|
|
278
|
+
// Append the DAG version row (parent = the previous head's version).
|
|
279
|
+
const contentHash = createHash('sha256').update(mergedText, 'utf-8').digest('hex');
|
|
280
|
+
const size = Buffer.byteLength(mergedText, 'utf-8');
|
|
281
|
+
const storageKey = `${contentHash.slice(0, 2)}/${contentHash.slice(2)}.md`;
|
|
282
|
+
// Write the blob BEFORE the insert so a committed row never references
|
|
283
|
+
// missing bytes; a rollback only leaves a harmless content-addressed
|
|
284
|
+
// orphan (reused by a later identical projection).
|
|
285
|
+
writeMergedBlob(projectRoot, storageKey, mergedText);
|
|
286
|
+
const parentVersionUid = input.parentVersionUid !== undefined
|
|
287
|
+
? input.parentVersionUid
|
|
288
|
+
: (previousHead?.headVersionUid ?? null);
|
|
289
|
+
const version = await appendConduitAttachmentVersion(projectRoot, {
|
|
290
|
+
attachmentUid: docUid,
|
|
291
|
+
parentVersionUid,
|
|
292
|
+
authorAgentId: input.authorAgentId,
|
|
293
|
+
storageKey,
|
|
294
|
+
contentHash,
|
|
295
|
+
originalSize: size,
|
|
296
|
+
compressedSize: size,
|
|
297
|
+
tokens: countTokens(mergedText),
|
|
298
|
+
changeType: 'patch',
|
|
299
|
+
...(input.changeSummary !== undefined ? { changeSummary: input.changeSummary } : {}),
|
|
300
|
+
sectionsModified,
|
|
301
|
+
tokensAdded: countTokens(input.content),
|
|
302
|
+
});
|
|
303
|
+
// Project into the current-version row under the max-HLC rule. The
|
|
304
|
+
// version row survives even when a concurrent head wins, so nothing
|
|
305
|
+
// written above is lost.
|
|
306
|
+
const headHlc = input.hlc ?? mintHeadHlc(previousHead?.headHlc ?? null);
|
|
307
|
+
const head = await upsertConduitDocHead(projectRoot, {
|
|
308
|
+
attachmentUid: docUid,
|
|
309
|
+
headVersionUid: version.uid ?? '',
|
|
310
|
+
headHlc,
|
|
311
|
+
});
|
|
312
|
+
// T13595: a non-append concurrent section edit cannot be merged without
|
|
313
|
+
// fabricating text (the primitives are append-only), so land the incoming
|
|
314
|
+
// edit as its OWN version row — append-only history already preserves
|
|
315
|
+
// both — keep the stored projection as the head, and record the conflict
|
|
316
|
+
// on the head, mirroring the docs.head.max-hlc semantics. The conflict
|
|
317
|
+
// upsert uses the previous head's HLC, which is strictly below the
|
|
318
|
+
// projection HLC in the local-mint path, so the incoming version always
|
|
319
|
+
// loses honestly. (With an explicit `hlc` at or below the previous
|
|
320
|
+
// head's, the projection itself loses first and the incoming version
|
|
321
|
+
// records a second conflict — both stay visible, nothing is invented.)
|
|
322
|
+
const conflicts = [];
|
|
323
|
+
if (conflictedSections.length > 0) {
|
|
324
|
+
const incomingHash = createHash('sha256').update(input.content, 'utf-8').digest('hex');
|
|
325
|
+
const incomingKey = `${incomingHash.slice(0, 2)}/${incomingHash.slice(2)}.md`;
|
|
326
|
+
writeMergedBlob(projectRoot, incomingKey, input.content);
|
|
327
|
+
const incoming = await appendConduitAttachmentVersion(projectRoot, {
|
|
328
|
+
attachmentUid: docUid,
|
|
329
|
+
parentVersionUid,
|
|
330
|
+
authorAgentId: input.authorAgentId,
|
|
331
|
+
storageKey: incomingKey,
|
|
332
|
+
contentHash: incomingHash,
|
|
333
|
+
originalSize: Buffer.byteLength(input.content, 'utf-8'),
|
|
334
|
+
compressedSize: Buffer.byteLength(input.content, 'utf-8'),
|
|
335
|
+
tokens: countTokens(input.content),
|
|
336
|
+
changeType: 'patch',
|
|
337
|
+
changeSummary: `concurrent non-append edit on ${conflictedSections.join(', ')} — conflict recorded, not merged`,
|
|
338
|
+
sectionsModified: conflictedSections,
|
|
339
|
+
tokensAdded: countTokens(input.content),
|
|
340
|
+
});
|
|
341
|
+
if (previousHead !== null && incoming.uid) {
|
|
342
|
+
await upsertConduitDocHead(projectRoot, {
|
|
343
|
+
attachmentUid: docUid,
|
|
344
|
+
headVersionUid: incoming.uid,
|
|
345
|
+
headHlc: previousHead.headHlc,
|
|
346
|
+
});
|
|
347
|
+
for (const sectionId of conflictedSections) {
|
|
348
|
+
conflicts.push({
|
|
349
|
+
sectionId,
|
|
350
|
+
incomingVersionUid: incoming.uid,
|
|
351
|
+
projectionVersionUid: version.uid ?? '',
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// A landed version rebases the local section states; the derived
|
|
357
|
+
// contributors rollup is rebuilt from the versions table.
|
|
358
|
+
if (version.uid) {
|
|
359
|
+
await rebaseSectionCrdtStates(projectRoot, docUid, version.uid);
|
|
360
|
+
}
|
|
361
|
+
await rebuildConduitAttachmentContributors(projectRoot, docUid);
|
|
362
|
+
return {
|
|
363
|
+
docUid,
|
|
364
|
+
adopted,
|
|
365
|
+
mergedText,
|
|
366
|
+
sectionsModified,
|
|
367
|
+
version,
|
|
368
|
+
head,
|
|
369
|
+
previousHeadVersionUid: previousHead?.headVersionUid ?? null,
|
|
370
|
+
conflicts,
|
|
371
|
+
leaseNotices,
|
|
372
|
+
};
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
// The projection rolled back: release the leases acquired for it, so a
|
|
377
|
+
// failed edit leaves no stale claim. (Renewed pre-existing leases stay —
|
|
378
|
+
// the session still holds them.)
|
|
379
|
+
if (boundSessionId !== null && acquiredThisRun.length > 0) {
|
|
380
|
+
const leaseManager = new SectionLeaseManager(projectRoot, {
|
|
381
|
+
sessionId: boundSessionId,
|
|
382
|
+
holder: input.authorAgentId,
|
|
383
|
+
});
|
|
384
|
+
for (const section of acquiredThisRun) {
|
|
385
|
+
await leaseManager.release(input.slug, section);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
throw err;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/** Conflict view of a lease for a {@link SectionLeaseNotice}. */
|
|
392
|
+
function noticeOf(sectionIdValue, conflict) {
|
|
393
|
+
return {
|
|
394
|
+
sectionId: sectionIdValue,
|
|
395
|
+
holder: conflict.holder,
|
|
396
|
+
sessionId: conflict.sessionId,
|
|
397
|
+
expiresAt: conflict.expiresAt,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Release every section lease this session holds for a doc — the "release
|
|
402
|
+
* on completion" path of T13356 AC2: a session calls this when it finishes
|
|
403
|
+
* editing the doc, so the sections are free for the next editor before the
|
|
404
|
+
* TTL would have expired them.
|
|
405
|
+
*
|
|
406
|
+
* @param projectRoot - Absolute path to the CLEO project root.
|
|
407
|
+
* @param slug - The doc's live slug (leases are keyed by it).
|
|
408
|
+
* @param sessionId - The finishing session (only its own leases are released).
|
|
409
|
+
* @returns The section ids released.
|
|
410
|
+
*/
|
|
411
|
+
export async function releaseSectionLeases(projectRoot, slug, sessionId) {
|
|
412
|
+
const leaseManager = new SectionLeaseManager(projectRoot, { sessionId });
|
|
413
|
+
const released = [];
|
|
414
|
+
for (const lease of await listSectionLeases(projectRoot, slug)) {
|
|
415
|
+
if (lease.sessionId !== sessionId)
|
|
416
|
+
continue;
|
|
417
|
+
if (await leaseManager.release(lease.slug, lease.sectionId)) {
|
|
418
|
+
released.push(lease.sectionId);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return released;
|
|
422
|
+
}
|
|
423
|
+
//# sourceMappingURL=section-write-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-write-path.js","sourceRoot":"","sources":["../../src/docs/section-write-path.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `docs.stats` — per-project CleoDocs adoption metrics (T13363, spec §7/§9).
|
|
3
|
+
*
|
|
4
|
+
* Emitted so the CleoDocs re-architecture is judged by behaviour, not by
|
|
5
|
+
* file counts in the store:
|
|
6
|
+
*
|
|
7
|
+
* - **add:update ratio** — `docs.add` vs `docs.update` entries in the docs
|
|
8
|
+
* audit log (`.cleo/audit/docs-audit.jsonl`, via {@link readAuditLog}).
|
|
9
|
+
* The pilot's starting point is ~10:1.
|
|
10
|
+
* - **loose-markdown count** — `*.md` files TRACKED BY GIT outside the
|
|
11
|
+
* CleoDocs store (`git ls-files '*.md'` minus the store). Using the index
|
|
12
|
+
* makes the scan gitignore-aware by construction: dependency trees,
|
|
13
|
+
* VCS internals, `.cleo/council-runs`, and nested git worktrees never
|
|
14
|
+
* appear because real projects ignore them. The Council baseline counts
|
|
15
|
+
* (~751 axiom-app + 1,265 admin-operations) were of tracked/counted
|
|
16
|
+
* loose files, so this is the comparable shape. UNTracked loose `.md`
|
|
17
|
+
* files are intentionally out of scope — they are invisible to the
|
|
18
|
+
* adoption signal. Outside a git work tree the metric is 0 (there is no
|
|
19
|
+
* tracked-files notion to measure).
|
|
20
|
+
* - **wikilinks edges** — row count of the `docs_wikilinks` graph table
|
|
21
|
+
* (T11826), through the chokepoint drizzle handle.
|
|
22
|
+
* - **decisions linked** — live `decision_adr_links` rows (status
|
|
23
|
+
* `linked`, T13359). The legacy ledger `adrPath` field has no read
|
|
24
|
+
* accessor, so only the link table contributes.
|
|
25
|
+
*
|
|
26
|
+
* Read-only: never writes to the store, the filesystem, or the audit log.
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
* @task T13363
|
|
30
|
+
* @task T13629 — loose-markdown scan is git ls-files-based (gitignore-aware)
|
|
31
|
+
* @epic T13340
|
|
32
|
+
* @saga T13339
|
|
33
|
+
*/
|
|
34
|
+
import type { DocsStatsResult, LooseMarkdownMetrics } from '@cleocode/contracts/operations/docs.js';
|
|
35
|
+
/**
|
|
36
|
+
* Count loose `*.md` files — git-tracked markdown outside the CleoDocs
|
|
37
|
+
* store. `git ls-files '*.md'` from the project root returns the tracked
|
|
38
|
+
* markdown paths (gitignore-aware); the store and dependency trees are then
|
|
39
|
+
* filtered defensively. Untracked files are intentionally NOT counted.
|
|
40
|
+
*
|
|
41
|
+
* @param projectRoot - Absolute project root (must be a git work tree).
|
|
42
|
+
* @returns The count plus what was scanned and excluded; `count` is 0 with
|
|
43
|
+
* empty `scannedDirs` when the root is not a git work tree.
|
|
44
|
+
*/
|
|
45
|
+
export declare function countLooseMarkdown(projectRoot: string): LooseMarkdownMetrics;
|
|
46
|
+
/**
|
|
47
|
+
* Compute the per-project adoption metrics for `docs.stats` (T13363).
|
|
48
|
+
*
|
|
49
|
+
* @param opts.projectRoot - Absolute path to the CLEO project root.
|
|
50
|
+
* @returns The metrics envelope (read-only; nothing is written).
|
|
51
|
+
* @task T13363
|
|
52
|
+
*/
|
|
53
|
+
export declare function computeDocsStats(opts: {
|
|
54
|
+
projectRoot: string;
|
|
55
|
+
}): Promise<DocsStatsResult>;
|
|
56
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/docs/stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAGH,OAAO,KAAK,EAEV,eAAe,EACf,oBAAoB,EACrB,MAAM,wCAAwC,CAAC;AAgBhD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAkB5E;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAsC9F"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `docs.stats` — per-project CleoDocs adoption metrics (T13363, spec §7/§9).
|
|
3
|
+
*
|
|
4
|
+
* Emitted so the CleoDocs re-architecture is judged by behaviour, not by
|
|
5
|
+
* file counts in the store:
|
|
6
|
+
*
|
|
7
|
+
* - **add:update ratio** — `docs.add` vs `docs.update` entries in the docs
|
|
8
|
+
* audit log (`.cleo/audit/docs-audit.jsonl`, via {@link readAuditLog}).
|
|
9
|
+
* The pilot's starting point is ~10:1.
|
|
10
|
+
* - **loose-markdown count** — `*.md` files TRACKED BY GIT outside the
|
|
11
|
+
* CleoDocs store (`git ls-files '*.md'` minus the store). Using the index
|
|
12
|
+
* makes the scan gitignore-aware by construction: dependency trees,
|
|
13
|
+
* VCS internals, `.cleo/council-runs`, and nested git worktrees never
|
|
14
|
+
* appear because real projects ignore them. The Council baseline counts
|
|
15
|
+
* (~751 axiom-app + 1,265 admin-operations) were of tracked/counted
|
|
16
|
+
* loose files, so this is the comparable shape. UNTracked loose `.md`
|
|
17
|
+
* files are intentionally out of scope — they are invisible to the
|
|
18
|
+
* adoption signal. Outside a git work tree the metric is 0 (there is no
|
|
19
|
+
* tracked-files notion to measure).
|
|
20
|
+
* - **wikilinks edges** — row count of the `docs_wikilinks` graph table
|
|
21
|
+
* (T11826), through the chokepoint drizzle handle.
|
|
22
|
+
* - **decisions linked** — live `decision_adr_links` rows (status
|
|
23
|
+
* `linked`, T13359). The legacy ledger `adrPath` field has no read
|
|
24
|
+
* accessor, so only the link table contributes.
|
|
25
|
+
*
|
|
26
|
+
* Read-only: never writes to the store, the filesystem, or the audit log.
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
* @task T13363
|
|
30
|
+
* @task T13629 — loose-markdown scan is git ls-files-based (gitignore-aware)
|
|
31
|
+
* @epic T13340
|
|
32
|
+
* @saga T13339
|
|
33
|
+
*/
|
|
34
|
+
import { spawnSync } from 'node:child_process';
|
|
35
|
+
import { count } from 'drizzle-orm';
|
|
36
|
+
import { countLinkedDecisionAdrs } from '../store/decision-adr-links.js';
|
|
37
|
+
import { docsWikilinks } from '../store/schema/attachments.js';
|
|
38
|
+
import { getDb } from '../store/sqlite.js';
|
|
39
|
+
import { readAuditLog } from './docs-audit.js';
|
|
40
|
+
/**
|
|
41
|
+
* Directory names excluded from the loose-markdown count. The gitignore-aware
|
|
42
|
+
* `git ls-files` scan already keeps these out of the index in real projects;
|
|
43
|
+
* the filter below is defense-in-depth for a project that force-adds one.
|
|
44
|
+
* `.cleo` covers the CleoDocs storage surfaces (attachments, blobs) and
|
|
45
|
+
* `.cleo/council-runs` (the Council workload store).
|
|
46
|
+
*/
|
|
47
|
+
const EXCLUDED_DIR_NAMES = ['node_modules', '.git', '.cleo'];
|
|
48
|
+
/**
|
|
49
|
+
* Count loose `*.md` files — git-tracked markdown outside the CleoDocs
|
|
50
|
+
* store. `git ls-files '*.md'` from the project root returns the tracked
|
|
51
|
+
* markdown paths (gitignore-aware); the store and dependency trees are then
|
|
52
|
+
* filtered defensively. Untracked files are intentionally NOT counted.
|
|
53
|
+
*
|
|
54
|
+
* @param projectRoot - Absolute project root (must be a git work tree).
|
|
55
|
+
* @returns The count plus what was scanned and excluded; `count` is 0 with
|
|
56
|
+
* empty `scannedDirs` when the root is not a git work tree.
|
|
57
|
+
*/
|
|
58
|
+
export function countLooseMarkdown(projectRoot) {
|
|
59
|
+
const excludedDirs = [...EXCLUDED_DIR_NAMES];
|
|
60
|
+
const result = spawnSync('git', ['ls-files', '--', '*.md'], {
|
|
61
|
+
cwd: projectRoot,
|
|
62
|
+
encoding: 'utf8',
|
|
63
|
+
});
|
|
64
|
+
if (result.status !== 0) {
|
|
65
|
+
// Not a git work tree (or git unavailable): the metric counts tracked
|
|
66
|
+
// loose markdown only, so there is nothing to measure.
|
|
67
|
+
return { count: 0, scannedDirs: [], excludedDirs };
|
|
68
|
+
}
|
|
69
|
+
const tracked = result.stdout.split('\n').filter((line) => line.length > 0);
|
|
70
|
+
const counted = tracked.filter((p) => !p.startsWith('.cleo/') && !p.startsWith('node_modules/'));
|
|
71
|
+
const scanned = new Set();
|
|
72
|
+
for (const path of counted) {
|
|
73
|
+
scanned.add(path.includes('/') ? path.slice(0, path.indexOf('/')) : '(root)');
|
|
74
|
+
}
|
|
75
|
+
return { count: counted.length, scannedDirs: [...scanned].sort(), excludedDirs };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Compute the per-project adoption metrics for `docs.stats` (T13363).
|
|
79
|
+
*
|
|
80
|
+
* @param opts.projectRoot - Absolute path to the CLEO project root.
|
|
81
|
+
* @returns The metrics envelope (read-only; nothing is written).
|
|
82
|
+
* @task T13363
|
|
83
|
+
*/
|
|
84
|
+
export async function computeDocsStats(opts) {
|
|
85
|
+
const { projectRoot } = opts;
|
|
86
|
+
// Add vs update activity — the audit log is the canonical mutation record.
|
|
87
|
+
const audit = readAuditLog(projectRoot);
|
|
88
|
+
let adds = 0;
|
|
89
|
+
let updates = 0;
|
|
90
|
+
for (const entry of audit.entries) {
|
|
91
|
+
if (entry.op === 'docs.add')
|
|
92
|
+
adds += 1;
|
|
93
|
+
else if (entry.op === 'docs.update')
|
|
94
|
+
updates += 1;
|
|
95
|
+
}
|
|
96
|
+
const addUpdate = {
|
|
97
|
+
adds,
|
|
98
|
+
updates,
|
|
99
|
+
ratio: updates > 0 ? adds / updates : null,
|
|
100
|
+
};
|
|
101
|
+
const looseMarkdown = countLooseMarkdown(projectRoot);
|
|
102
|
+
// Graph edge count, through the chokepoint drizzle handle (reads only).
|
|
103
|
+
const db = await getDb(projectRoot);
|
|
104
|
+
const edgeRows = await db.select({ n: count() }).from(docsWikilinks).all();
|
|
105
|
+
const wikilinksEdges = edgeRows[0]?.n ?? 0;
|
|
106
|
+
const decisionsLinked = await countLinkedDecisionAdrs(projectRoot);
|
|
107
|
+
const ratioText = addUpdate.ratio === null ? 'n/a' : addUpdate.ratio.toFixed(2);
|
|
108
|
+
return {
|
|
109
|
+
addUpdate,
|
|
110
|
+
looseMarkdown,
|
|
111
|
+
wikilinksEdges,
|
|
112
|
+
decisionsLinked,
|
|
113
|
+
generatedAt: new Date().toISOString(),
|
|
114
|
+
summary: `adds ${adds} / updates ${updates} (ratio ${ratioText}); ` +
|
|
115
|
+
`${looseMarkdown.count} loose markdown files; ` +
|
|
116
|
+
`${wikilinksEdges} wikilinks edges; ${decisionsLinked} decisions linked`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/docs/stats.ts"],"names":[],"mappings":"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"}
|
package/dist/gc/cleanup.d.ts
CHANGED
|
@@ -160,7 +160,7 @@ export interface OrphanEntry {
|
|
|
160
160
|
* @task T9043
|
|
161
161
|
* @task T11996
|
|
162
162
|
*/
|
|
163
|
-
export declare function pruneOrphanWorktrees(options: PruneOrphanWorktreesOptions): CleanupResult
|
|
163
|
+
export declare function pruneOrphanWorktrees(options: PruneOrphanWorktreesOptions): Promise<CleanupResult>;
|
|
164
164
|
/**
|
|
165
165
|
* Prune orphaned CLEO-generated temp directories.
|
|
166
166
|
*
|
package/dist/gc/cleanup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../src/gc/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAoBH;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAqCtC,CAAC;AAEX;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,QAAqB,CAAC;AAM1D;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,kCAAkC;IAClC,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAoND;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../src/gc/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAoBH;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAqCtC,CAAC;AAEX;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,QAAqB,CAAC;AAM1D;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,kCAAkC;IAClC,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAoND;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,aAAa,CAAC,CAkLxB;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,0BAA+B,GAAG,aAAa,CAsE3F;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,GAAE,MAAgC,EAC1C,OAAO,GAAE,MAAiB,GACzB,WAAW,EAAE,CA4Bf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,EAC1B,WAAW,CAAC,EAAE,MAAM,GACnB,WAAW,EAAE,CAkDf"}
|