@skanl/brambo-environment 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 +29 -0
- package/dist/doctor.d.ts +261 -0
- package/dist/doctor.js +551 -0
- package/dist/executors.d.ts +88 -0
- package/dist/executors.js +109 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +60 -0
- package/dist/ingest.d.ts +60 -0
- package/dist/ingest.js +85 -0
- package/dist/init.d.ts +325 -0
- package/dist/init.js +641 -0
- package/dist/remediate.d.ts +51 -0
- package/dist/remediate.js +140 -0
- package/package.json +56 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { CLAUDE_MCP_TRAITS, CODEX_CONFIG_TRAITS, OPENCODE_CONFIG_TRAITS, readNativeMcpEntries, CLAUDE_MCP_TARGET_ID, CLAUDE_SKILLS_TARGET_ID, CODEX_CONFIG_TARGET_ID, CODEX_SKILLS_TARGET_ID, OPENCODE_CONFIG_TARGET_ID, OPENCODE_SKILLS_TARGET_ID, createClaudeMcpTarget, createClaudeSkillsTarget, createCodexConfigTarget, createCodexSkillsTarget, createOpenCodeConfigTarget, createOpenCodeSkillsTarget, } from '@skanl/brambo-projection';
|
|
4
|
+
export const EXECUTOR_PROFILES = [
|
|
5
|
+
{
|
|
6
|
+
executorId: 'claude-code',
|
|
7
|
+
targetId: CLAUDE_MCP_TARGET_ID,
|
|
8
|
+
// `~/.claude` is the directory Claude Code creates for itself; `~/.claude.json`
|
|
9
|
+
// is the file it reads MCP servers from. Either one is evidence it has run
|
|
10
|
+
// here. The home directory itself is deliberately not evidence — it exists on
|
|
11
|
+
// every machine and would make detection answer "yes" unconditionally.
|
|
12
|
+
evidencePaths: (homeDir) => [join(homeDir, '.claude.json'), join(homeDir, '.claude')],
|
|
13
|
+
machineConfig: (homeDir) => join(homeDir, '.claude.json'),
|
|
14
|
+
projectConfig: (projectDir) => join(projectDir, '.mcp.json'),
|
|
15
|
+
createTarget: (filePath) => createClaudeMcpTarget({ filePath }),
|
|
16
|
+
readMcpEntries: async (filePath) => await readNativeMcpEntries(CLAUDE_MCP_TRAITS, { filePath }),
|
|
17
|
+
machineSkills: (homeDir) => join(homeDir, '.claude', 'skills'),
|
|
18
|
+
skillsTargetId: CLAUDE_SKILLS_TARGET_ID,
|
|
19
|
+
createSkillsTarget: (rootPath) => createClaudeSkillsTarget({ rootPath }),
|
|
20
|
+
// `settings.json`, NOT `~/.claude.json`: correction-01 measured that the
|
|
21
|
+
// previous build wrote `$.brambo.{tools,mcpServers,skills,hooks}` there, into
|
|
22
|
+
// a file whose schema has none of those keys. Brambo's corrected target does
|
|
23
|
+
// not touch this file at all, which is exactly why nothing would ever clean
|
|
24
|
+
// it without this entry.
|
|
25
|
+
legacyConfig: (homeDir) => ({
|
|
26
|
+
filePath: join(homeDir, '.claude', 'settings.json'),
|
|
27
|
+
fileFormat: 'jsonc',
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
executorId: 'codex',
|
|
32
|
+
targetId: CODEX_CONFIG_TARGET_ID,
|
|
33
|
+
// ponytail: no `projectConfig`, because Codex reads MCP servers from
|
|
34
|
+
// `~/.codex/config.toml` alone — correction-01 verified no project-scope
|
|
35
|
+
// location, and brambo does not invent one. Consequence, reported per run
|
|
36
|
+
// rather than silent: `brambo project init` cannot bind Codex to a project.
|
|
37
|
+
// Upgrade path: a verified per-project Codex config, if Codex grows one.
|
|
38
|
+
evidencePaths: (homeDir) => [join(homeDir, '.codex', 'config.toml'), join(homeDir, '.codex')],
|
|
39
|
+
machineConfig: (homeDir) => join(homeDir, '.codex', 'config.toml'),
|
|
40
|
+
projectConfig: undefined,
|
|
41
|
+
createTarget: (filePath) => createCodexConfigTarget({ filePath }),
|
|
42
|
+
readMcpEntries: async (filePath) => await readNativeMcpEntries(CODEX_CONFIG_TRAITS, { filePath }),
|
|
43
|
+
machineSkills: (homeDir) => join(homeDir, '.codex', 'skills'),
|
|
44
|
+
skillsTargetId: CODEX_SKILLS_TARGET_ID,
|
|
45
|
+
createSkillsTarget: (rootPath) => createCodexSkillsTarget({ rootPath }),
|
|
46
|
+
// The harmful one. A `# BEGIN brambo-managed` block here carries foreign
|
|
47
|
+
// sub-keys inside `[tools]` and `[skills]`, which are real fixed structs, so
|
|
48
|
+
// a documented `--strict-config` run fails to load the ENTIRE file.
|
|
49
|
+
legacyConfig: (homeDir) => ({
|
|
50
|
+
filePath: join(homeDir, '.codex', 'config.toml'),
|
|
51
|
+
fileFormat: 'toml',
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
executorId: 'opencode',
|
|
56
|
+
targetId: OPENCODE_CONFIG_TARGET_ID,
|
|
57
|
+
evidencePaths: (homeDir) => [
|
|
58
|
+
join(homeDir, '.config', 'opencode', 'opencode.json'),
|
|
59
|
+
join(homeDir, '.config', 'opencode'),
|
|
60
|
+
],
|
|
61
|
+
machineConfig: (homeDir) => join(homeDir, '.config', 'opencode', 'opencode.json'),
|
|
62
|
+
projectConfig: (projectDir) => join(projectDir, 'opencode.json'),
|
|
63
|
+
createTarget: (filePath) => createOpenCodeConfigTarget({ filePath }),
|
|
64
|
+
readMcpEntries: async (filePath) => await readNativeMcpEntries(OPENCODE_CONFIG_TRAITS, { filePath }),
|
|
65
|
+
machineSkills: (homeDir) => join(homeDir, '.config', 'opencode', 'skills'),
|
|
66
|
+
skillsTargetId: OPENCODE_SKILLS_TARGET_ID,
|
|
67
|
+
createSkillsTarget: (rootPath) => createOpenCodeSkillsTarget({ rootPath }),
|
|
68
|
+
// The same file brambo's corrected target merges into, so the reserved key
|
|
69
|
+
// sits beside the `mcp` entries brambo writes today; it is dropped at decode
|
|
70
|
+
// (`onExcessProperty: 'ignore'`) and read by nothing.
|
|
71
|
+
legacyConfig: (homeDir) => ({
|
|
72
|
+
filePath: join(homeDir, '.config', 'opencode', 'opencode.json'),
|
|
73
|
+
fileFormat: 'jsonc',
|
|
74
|
+
}),
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
async function evidenceFor(path) {
|
|
78
|
+
try {
|
|
79
|
+
await stat(path);
|
|
80
|
+
return { path, exists: true };
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const code = error?.code;
|
|
84
|
+
// ENOENT and ENOTDIR are the two definitive absences: the path is not there,
|
|
85
|
+
// or a component of it is a file. Every other errno — EACCES, EPERM, ELOOP,
|
|
86
|
+
// an unreadable home — is brambo unable to LOOK, which is a different fact
|
|
87
|
+
// and is reported as one.
|
|
88
|
+
if (code === 'ENOENT' || code === 'ENOTDIR')
|
|
89
|
+
return { path, exists: false };
|
|
90
|
+
return { path, exists: undefined, error: code ?? String(error) };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Every executor brambo knows about, whether it was found, and the exact paths
|
|
95
|
+
* consulted for each. Returns the FULL catalogue on purpose: a run that detects
|
|
96
|
+
* nothing has to be able to tell the user what was looked for and where, and a
|
|
97
|
+
* list that omitted the misses could not.
|
|
98
|
+
*/
|
|
99
|
+
export async function detectExecutors(homeDir) {
|
|
100
|
+
return await Promise.all(EXECUTOR_PROFILES.map(async (profile) => {
|
|
101
|
+
const evidence = await Promise.all(profile.evidencePaths(homeDir).map(evidenceFor));
|
|
102
|
+
return {
|
|
103
|
+
executorId: profile.executorId,
|
|
104
|
+
targetId: profile.targetId,
|
|
105
|
+
present: evidence.some((candidate) => candidate.exists === true),
|
|
106
|
+
evidence,
|
|
107
|
+
};
|
|
108
|
+
}));
|
|
109
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { PROJECTION_ACTION_ID, initMachine, initProject, noExecutorsDetected, deliveryFor, scopeDirectory, storeFor, type EntryDelivery, type InitMachineOptions, type InitProjectOptions, type InitResult, type LegacyBlock, type SkippedExecutor, type TargetFailure, type TargetProjection, type UnprojectableEntry, } from './init.ts';
|
|
2
|
+
export { DIAGNOSIS_FINDING_KINDS, FINDING_EXITS, diagnose, findingKindsFor, hasProblem, type Diagnosis, type DiagnoseOptions, type DiagnosisFinding, type DiagnosisFindingKind, type DiagnosisFindingSeverity, type DiagnosisTarget, type FindingExit, type WorktreeLeftover, } from './doctor.ts';
|
|
3
|
+
export { remediate, type RemediateOptions, type RemediationReport } from './remediate.ts';
|
|
4
|
+
export { EXECUTOR_PROFILES, detectExecutors, type EvidencePath, type ExecutorDetection, type ExecutorProfile } from './executors.ts';
|
|
5
|
+
export { RegistryStore } from '@skanl/brambo-registry';
|
|
6
|
+
export type { RegistryStoreOptions } from '@skanl/brambo-registry';
|
|
7
|
+
export { BUNDLE_KIND, BUNDLE_VERSION, OMITTED_FIELDS, createBundle, isCredential, parseBundle, readBundle, serializeBundle, writeBundle } from '@skanl/brambo-registry';
|
|
8
|
+
export type { OmittedEntry, OmittedField, RegistryBundle } from '@skanl/brambo-registry';
|
|
9
|
+
export { DRIFT_KINDS, BRAMBO_VERSION, REGISTRY_ENTRY_TYPES, REMEDIATION_KINDS, REMOVABLE_ENTRY_TYPES, RETIRED_ENTRY_TYPES, expandRegistryEntryPaths, isRetiredEntryType, } from '@skanl/brambo-contracts';
|
|
10
|
+
export type { DriftEntry, DriftKind, BramboErrorCode, ProjectionWarning, RegistryEntry, RegistryEntryType, RegistryScope, RemediationChange, RemediationKind, RemediationOutcome, RemediationRefusal, RetiredEntryType, StoredEntryType, } from '@skanl/brambo-contracts';
|
|
11
|
+
export { createMemoryLogSink } from '@skanl/brambo-kernel';
|
|
12
|
+
export type { LogRecord, LogSink, MemoryLogSink } from '@skanl/brambo-kernel';
|
|
13
|
+
export { WRITABLE_CONFIG_KEYS, configPathFor, setConfigValue, type ConfigWriteOptions, type ConfigWriteResult, type WritableConfigKey, } from '@skanl/brambo-projection';
|
|
14
|
+
export { ingestMachine } from './ingest.ts';
|
|
15
|
+
export type { IngestMachineOptions, MachineIngest, MachineMcpIngest, MachineMcpSkip, MachineSkillsSkip, OwnedMcpEntry, } from './ingest.ts';
|
|
16
|
+
export type { IngestOutcome, IngestWarning } from '@skanl/brambo-contracts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export { PROJECTION_ACTION_ID, initMachine, initProject, noExecutorsDetected,
|
|
2
|
+
// The seams `brambo add` / `remove` / `list` bind to. Exported rather than
|
|
3
|
+
// re-derived in the binding: `storeFor` is the ONE mapping from a scope to a
|
|
4
|
+
// store, `scopeDirectory` is the trust boundary that keeps a project verb from
|
|
5
|
+
// building a tree brambo was asked to bind rather than create, and
|
|
6
|
+
// `deliveryFor` is what `add` reports its next step FROM — so the binding
|
|
7
|
+
// holds no idea of which entry type has a location at which scope.
|
|
8
|
+
deliveryFor, scopeDirectory, storeFor, } from './init.js';
|
|
9
|
+
export { DIAGNOSIS_FINDING_KINDS, FINDING_EXITS, diagnose, findingKindsFor, hasProblem, } from './doctor.js';
|
|
10
|
+
export { remediate } from './remediate.js';
|
|
11
|
+
export { EXECUTOR_PROFILES, detectExecutors } from './executors.js';
|
|
12
|
+
// Re-exported, not merely referenced. Under pnpm's strict layout a consumer that
|
|
13
|
+
// installed `@skanl/brambo-environment` cannot resolve `@skanl/brambo-contracts`,
|
|
14
|
+
// `@skanl/brambo-kernel` or `@skanl/brambo-registry` unless it declares them too — so a
|
|
15
|
+
// surface whose result carries a `DriftEntry` and whose precondition is "put
|
|
16
|
+
// entries in the registry first" has to hand back both, or the SDK promise is
|
|
17
|
+
// only true inside this monorepo. The list is exactly that: what you need to
|
|
18
|
+
// POPULATE the registry this projects from, READ a result, and OBSERVE the run.
|
|
19
|
+
//
|
|
20
|
+
// The closure is NOT total, and saying so is cheaper than a claim that rots:
|
|
21
|
+
// `RegistryStoreOptions.onStaleLockBreak` takes a `StaleLockBreak`,
|
|
22
|
+
// `ExecutorProfile.createTarget` returns a `ProjectionConfigTarget` and
|
|
23
|
+
// `ExecutorProfile.createSkillsTarget` a `ProjectionMaterialiseTarget`, none of
|
|
24
|
+
// which is re-exported. All three are reachable only by a consumer implementing
|
|
25
|
+
// one of those callbacks; the ordinary path needs none of them. Recorded in
|
|
26
|
+
// deferred-work.md rather than fixed by widening the surface on speculation.
|
|
27
|
+
export { RegistryStore } from '@skanl/brambo-registry';
|
|
28
|
+
// The bundle surface, for the same reason: a consumer holding a `RegistryStore`
|
|
29
|
+
// can build the artifact and read what did not travel without resolving
|
|
30
|
+
// `@skanl/brambo-registry` itself. `writeBundle` is here and this package still writes
|
|
31
|
+
// no file — it names a capability its own guard test forbids it to PERFORM,
|
|
32
|
+
// which is the whole point of a facade.
|
|
33
|
+
export { BUNDLE_KIND, BUNDLE_VERSION, OMITTED_FIELDS, createBundle, isCredential, parseBundle, readBundle, serializeBundle, writeBundle } from '@skanl/brambo-registry';
|
|
34
|
+
export { DRIFT_KINDS,
|
|
35
|
+
// `brambo --version`'s value, re-exported for the same reason every other
|
|
36
|
+
// constant on this list is: `@skanl/brambo-cli` is a THIN BINDING on the
|
|
37
|
+
// consumer tier, pinned by `packages/cli/test/run.test.ts` to
|
|
38
|
+
// `@skanl/brambo-environment` and `@skanl/brambo-session` and nothing else.
|
|
39
|
+
// M37.B put `import { BRAMBO_VERSION } from '@skanl/brambo-contracts'` at the top
|
|
40
|
+
// of `run.ts` and the pin's own comment records the premise that broke:
|
|
41
|
+
// "contracts moved to devDependencies once `describe()` stopped needing
|
|
42
|
+
// `instanceof BramboError`: the shipped CLI imports only consumer-tier
|
|
43
|
+
// packages". It did not any more, and the manifest and the import disagreed
|
|
44
|
+
// for a whole milestone -- the published binary importing a package it did not
|
|
45
|
+
// declare, which starts only because npm hoists the tree flat.
|
|
46
|
+
BRAMBO_VERSION, REGISTRY_ENTRY_TYPES, REMEDIATION_KINDS, REMOVABLE_ENTRY_TYPES, RETIRED_ENTRY_TYPES,
|
|
47
|
+
// The read-time inverse of the store's write-time normalization. A caller
|
|
48
|
+
// holding an entry in its PORTABLE form — the one a bundle carries — needs it
|
|
49
|
+
// to get back to the real paths the store's surface takes, and re-normalizing
|
|
50
|
+
// an already-normalized value corrupts it rather than being a no-op.
|
|
51
|
+
expandRegistryEntryPaths, isRetiredEntryType, } from '@skanl/brambo-contracts';
|
|
52
|
+
export { createMemoryLogSink } from '@skanl/brambo-kernel';
|
|
53
|
+
// Re-exported, not reimplemented. This package may not touch the filesystem at
|
|
54
|
+
// all (see test/guard.test.ts): the ledger is the sole authority for what brambo
|
|
55
|
+
// writes, and the clause is blunt on purpose. The WRITER is forwarded so the CLI
|
|
56
|
+
// reaches it through this facade -- the same shape as createMemoryLogSink above
|
|
57
|
+
// -- while the atomic primitive it uses stays inside @skanl/brambo-projection, where a
|
|
58
|
+
// previous story deliberately un-exported it.
|
|
59
|
+
export { WRITABLE_CONFIG_KEYS, configPathFor, setConfigValue, } from '@skanl/brambo-projection';
|
|
60
|
+
export { ingestMachine } from './ingest.js';
|
package/dist/ingest.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { IngestOutcome } from '@skanl/brambo-contracts';
|
|
2
|
+
import type { McpSourceDropped, McpSourceExclusion, McpSourceWarning, SkillsSourceWarning } from '@skanl/brambo-registry';
|
|
3
|
+
/** Which skill candidate the ingest looked at and did not contribute, and why. */
|
|
4
|
+
export type MachineSkillsSkip = SkillsSourceWarning;
|
|
5
|
+
/** Which server candidate the ingest looked at and did not contribute, and why. */
|
|
6
|
+
export type MachineMcpSkip = McpSourceWarning;
|
|
7
|
+
export interface IngestMachineOptions {
|
|
8
|
+
/** Defaults to the OS home directory, like every other machine-scope command. */
|
|
9
|
+
readonly homeDir?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Decide everything a writing run decides and write nothing.
|
|
12
|
+
*
|
|
13
|
+
* Forwarded to the ONE `ingestProviders` call rather than answered by a second
|
|
14
|
+
* pass here: a preview computed by different code than the write is a preview
|
|
15
|
+
* that can lie.
|
|
16
|
+
*/
|
|
17
|
+
readonly dryRun?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** The mcp-server half of one run, beside the skills half it shares a call with. */
|
|
20
|
+
export interface MachineMcpIngest {
|
|
21
|
+
/** The verified vendor config locations consulted, in profile order. */
|
|
22
|
+
readonly configPaths: readonly string[];
|
|
23
|
+
/** Candidates skipped: unreadable, an unusable id, or an ambiguous one. */
|
|
24
|
+
readonly skipped: readonly MachineMcpSkip[];
|
|
25
|
+
/**
|
|
26
|
+
* Servers left alone because brambo's own ledger claims them (D3), each paired
|
|
27
|
+
* with the `nativeLocation` that ledger record renders. The location is
|
|
28
|
+
* REPORTED and is deliberately not the match key: it is a rendering of the
|
|
29
|
+
* `targetId` and `entryId` that are, and matching on a rendering is how two
|
|
30
|
+
* answers come to differ.
|
|
31
|
+
*/
|
|
32
|
+
readonly ownedByBrambo: readonly OwnedMcpEntry[];
|
|
33
|
+
/** Vendor keys the registry envelope cannot carry, per ingested server (D10). */
|
|
34
|
+
readonly dropped: readonly McpSourceDropped[];
|
|
35
|
+
}
|
|
36
|
+
export type OwnedMcpEntry = McpSourceExclusion;
|
|
37
|
+
export interface MachineIngest {
|
|
38
|
+
readonly homeDir: string;
|
|
39
|
+
/** The machine registry document the run wrote to, or would have. */
|
|
40
|
+
readonly registryPath: string;
|
|
41
|
+
/** The verified skills roots consulted, in the order the profiles declare. */
|
|
42
|
+
readonly roots: readonly string[];
|
|
43
|
+
readonly dryRun: boolean;
|
|
44
|
+
readonly outcome: IngestOutcome;
|
|
45
|
+
/** Skill candidates skipped: not a skill, an unusable id, or an ambiguous one. */
|
|
46
|
+
readonly skipped: readonly MachineSkillsSkip[];
|
|
47
|
+
/** Skill directories left alone because brambo's own ledger claims them (D3). */
|
|
48
|
+
readonly ownedByBrambo: readonly string[];
|
|
49
|
+
/** The other half of the same run, reported so neither can go silent. */
|
|
50
|
+
readonly mcpServers: MachineMcpIngest;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Puts the skills and the MCP servers already on this machine into the machine
|
|
54
|
+
* registry, in ONE two-phase run over both origins.
|
|
55
|
+
*
|
|
56
|
+
* Additive, exactly as both ports document: an entry an origin stops listing is
|
|
57
|
+
* left in the registry untouched, and nothing is ever removed. Pruning is a
|
|
58
|
+
* separate decision and is deliberately not made here.
|
|
59
|
+
*/
|
|
60
|
+
export declare function ingestMachine(options?: IngestMachineOptions): Promise<MachineIngest>;
|
package/dist/ingest.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { BRAMBO_ERROR_CODES, BramboError } from '@skanl/brambo-contracts';
|
|
3
|
+
import { ProjectionLedger, SKILL_ENTRY_FILE } from '@skanl/brambo-projection';
|
|
4
|
+
import { createMachineMcpSource, createMachineSkillsSource, ingestProviders } from '@skanl/brambo-registry';
|
|
5
|
+
import { EXECUTOR_PROFILES } from './executors.js';
|
|
6
|
+
import { scopeDirectory, storeFor } from './init.js';
|
|
7
|
+
/**
|
|
8
|
+
* Puts the skills and the MCP servers already on this machine into the machine
|
|
9
|
+
* registry, in ONE two-phase run over both origins.
|
|
10
|
+
*
|
|
11
|
+
* Additive, exactly as both ports document: an entry an origin stops listing is
|
|
12
|
+
* left in the registry untouched, and nothing is ever removed. Pruning is a
|
|
13
|
+
* separate decision and is deliberately not made here.
|
|
14
|
+
*/
|
|
15
|
+
export async function ingestMachine(options = {}) {
|
|
16
|
+
const dryRun = options.dryRun === true;
|
|
17
|
+
// The same trust boundary every other machine-scope command applies, and the
|
|
18
|
+
// same `homedir()` call: a second spelling of "the home directory" is how two
|
|
19
|
+
// commands come to disagree about which registry they are talking about.
|
|
20
|
+
const home = await scopeDirectory('the home directory', options.homeDir ?? homedir());
|
|
21
|
+
const ledger = new ProjectionLedger({ homeDir: home });
|
|
22
|
+
const read = await ledger.read();
|
|
23
|
+
if (read.state === 'unreadable') {
|
|
24
|
+
// BEFORE the roots or the vendor documents are even listed, let alone
|
|
25
|
+
// written. Without the ledger brambo cannot tell its own projections apart
|
|
26
|
+
// from your skills and your servers, and ingesting brambo's own output is
|
|
27
|
+
// worse than not ingesting at all — so this is a refusal rather than a run
|
|
28
|
+
// that proceeds with a weaker guarantee.
|
|
29
|
+
throw new BramboError(BRAMBO_ERROR_CODES.projectionLedgerUnavailable,
|
|
30
|
+
// Deliberately not opened with the word `brambo`: `test/printed-commands.ts`
|
|
31
|
+
// treats a backtick-quoted string that starts that way as a COMMAND, and
|
|
32
|
+
// this is a sentence.
|
|
33
|
+
`refusing to ingest without the ownership ledger, because without it brambo cannot tell its own projections from your skills and servers: ${read.warnings.map((warning) => warning.detail).join('; ')}`);
|
|
34
|
+
}
|
|
35
|
+
const ownedPaths = read.records.flatMap((record) => (record.ownedPaths ?? []).map((owned) => owned.path));
|
|
36
|
+
// ONE ledger read serves both origins. `targetId` + `entryId` is the match
|
|
37
|
+
// key; the `nativeLocation` beside it is carried into the report only.
|
|
38
|
+
const ownedEntries = read.records.map((record) => ({
|
|
39
|
+
targetId: record.targetId,
|
|
40
|
+
entryId: record.entryId,
|
|
41
|
+
nativeLocation: record.nativeLocation,
|
|
42
|
+
}));
|
|
43
|
+
// `machineSkills` and `machineConfig`, and nothing else. Every one of these was
|
|
44
|
+
// verified by running the real binary under an injected home; an executor whose
|
|
45
|
+
// skills location brambo has NOT proven carries `undefined` and contributes no
|
|
46
|
+
// root, which is the honest answer rather than a location brambo invented.
|
|
47
|
+
const roots = EXECUTOR_PROFILES.flatMap((profile) => profile.machineSkills === undefined ? [] : [profile.machineSkills(home)]);
|
|
48
|
+
const locations = EXECUTOR_PROFILES.map((profile) => {
|
|
49
|
+
const filePath = profile.machineConfig(home);
|
|
50
|
+
return { targetId: profile.targetId, filePath, read: async () => await profile.readMcpEntries(filePath) };
|
|
51
|
+
});
|
|
52
|
+
const skills = createMachineSkillsSource({ roots, entryFileName: SKILL_ENTRY_FILE, ownedPaths });
|
|
53
|
+
const servers = createMachineMcpSource({ locations, ownedEntries });
|
|
54
|
+
const store = storeFor('machine', home, home);
|
|
55
|
+
try {
|
|
56
|
+
// ONE call, both origins, one `dryRun`. Splitting it would give the two
|
|
57
|
+
// halves two chances to disagree about a store they both write to.
|
|
58
|
+
const outcome = await ingestProviders(store, {
|
|
59
|
+
toolProviders: [servers],
|
|
60
|
+
skillSources: [skills],
|
|
61
|
+
dryRun,
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
homeDir: home,
|
|
65
|
+
registryPath: store.storePath('global'),
|
|
66
|
+
roots,
|
|
67
|
+
dryRun,
|
|
68
|
+
outcome,
|
|
69
|
+
skipped: [...skills.warnings],
|
|
70
|
+
ownedByBrambo: [...skills.excluded],
|
|
71
|
+
mcpServers: {
|
|
72
|
+
configPaths: locations.map((location) => location.filePath),
|
|
73
|
+
skipped: [...servers.warnings],
|
|
74
|
+
// Already whole: the source echoes back the ledger record it matched,
|
|
75
|
+
// so the location reported beside an exclusion is by construction the
|
|
76
|
+
// one that caused it, with no second lookup that could miss.
|
|
77
|
+
ownedByBrambo: [...servers.excluded],
|
|
78
|
+
dropped: [...servers.dropped],
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await store.dispose();
|
|
84
|
+
}
|
|
85
|
+
}
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import type { DriftEntry, BramboErrorCode, ProjectionTarget, ProjectionWarning, RegistryEntry, RegistryScope } from '@skanl/brambo-contracts';
|
|
2
|
+
import type { LogSink } from '@skanl/brambo-kernel';
|
|
3
|
+
import type { ProjectionMode } from '@skanl/brambo-projection';
|
|
4
|
+
import { RegistryStore } from '@skanl/brambo-registry';
|
|
5
|
+
import type { ExecutorDetection, ExecutorProfile } from './executors.ts';
|
|
6
|
+
/**
|
|
7
|
+
* Subject PREFIX every projection record is written under. The subject is
|
|
8
|
+
* `${PROJECTION_ACTION_ID}#${targetId}` — bounded by brambo's own constants, so
|
|
9
|
+
* it can never be rejected by the sink's identifier rules the way a file path
|
|
10
|
+
* (unbounded length, arbitrary characters) could be.
|
|
11
|
+
*
|
|
12
|
+
* Exported because a reader of the record stream needs the same string brambo
|
|
13
|
+
* wrote; match with `subject.startsWith(PROJECTION_ACTION_ID + '#')`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const PROJECTION_ACTION_ID = "environment.projection";
|
|
16
|
+
/** A registry entry a target could not express natively, and why (correction-01 C5). */
|
|
17
|
+
export interface UnprojectableEntry {
|
|
18
|
+
readonly entryId: string;
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
}
|
|
21
|
+
/** A detected executor that was not projected into, and why. */
|
|
22
|
+
export interface SkippedExecutor {
|
|
23
|
+
readonly executorId: string;
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A per-target failure, flattened to code and message. A live `Error` would
|
|
28
|
+
* serialise to `{}` for every caller that prints the result, and the code is the
|
|
29
|
+
* part a caller acts on.
|
|
30
|
+
*/
|
|
31
|
+
export interface TargetFailure {
|
|
32
|
+
readonly code: BramboErrorCode;
|
|
33
|
+
readonly message: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* What happened to ONE executor's configuration. The four facts the caller has
|
|
37
|
+
* to be able to tell apart are separate fields rather than one status word,
|
|
38
|
+
* because they are not mutually exclusive: a single run can write one entry,
|
|
39
|
+
* report a second as drifted, and report a third as unprojectable.
|
|
40
|
+
*
|
|
41
|
+
* written — `written === true`: the file on disk changed.
|
|
42
|
+
* unchanged — `written === false` with empty `drift` and no `error`.
|
|
43
|
+
* drifted — `drift` is non-empty; nothing in it was overwritten.
|
|
44
|
+
* unprojectable — `unprojectable` is non-empty; nothing was written for those.
|
|
45
|
+
* failed — `error` is set; this target alone is affected.
|
|
46
|
+
*/
|
|
47
|
+
export interface TargetProjection {
|
|
48
|
+
readonly executorId: string;
|
|
49
|
+
readonly targetId: string;
|
|
50
|
+
/**
|
|
51
|
+
* The vendor's own location, as that vendor reads it: a configuration FILE in
|
|
52
|
+
* `targets`, a skills ROOT DIRECTORY in `skills`.
|
|
53
|
+
*/
|
|
54
|
+
readonly filePath: string;
|
|
55
|
+
readonly written: boolean;
|
|
56
|
+
readonly drift: readonly DriftEntry[];
|
|
57
|
+
readonly unprojectable: readonly UnprojectableEntry[];
|
|
58
|
+
readonly error?: TargetFailure;
|
|
59
|
+
}
|
|
60
|
+
export interface InitResult {
|
|
61
|
+
readonly scope: 'machine' | 'project';
|
|
62
|
+
/** Brambo's own state directory for this scope; it exists once init returns. */
|
|
63
|
+
readonly bramboDir: string;
|
|
64
|
+
/** The registry store this run read from; it exists once init returns. */
|
|
65
|
+
readonly registryPath: string;
|
|
66
|
+
readonly ledgerPath: string;
|
|
67
|
+
/** Registry entries this run projected from, across every scope it can see. */
|
|
68
|
+
readonly entryCount: number;
|
|
69
|
+
/** EVERY executor brambo knows, found or not, with the paths consulted. */
|
|
70
|
+
readonly detected: readonly ExecutorDetection[];
|
|
71
|
+
readonly targets: readonly TargetProjection[];
|
|
72
|
+
/**
|
|
73
|
+
* The skills root of every detected executor whose location brambo has
|
|
74
|
+
* VERIFIED, one row each — a separate array rather than more `targets` rows
|
|
75
|
+
* because the two surfaces are not the same thing: one names a file brambo
|
|
76
|
+
* merges text into, the other a directory tree brambo materialises and, when a
|
|
77
|
+
* skill leaves the registry, removes. Empty where no verified location
|
|
78
|
+
* applies, which is every executor at project scope.
|
|
79
|
+
*/
|
|
80
|
+
readonly skills: readonly TargetProjection[];
|
|
81
|
+
readonly skipped: readonly SkippedExecutor[];
|
|
82
|
+
readonly warnings: readonly ProjectionWarning[];
|
|
83
|
+
}
|
|
84
|
+
export interface InitMachineOptions {
|
|
85
|
+
/** Defaults to the OS home directory. */
|
|
86
|
+
readonly homeDir?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Where the projection records go. Omitted, a memory sink is built and then
|
|
89
|
+
* dropped — the records are still produced, simply unread.
|
|
90
|
+
*
|
|
91
|
+
* ponytail: the caller owns the sink they pass, draining included. Read
|
|
92
|
+
* `sink.records` only after `await sink.drain()`.
|
|
93
|
+
*/
|
|
94
|
+
readonly log?: LogSink;
|
|
95
|
+
}
|
|
96
|
+
export interface InitProjectOptions extends InitMachineOptions {
|
|
97
|
+
/** Defaults to `process.cwd()`. */
|
|
98
|
+
readonly projectDir?: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* True when no executor was found — the caller's non-zero-exit condition.
|
|
102
|
+
*
|
|
103
|
+
* Takes the DETECTION, not an `InitResult`: `brambo doctor` has to answer the
|
|
104
|
+
* same question about the same evidence, and two spellings of "did brambo find
|
|
105
|
+
* anything" is how the two commands come to disagree about one machine.
|
|
106
|
+
*/
|
|
107
|
+
export declare function noExecutorsDetected(result: {
|
|
108
|
+
readonly detected: readonly ExecutorDetection[];
|
|
109
|
+
}): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* The trust boundary. `homeDir` and `projectDir` are caller-supplied paths that
|
|
112
|
+
* decide where brambo creates directories and which vendor files it writes, so
|
|
113
|
+
* every one of them is resolved ONCE here and rejected unless it already names a
|
|
114
|
+
* directory.
|
|
115
|
+
*
|
|
116
|
+
* Three failures this closes, all of them observed: `homeDir: ''` — which is
|
|
117
|
+
* exactly `process.env.HOME ?? ''` in a consumer — resolves to the CWD and
|
|
118
|
+
* relocates the machine scope into whatever directory the process happens to be
|
|
119
|
+
* in; `brambo project init ~/typo` built the whole missing tree and wrote a
|
|
120
|
+
* vendor config into it; and `brambo project init ~/repo/.git` would have done
|
|
121
|
+
* the same inside a git directory. Brambo BINDS a project, it does not create one.
|
|
122
|
+
*/
|
|
123
|
+
export declare function scopeDirectory(label: string, value: string): Promise<string>;
|
|
124
|
+
interface PlannedTarget {
|
|
125
|
+
readonly profile: ExecutorProfile;
|
|
126
|
+
readonly target: ProjectionTarget;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Brambo's OWN prior output still sitting in a vendor file (correction-01 C6).
|
|
130
|
+
*
|
|
131
|
+
* Every field of every row is produced by `runRemediation` under INSPECTION —
|
|
132
|
+
* the same call, in the same file, that `discard` performs. So the sentence
|
|
133
|
+
* `brambo doctor` prints about a legacy block is the sentence the remediation
|
|
134
|
+
* will act on, and the two cannot describe different regions.
|
|
135
|
+
*/
|
|
136
|
+
export interface LegacyBlock {
|
|
137
|
+
readonly executorId: string;
|
|
138
|
+
readonly targetId: string;
|
|
139
|
+
readonly filePath: string;
|
|
140
|
+
/** What brambo found and would remove, or why it will not touch it. */
|
|
141
|
+
readonly detail: string;
|
|
142
|
+
/** Bytes the file would lose; 0 when brambo refuses. */
|
|
143
|
+
readonly byteDelta: number;
|
|
144
|
+
readonly refusal?: TargetFailure;
|
|
145
|
+
}
|
|
146
|
+
export declare function targetsFor(scope: 'machine' | 'project', detected: readonly ExecutorDetection[], homeDir: string, projectDir: string): {
|
|
147
|
+
readonly planned: readonly PlannedTarget[];
|
|
148
|
+
/**
|
|
149
|
+
* The skills roots, planned only where the executor has a location brambo
|
|
150
|
+
* VERIFIED. Machine scope only: no executor has a project-scope skills
|
|
151
|
+
* location brambo has proven, so `brambo project init` materialises none and
|
|
152
|
+
* the skills stay reported as unprojectable — the same refusal that keeps
|
|
153
|
+
* Codex out of a project's MCP configuration.
|
|
154
|
+
*/
|
|
155
|
+
readonly skills: readonly PlannedTarget[];
|
|
156
|
+
readonly skipped: readonly SkippedExecutor[];
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* A per-target row in BOTH modes. `changed` is the one fact whose SENTENCE the
|
|
160
|
+
* mode decides — the merged text differs from the bytes on disk — so it is named
|
|
161
|
+
* for the fact and never for the write: `initMachine`/`initProject` map it to
|
|
162
|
+
* `written`, `diagnose` maps it to `wouldWrite`, and neither reading can be
|
|
163
|
+
* mistaken for the other by a caller holding the wrong one.
|
|
164
|
+
*/
|
|
165
|
+
export interface ScopeTarget {
|
|
166
|
+
readonly executorId: string;
|
|
167
|
+
readonly targetId: string;
|
|
168
|
+
readonly filePath: string;
|
|
169
|
+
readonly changed: boolean;
|
|
170
|
+
readonly drift: readonly DriftEntry[];
|
|
171
|
+
readonly unprojectable: readonly UnprojectableEntry[];
|
|
172
|
+
readonly error?: TargetFailure;
|
|
173
|
+
}
|
|
174
|
+
/** Everything one scope's engine run produced, before either caller phrases it. */
|
|
175
|
+
export interface ScopeReport {
|
|
176
|
+
readonly bramboDir: string;
|
|
177
|
+
/**
|
|
178
|
+
* This scope's registry document. Under `'apply'` it exists by the time the
|
|
179
|
+
* report is built; under `'inspect'` it is only a PATH, and whether anything
|
|
180
|
+
* is there is the answer to "has brambo been initialised here".
|
|
181
|
+
*/
|
|
182
|
+
readonly registryPath: string;
|
|
183
|
+
readonly ledgerPath: string;
|
|
184
|
+
readonly entryCount: number;
|
|
185
|
+
readonly detected: readonly ExecutorDetection[];
|
|
186
|
+
readonly targets: readonly ScopeTarget[];
|
|
187
|
+
/** One row per VERIFIED skills root; see `InitResult.skills`. */
|
|
188
|
+
readonly skills: readonly ScopeTarget[];
|
|
189
|
+
readonly skipped: readonly SkippedExecutor[];
|
|
190
|
+
/**
|
|
191
|
+
* Brambo's own prior output found in a vendor file. Computed under `'inspect'`
|
|
192
|
+
* ONLY: `brambo init` neither reports nor removes it, because removing it is a
|
|
193
|
+
* decision and this story's rule is that a decision is a user's.
|
|
194
|
+
*/
|
|
195
|
+
readonly legacy: readonly LegacyBlock[];
|
|
196
|
+
readonly warnings: readonly ProjectionWarning[];
|
|
197
|
+
/**
|
|
198
|
+
* Set ONLY under `'inspect'`, and only when brambo's own registry document
|
|
199
|
+
* could not be read. `'apply'` rethrows instead: projecting against a registry
|
|
200
|
+
* brambo cannot read would delete every entry it holds from every vendor file.
|
|
201
|
+
* When this is set, `targets` is EMPTY — with no registry there is nothing
|
|
202
|
+
* brambo can honestly say projecting would do.
|
|
203
|
+
*/
|
|
204
|
+
readonly registryError?: TargetFailure;
|
|
205
|
+
/**
|
|
206
|
+
* Entries whose type brambo has RETIRED — readable, listed and removable, and
|
|
207
|
+
* never handed to a target. Reported by `brambo doctor` because a word brambo no
|
|
208
|
+
* longer has is a state a user has to be told about AND given an exit from;
|
|
209
|
+
* `brambo init` neither projects nor removes them, because removing an entry is
|
|
210
|
+
* a decision and a decision is a user's.
|
|
211
|
+
*/
|
|
212
|
+
readonly retired: readonly RetiredEntry[];
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* One retired entry AND the document that actually holds it.
|
|
216
|
+
*
|
|
217
|
+
* The scope is carried rather than inferred, and that is the whole point of this
|
|
218
|
+
* type. `RegistryEntry` has no scope field and `store.list()` is the MERGED
|
|
219
|
+
* view, so a caller holding only the entry has to guess — and the only guess
|
|
220
|
+
* available is the scope being diagnosed. `brambo project doctor` reads the
|
|
221
|
+
* GLOBAL registry too, so that guess attributed a global entry to the (possibly
|
|
222
|
+
* empty) project document and told the user to run `brambo project remove`, which
|
|
223
|
+
* exits 1 for an entry that is not there. A finding that names a file must name
|
|
224
|
+
* the file the entry is actually in.
|
|
225
|
+
*/
|
|
226
|
+
export interface RetiredEntry {
|
|
227
|
+
readonly entry: RegistryEntry;
|
|
228
|
+
readonly scope: Exclude<RegistryScope, 'agent'>;
|
|
229
|
+
/** The document holding it — NOT necessarily the one being diagnosed. */
|
|
230
|
+
readonly registryPath: string;
|
|
231
|
+
}
|
|
232
|
+
export declare function storeFor(scope: 'machine' | 'project', homeDir: string, projectDir: string): RegistryStore;
|
|
233
|
+
/**
|
|
234
|
+
* What would actually DELIVER one entry at one scope, and — when nothing at
|
|
235
|
+
* that scope would — which scope does.
|
|
236
|
+
*
|
|
237
|
+
* DERIVED, never asserted. `brambo add` used to end with a sentence written
|
|
238
|
+
* beside the command ("`brambo project init` puts it into every detected
|
|
239
|
+
* executor"), and for a project-scope SKILL that sentence was false: no
|
|
240
|
+
* executor has a project-scope skills root brambo has verified, machine-scope
|
|
241
|
+
* projection cannot see a project-scope entry, and the entry was inert forever
|
|
242
|
+
* while the command it named exited 0. A promise can be kept syntactically and
|
|
243
|
+
* broken in substance, which is exactly what the printed-command invariant
|
|
244
|
+
* cannot catch.
|
|
245
|
+
*
|
|
246
|
+
* So nothing here knows which entry TYPE has a location at which scope. It runs
|
|
247
|
+
* `targetsFor` — the same planner `brambo init` runs — and then asks each target
|
|
248
|
+
* it planned whether it would take THIS entry, in the target's own words:
|
|
249
|
+
*
|
|
250
|
+
* - a config target is asked to merge into an EMPTY document. That call is
|
|
251
|
+
* pure text (`nativeText: ''` is the contract's "the file does not exist
|
|
252
|
+
* yet"), so no vendor file is read and none is written; an entry the target
|
|
253
|
+
* cannot express comes back in `skippedEntryIds`.
|
|
254
|
+
* - a materialise target is asked to PLAN. It describes what it would place
|
|
255
|
+
* and never touches the destination, and where it refuses it says why.
|
|
256
|
+
*
|
|
257
|
+
* Consequence, and it is the point: giving any `ExecutorProfile` a project-scope
|
|
258
|
+
* skills root changes what `brambo add` prints with no edit to the CLI and none
|
|
259
|
+
* to this function, and removing every skills root changes it the other way.
|
|
260
|
+
*/
|
|
261
|
+
export interface EntryDelivery {
|
|
262
|
+
readonly scope: 'machine' | 'project';
|
|
263
|
+
/** The command that projects this scope. Always one the binary dispatches. */
|
|
264
|
+
readonly command: string;
|
|
265
|
+
/** Detected executors whose target for this scope would take the entry. */
|
|
266
|
+
readonly executorIds: readonly string[];
|
|
267
|
+
/** Where a target refused, in the target's own words. */
|
|
268
|
+
readonly reasons: readonly string[];
|
|
269
|
+
/**
|
|
270
|
+
* Set ONLY when nothing at this scope takes the entry and another scope
|
|
271
|
+
* would. Naming it is the difference between a dead end and a next step.
|
|
272
|
+
*/
|
|
273
|
+
readonly elsewhere?: EntryDelivery;
|
|
274
|
+
/**
|
|
275
|
+
* Set when brambo could not work the answer out at all. The entry is already
|
|
276
|
+
* registered by the time this runs, so a failure here reports itself and
|
|
277
|
+
* never turns a completed registration into a failed command.
|
|
278
|
+
*/
|
|
279
|
+
readonly undetermined?: string;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The command that projects one scope. One spelling, and `doctor.ts` is now a
|
|
283
|
+
* caller too: it named `brambo init` as the exit from a PROJECT's
|
|
284
|
+
* `not-initialised` and `out-of-date`, and that command exits 0 leaving the
|
|
285
|
+
* project exactly as it was.
|
|
286
|
+
*/
|
|
287
|
+
export declare function projectCommandFor(scope: 'machine' | 'project'): string;
|
|
288
|
+
/**
|
|
289
|
+
* {@link EntryDelivery} for one scope, with the OTHER scope answered too
|
|
290
|
+
* whenever this one takes the entry nowhere.
|
|
291
|
+
*
|
|
292
|
+
* Contained: a target that throws while being asked leaves `undetermined` set
|
|
293
|
+
* rather than propagating, because the caller has already registered the entry
|
|
294
|
+
* and a message is not worth losing a completed write over.
|
|
295
|
+
*/
|
|
296
|
+
export declare function deliveryFor(entry: RegistryEntry, scope: 'machine' | 'project', homeDir: string, projectDir: string): Promise<EntryDelivery>;
|
|
297
|
+
/**
|
|
298
|
+
* Registry -> detection -> projection engine, for one scope. `brambo init` and
|
|
299
|
+
* `brambo doctor` are THIS function under the two projection modes and nothing
|
|
300
|
+
* else: same entries, same detection, same targets, same engine call, same drift
|
|
301
|
+
* classification. Two code paths could disagree about what applying would do,
|
|
302
|
+
* and they would disagree exactly when a user is trying to fix something.
|
|
303
|
+
*
|
|
304
|
+
* Every line below either reads, or writes through the projection engine — which
|
|
305
|
+
* under `'inspect'` writes nothing at all.
|
|
306
|
+
*/
|
|
307
|
+
export declare function runScope(scope: 'machine' | 'project', homeDir: string, projectDir: string, log: LogSink | undefined, mode: ProjectionMode): Promise<ScopeReport>;
|
|
308
|
+
/**
|
|
309
|
+
* Prepares this machine: brambo's own directory and registry store exist
|
|
310
|
+
* afterwards, and the global registry is projected into every detected
|
|
311
|
+
* executor's own machine-scope configuration.
|
|
312
|
+
*
|
|
313
|
+
* Idempotent. A second run over an unchanged registry writes no vendor byte and
|
|
314
|
+
* reports every target as unchanged.
|
|
315
|
+
*/
|
|
316
|
+
export declare function initMachine(options?: InitMachineOptions): Promise<InitResult>;
|
|
317
|
+
/**
|
|
318
|
+
* Binds a project: brambo's own directory and project registry store exist under
|
|
319
|
+
* it afterwards, and the registry it can see — the project's entries over the
|
|
320
|
+
* machine's — is projected into every detected executor that has a project-scope
|
|
321
|
+
* configuration. An executor without one is reported as skipped, never written
|
|
322
|
+
* to somewhere it does not read.
|
|
323
|
+
*/
|
|
324
|
+
export declare function initProject(options?: InitProjectOptions): Promise<InitResult>;
|
|
325
|
+
export {};
|