@skanl/brambo-projection 0.1.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/LICENSE +21 -0
- package/README.md +112 -0
- package/dist/atomic-write.d.ts +3 -0
- package/dist/atomic-write.js +90 -0
- package/dist/config-write.d.ts +49 -0
- package/dist/config-write.js +146 -0
- package/dist/document-fault.d.ts +59 -0
- package/dist/document-fault.js +78 -0
- package/dist/engine.d.ts +69 -0
- package/dist/engine.js +235 -0
- package/dist/formats.d.ts +132 -0
- package/dist/formats.js +1301 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +15 -0
- package/dist/ledger.d.ts +193 -0
- package/dist/ledger.js +552 -0
- package/dist/materialise.d.ts +55 -0
- package/dist/materialise.js +620 -0
- package/dist/remediate.d.ts +65 -0
- package/dist/remediate.js +468 -0
- package/dist/targets/claude-mcp.d.ts +6 -0
- package/dist/targets/claude-mcp.js +36 -0
- package/dist/targets/codex-config.d.ts +6 -0
- package/dist/targets/codex-config.js +27 -0
- package/dist/targets/opencode-config.d.ts +6 -0
- package/dist/targets/opencode-config.js +58 -0
- package/dist/targets/skills.d.ts +22 -0
- package/dist/targets/skills.js +165 -0
- package/package.json +57 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { runProjection, groupByKind, hasFileChangedSince, resolveProjectionMode } from './engine.ts';
|
|
2
|
+
export type { NativeFileSnapshot, ProjectionMode, ProjectionRun, RunProjectionOptions, } from './engine.ts';
|
|
3
|
+
export { WRITABLE_CONFIG_KEYS, configPathFor, setConfigValue, type ConfigWriteOptions, type ConfigWriteResult, type WritableConfigKey, } from './config-write.ts';
|
|
4
|
+
export { runRemediation } from './remediate.ts';
|
|
5
|
+
export type { AdoptRemediationOptions, DiscardRemediationOptions, LegacyBlockLocation, ReleaseRemediationOptions, RepairRemediationOptions, RunRemediationOptions, } from './remediate.ts';
|
|
6
|
+
export { ProjectionLedger, hashOwnedBytes, hashOwnedText, resolveOwnedPath, sameOwnedPath } from './ledger.ts';
|
|
7
|
+
export type { ProjectionLedgerOptions, ProjectionLedgerRead, ProjectionLedgerScope, ProjectionLedgerState, } from './ledger.ts';
|
|
8
|
+
export { createProjectionTargetFromTraits, readNativeMcpEntries, scanLegacyBramboBlock } from './formats.ts';
|
|
9
|
+
export type { FileFormat, LegacyBramboBlock, LegacyBramboScan, NativeEntryShape, NativeMcpRead, ProjectionTargetTraits, TraitTargetOptions, } from './formats.ts';
|
|
10
|
+
export { createClaudeMcpTarget, CLAUDE_MCP_TARGET_ID, CLAUDE_MCP_TRAITS } from './targets/claude-mcp.ts';
|
|
11
|
+
export type { ClaudeMcpTargetOptions } from './targets/claude-mcp.ts';
|
|
12
|
+
export { createCodexConfigTarget, CODEX_CONFIG_TARGET_ID, CODEX_CONFIG_TRAITS } from './targets/codex-config.ts';
|
|
13
|
+
export type { CodexConfigTargetOptions } from './targets/codex-config.ts';
|
|
14
|
+
export { createOpenCodeConfigTarget, OPENCODE_CONFIG_TARGET_ID, OPENCODE_CONFIG_TRAITS, } from './targets/opencode-config.ts';
|
|
15
|
+
export type { OpenCodeConfigTargetOptions } from './targets/opencode-config.ts';
|
|
16
|
+
export { CLAUDE_SKILLS_TARGET_ID, CLAUDE_SKILLS_TRAITS, CODEX_SKILLS_TARGET_ID, CODEX_SKILLS_TRAITS, OPENCODE_SKILLS_TARGET_ID, OPENCODE_SKILLS_TRAITS, SKILL_ENTRY_FILE, createClaudeSkillsTarget, createCodexSkillsTarget, createOpenCodeSkillsTarget, createSkillsTargetFromTraits, } from './targets/skills.ts';
|
|
17
|
+
export type { SkillsTargetOptions, SkillsTargetTraits } from './targets/skills.ts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { runProjection, groupByKind, hasFileChangedSince, resolveProjectionMode } from './engine.js';
|
|
2
|
+
// `atomicWriteText` is deliberately NOT here. A previous story un-exported it so
|
|
3
|
+
// `@skanl/brambo-environment` could not reach it, and `packages/environment/test/guard.test.ts`
|
|
4
|
+
// is the clause that notices if it comes back — the ledger is the sole authority
|
|
5
|
+
// for what brambo writes into a vendor's file. Brambo's OWN configuration document
|
|
6
|
+
// is a different thing and needs the same symlink-resolving write, so the WRITER
|
|
7
|
+
// lives here, beside the primitive, and only the writer is published.
|
|
8
|
+
export { WRITABLE_CONFIG_KEYS, configPathFor, setConfigValue, } from './config-write.js';
|
|
9
|
+
export { runRemediation } from './remediate.js';
|
|
10
|
+
export { ProjectionLedger, hashOwnedBytes, hashOwnedText, resolveOwnedPath, sameOwnedPath } from './ledger.js';
|
|
11
|
+
export { createProjectionTargetFromTraits, readNativeMcpEntries, scanLegacyBramboBlock } from './formats.js';
|
|
12
|
+
export { createClaudeMcpTarget, CLAUDE_MCP_TARGET_ID, CLAUDE_MCP_TRAITS } from './targets/claude-mcp.js';
|
|
13
|
+
export { createCodexConfigTarget, CODEX_CONFIG_TARGET_ID, CODEX_CONFIG_TRAITS } from './targets/codex-config.js';
|
|
14
|
+
export { createOpenCodeConfigTarget, OPENCODE_CONFIG_TARGET_ID, OPENCODE_CONFIG_TRAITS, } from './targets/opencode-config.js';
|
|
15
|
+
export { CLAUDE_SKILLS_TARGET_ID, CLAUDE_SKILLS_TRAITS, CODEX_SKILLS_TARGET_ID, CODEX_SKILLS_TRAITS, OPENCODE_SKILLS_TARGET_ID, OPENCODE_SKILLS_TRAITS, SKILL_ENTRY_FILE, createClaudeSkillsTarget, createCodexSkillsTarget, createOpenCodeSkillsTarget, createSkillsTargetFromTraits, } from './targets/skills.js';
|
package/dist/ledger.d.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { ProjectionLedgerRecord, ProjectionWarning } from '@skanl/brambo-contracts';
|
|
2
|
+
import type { StaleLockBreak } from '@skanl/brambo-lock';
|
|
3
|
+
/**
|
|
4
|
+
* Hash of the CANONICAL form of the text brambo placed at a native location.
|
|
5
|
+
*
|
|
6
|
+
* EOL is normalised because a file that git, an editor or a formatter rewrites
|
|
7
|
+
* from LF to CRLF has not been edited in any sense a vendor can observe — and
|
|
8
|
+
* `~/.claude.json` is rewritten by Claude Code itself. Treating that as an edit
|
|
9
|
+
* would make brambo disown every entry it has in the file. Format-specific
|
|
10
|
+
* canonicalisation (indentation, key order) happens in the strategies, which
|
|
11
|
+
* are the only code that knows what "the same entry" means per format.
|
|
12
|
+
*/
|
|
13
|
+
export declare function hashOwnedText(text: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Hash of the exact BYTES brambo copied to a path.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately not {@link hashOwnedText}: a materialised file is copied verbatim
|
|
18
|
+
* from a source brambo does not author, so "the same file" means the same bytes.
|
|
19
|
+
* Normalising EOL here would let brambo overwrite a file whose line endings a
|
|
20
|
+
* user deliberately changed, and — far worse, since this is the delete path —
|
|
21
|
+
* let it REMOVE one.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hashOwnedBytes(bytes: Uint8Array): string;
|
|
24
|
+
/**
|
|
25
|
+
* The EOL-normalised form of the same bytes, for the OVERWRITE comparison only.
|
|
26
|
+
*
|
|
27
|
+
* Never for a removal. A removal is decided byte for byte, because a false
|
|
28
|
+
* match there precedes `rm`; an overwrite decided byte for byte instead makes
|
|
29
|
+
* a skills root kept under `core.autocrlf` report every brambo file as edited
|
|
30
|
+
* forever, and the product has no adopt, force or reclaim path out of that.
|
|
31
|
+
*/
|
|
32
|
+
export declare function canonicalBytesHash(bytes: Uint8Array): string;
|
|
33
|
+
/** Ownership keys must be one canonical spelling of a path, never two. */
|
|
34
|
+
export declare function resolveOwnedPath(filePath: string): string;
|
|
35
|
+
/** win32 paths differ in drive-letter and directory casing between processes. */
|
|
36
|
+
export declare function sameOwnedPath(left: string, right: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Whether `path` is strictly inside `root`. Both arguments must already be
|
|
39
|
+
* resolved — this predicate answers about paths, not about strings a caller
|
|
40
|
+
* hopes are paths.
|
|
41
|
+
*
|
|
42
|
+
* It lives beside {@link resolveOwnedPath} because it is the second half of the
|
|
43
|
+
* same rule: a path brambo acts on is CANONICALISED and then proven to be inside
|
|
44
|
+
* the location brambo owns. Every caller that skips either half has been a
|
|
45
|
+
* user-data defect — the removal path took raw ledger strings straight to `rm`,
|
|
46
|
+
* and a relative one resolved against the process working directory.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isUnderRoot(path: string, root: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The right to replace the WHOLE ownership document without merging.
|
|
51
|
+
*
|
|
52
|
+
* A module-scope symbol rather than a convention: the only way to hold it is to
|
|
53
|
+
* import it, so "one caller" stops being a claim a text scan makes and becomes
|
|
54
|
+
* something the runtime enforces. `test/guard.test.ts` pins who imports it.
|
|
55
|
+
*/
|
|
56
|
+
export declare const LEDGER_REPAIR_AUTHORITY: unique symbol;
|
|
57
|
+
export type ProjectionLedgerState = 'absent' | 'readable' | 'unreadable';
|
|
58
|
+
export interface ProjectionLedgerRead {
|
|
59
|
+
/** `unreadable` means the file exists but brambo must not write over it. */
|
|
60
|
+
readonly state: ProjectionLedgerState;
|
|
61
|
+
readonly records: readonly ProjectionLedgerRecord[];
|
|
62
|
+
/**
|
|
63
|
+
* Records `records` rejected, reduced to the smallest claim brambo can still
|
|
64
|
+
* ACT on. Empty unless the document holds damage, and read by `repair` alone:
|
|
65
|
+
* every other caller wants only what brambo can vouch for.
|
|
66
|
+
*/
|
|
67
|
+
readonly salvaged: readonly ProjectionLedgerRecord[];
|
|
68
|
+
readonly warnings: readonly ProjectionWarning[];
|
|
69
|
+
}
|
|
70
|
+
/** Everything one target claims in one file — the unit a run replaces. */
|
|
71
|
+
export interface ProjectionLedgerScope {
|
|
72
|
+
readonly targetId: string;
|
|
73
|
+
readonly filePath: string;
|
|
74
|
+
}
|
|
75
|
+
export interface ProjectionLedgerOptions {
|
|
76
|
+
/** Defaults to the OS home directory. */
|
|
77
|
+
readonly homeDir?: string;
|
|
78
|
+
/** Overrides the whole path; the default is `<home>/.brambo/projection-ledger.json`. */
|
|
79
|
+
readonly filePath?: string;
|
|
80
|
+
/** Bounded wait for the cross-process lock before a coded CONTENTION refusal. */
|
|
81
|
+
readonly lockTimeoutMs?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Observes every stale/corrupt-lock break performed on the way to a write.
|
|
84
|
+
*
|
|
85
|
+
* A break is brambo deciding that a lock left behind by a dead process no
|
|
86
|
+
* longer protects anything. That decision is REPORTED rather than silent,
|
|
87
|
+
* because it is the one moment where the outer boundary steps aside.
|
|
88
|
+
*/
|
|
89
|
+
readonly onStaleLockBreak?: (broken: StaleLockBreak) => void;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The hash brambo writes for a claim it can still ADDRESS but can no longer
|
|
93
|
+
* VOUCH for.
|
|
94
|
+
*
|
|
95
|
+
* Deliberately not hex. A real `contentHash` is `sha256` output, so this can
|
|
96
|
+
* never compare equal to one by accident — the never-matching property is
|
|
97
|
+
* structural rather than improbable. `isLedgerRecord` asks only for a non-empty
|
|
98
|
+
* string, and nothing anywhere validates the shape, so this survives a round
|
|
99
|
+
* trip and reads as `edited` forever: brambo knows which bytes the claim covers
|
|
100
|
+
* and admits it does not know what it wrote there.
|
|
101
|
+
*/
|
|
102
|
+
export declare const UNVOUCHED_CONTENT_HASH = "unreadable-after-repair";
|
|
103
|
+
/**
|
|
104
|
+
* The exact bytes of the ledger document for a record set.
|
|
105
|
+
*
|
|
106
|
+
* Exported so a remediation can PREDICT the document it is about to write and
|
|
107
|
+
* report the byte delta before writing it, using the same serialisation the
|
|
108
|
+
* write itself performs. A second spelling here would let a preview report a
|
|
109
|
+
* size the act does not produce.
|
|
110
|
+
*/
|
|
111
|
+
export declare function serialiseLedgerDocument(records: readonly ProjectionLedgerRecord[]): string;
|
|
112
|
+
export declare class ProjectionLedger {
|
|
113
|
+
#private;
|
|
114
|
+
readonly filePath: string;
|
|
115
|
+
constructor(options?: ProjectionLedgerOptions);
|
|
116
|
+
/** Never throws: the three states are what callers must distinguish. */
|
|
117
|
+
read(): Promise<ProjectionLedgerRead>;
|
|
118
|
+
/**
|
|
119
|
+
* Replaces the records this caller TOOK A POSITION ON inside one scope,
|
|
120
|
+
* keeping every other claim — including claims inside the same scope that
|
|
121
|
+
* landed after the caller's snapshot was taken.
|
|
122
|
+
*
|
|
123
|
+
* `examined` is the third parameter and it is required, because omission used
|
|
124
|
+
* to mean deletion and nobody could see it. The caller passes the entry ids it
|
|
125
|
+
* read out of its own ledger snapshot for this scope; `update` adds the ids it
|
|
126
|
+
* is being handed now. An id in neither set belongs to a writer this caller
|
|
127
|
+
* never saw, and survives untouched.
|
|
128
|
+
*
|
|
129
|
+
* WITHOUT IT, A CONCURRENT RUN ERASES CLAIMS IT DELIBERATELY LEFT ALONE.
|
|
130
|
+
* Measured on the binary: ten rounds of two concurrent `brambo init` over one
|
|
131
|
+
* home lost 20 of 40 claims, 20 of 20 processes exited 0, and no stderr line
|
|
132
|
+
* named foreign, skip, collision or ledger; the one-process control lost 0 of
|
|
133
|
+
* 40. A wider run lost 72 of 144 and left 16 vendor entries owned by nobody,
|
|
134
|
+
* after which `brambo doctor` exited 0 reporting `"drift": []` and a later
|
|
135
|
+
* `remove` + `init` could no longer take those entries back out — brambo had
|
|
136
|
+
* permanently lost the ability to undo bytes it wrote.
|
|
137
|
+
*
|
|
138
|
+
* The mechanism is a granularity mismatch, not a lock. Process B reads the
|
|
139
|
+
* ledger before A persists, so B's snapshot holds none of A's claims. B then
|
|
140
|
+
* reads the vendor file, which by now holds A's entries, and classifies them
|
|
141
|
+
* as foreign — CORRECTLY, since nothing B can see claims them — so they never
|
|
142
|
+
* reach `projected.records`. The merge decided per entry; the write then
|
|
143
|
+
* replaced the whole scope. B left A's bytes alone and erased A's claim in the
|
|
144
|
+
* same breath. Keying the drop to what the caller examined is what makes the
|
|
145
|
+
* two agree.
|
|
146
|
+
*
|
|
147
|
+
* Deliberately NOT one write per entry: that shape preserves the same claims,
|
|
148
|
+
* but measured 99 writes per init instead of 6 (p50 61.5 ms each, 69x the
|
|
149
|
+
* ledger phase) and opened a contention cliff — 17 of 48 scopes failing at
|
|
150
|
+
* eight concurrent inits against 0 of 48 today. This keeps one write per scope.
|
|
151
|
+
*
|
|
152
|
+
* Still serialised against concurrent calls on this instance, and still NOT a
|
|
153
|
+
* fix for the orphan window above it: `engine.ts` writes the vendor file
|
|
154
|
+
* before it reaches this method, so a persist that throws — or a crash in
|
|
155
|
+
* between — leaves bytes no record claims. That is a different defect with a
|
|
156
|
+
* different fix, and it is open.
|
|
157
|
+
*/
|
|
158
|
+
update(scope: ProjectionLedgerScope, records: readonly ProjectionLedgerRecord[], examined: readonly string[]): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Replaces ONE entry's record inside one scope, reading the current document
|
|
161
|
+
* INSIDE the queue.
|
|
162
|
+
*
|
|
163
|
+
* The granularity is the point. A caller that read the ledger, decided, and
|
|
164
|
+
* then handed `update` a whole replacement set for the scope would resurrect
|
|
165
|
+
* every claim another writer legitimately dropped in between — brambo would
|
|
166
|
+
* then claim a path it does not own, which on the materialisation path is a
|
|
167
|
+
* delete authority. Only the named entry moves here; every sibling claim is
|
|
168
|
+
* whatever the document says at the moment of the write.
|
|
169
|
+
*
|
|
170
|
+
* `record === undefined` drops the entry instead of replacing it.
|
|
171
|
+
*/
|
|
172
|
+
updateEntry(scope: ProjectionLedgerScope, entryId: string, record: ProjectionLedgerRecord | undefined): Promise<void>;
|
|
173
|
+
/**
|
|
174
|
+
* Replaces the WHOLE document with whatever `select` returns for the document
|
|
175
|
+
* as it is INSIDE the queue.
|
|
176
|
+
*
|
|
177
|
+
* This is the one write that does not merge, and it exists for exactly one
|
|
178
|
+
* caller: the user-named `repair` remediation, which is how a ledger holding
|
|
179
|
+
* records brambo cannot read stops being a state with no exit. Nothing else may
|
|
180
|
+
* use it — `update` is the merging write every projection performs, and its
|
|
181
|
+
* refusal to overwrite an unreadable ledger is a load-bearing guarantee that
|
|
182
|
+
* this method deliberately does not have. `test/guard.test.ts` pins the caller
|
|
183
|
+
* list, because a second one would silently reintroduce the orphan-every-claim
|
|
184
|
+
* failure Story 2.8 declared terminal.
|
|
185
|
+
*
|
|
186
|
+
* `select` runs INSIDE the queue and is handed the read the write will be
|
|
187
|
+
* based on. A caller that read the document itself and passed the result would
|
|
188
|
+
* destroy every claim written in between — with no merge to save it, which is
|
|
189
|
+
* exactly what makes this method the dangerous one. It may throw to abort the
|
|
190
|
+
* write, which is how `repair` refuses when the document moved under it.
|
|
191
|
+
*/
|
|
192
|
+
rewriteAll(authority: typeof LEDGER_REPAIR_AUTHORITY, select: (read: ProjectionLedgerRead) => readonly ProjectionLedgerRecord[]): Promise<void>;
|
|
193
|
+
}
|