brainclaw 0.19.12 → 0.20.0
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/README.md +42 -11
- package/dist/cli.js +55 -1
- package/dist/commands/claude-desktop-extension.js +18 -0
- package/dist/commands/context.js +3 -1
- package/dist/commands/doctor.js +12 -5
- package/dist/commands/export.js +44 -0
- package/dist/commands/init.js +22 -6
- package/dist/commands/list-surface-tasks.js +39 -0
- package/dist/commands/mcp.js +86 -5
- package/dist/commands/reconcile.js +138 -0
- package/dist/commands/setup.js +19 -0
- package/dist/commands/status.js +17 -12
- package/dist/commands/surface-task-resource.js +35 -0
- package/dist/commands/surface-task.js +57 -0
- package/dist/commands/uninstall.js +145 -0
- package/dist/commands/update-surface-task.js +30 -0
- package/dist/core/agent-capability.js +184 -0
- package/dist/core/agent-context.js +24 -6
- package/dist/core/agent-files.js +18 -18
- package/dist/core/ai-surface-inventory.js +321 -0
- package/dist/core/ai-surface-tasks.js +40 -0
- package/dist/core/bootstrap.js +177 -0
- package/dist/core/claude-desktop-extension.js +224 -0
- package/dist/core/context.js +47 -24
- package/dist/core/ids.js +1 -0
- package/dist/core/instruction-templates.js +308 -0
- package/dist/core/io.js +1 -0
- package/dist/core/machine-profile.js +7 -1
- package/dist/core/migration.js +3 -1
- package/dist/core/schema.js +34 -0
- package/dist/core/setup-flow.js +191 -0
- package/dist/core/setup-state.js +30 -1
- package/dist/core/store-resolution.js +58 -0
- package/dist/core/workspace-projects.js +115 -0
- package/docs/architecture/project-refs.md +305 -0
- package/docs/cli.md +133 -1
- package/docs/integrations/agents.md +102 -150
- package/docs/integrations/overview.md +71 -45
- package/docs/quickstart.md +44 -111
- package/package.json +1 -1
package/dist/core/bootstrap.js
CHANGED
|
@@ -267,6 +267,12 @@ function buildBootstrapArtifacts(input) {
|
|
|
267
267
|
const repoAnalysis = analyzeRepository(input.cwd);
|
|
268
268
|
sourcesScanned.push('repo-analysis');
|
|
269
269
|
seeds.push(...extractRepoAnalysisSeeds(repoAnalysis, input.target));
|
|
270
|
+
// Additional brownfield sources (step 12)
|
|
271
|
+
const additionalSeeds = extractAdditionalBrownfieldSeeds(input.cwd, input.target);
|
|
272
|
+
if (additionalSeeds.seeds.length > 0) {
|
|
273
|
+
sourcesScanned.push(...additionalSeeds.sources);
|
|
274
|
+
seeds.push(...additionalSeeds.seeds);
|
|
275
|
+
}
|
|
270
276
|
const gitProbe = probeGit(input.cwd, input.target);
|
|
271
277
|
if (gitProbe.available) {
|
|
272
278
|
sourcesScanned.push('git');
|
|
@@ -703,6 +709,44 @@ function probeGit(cwd, target) {
|
|
|
703
709
|
}));
|
|
704
710
|
}
|
|
705
711
|
}
|
|
712
|
+
// Step 13: Active branches
|
|
713
|
+
const branchResult = spawnSync('git', ['branch', '--no-merged', 'HEAD', '--format=%(refname:short)'], {
|
|
714
|
+
cwd,
|
|
715
|
+
encoding: 'utf-8',
|
|
716
|
+
timeout: 5000,
|
|
717
|
+
});
|
|
718
|
+
if (branchResult.status === 0) {
|
|
719
|
+
const branches = branchResult.stdout.split(/\r?\n/).map((b) => b.trim()).filter(Boolean).slice(0, 5);
|
|
720
|
+
for (const branch of branches) {
|
|
721
|
+
hotspotSeeds.push(createSeed({
|
|
722
|
+
text: `Active branch: ${branch}`,
|
|
723
|
+
seedKind: 'hotspot',
|
|
724
|
+
sourceKind: 'git',
|
|
725
|
+
sourceRef: `branch:${branch}`,
|
|
726
|
+
confidence: 'low',
|
|
727
|
+
tags: ['bootstrap', 'git', 'branch'],
|
|
728
|
+
}));
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
// Step 13: Recent tags
|
|
732
|
+
const tagResult = spawnSync('git', ['tag', '--sort=-creatordate', '-l'], {
|
|
733
|
+
cwd,
|
|
734
|
+
encoding: 'utf-8',
|
|
735
|
+
timeout: 5000,
|
|
736
|
+
});
|
|
737
|
+
if (tagResult.status === 0) {
|
|
738
|
+
const tags = tagResult.stdout.split(/\r?\n/).map((t) => t.trim()).filter(Boolean).slice(0, 3);
|
|
739
|
+
if (tags.length > 0) {
|
|
740
|
+
hotspotSeeds.push(createSeed({
|
|
741
|
+
text: `Version tags: ${tags.join(', ')} (${tags.length} most recent)`,
|
|
742
|
+
seedKind: 'convention',
|
|
743
|
+
sourceKind: 'git',
|
|
744
|
+
sourceRef: 'tags',
|
|
745
|
+
confidence: 'medium',
|
|
746
|
+
tags: ['bootstrap', 'git', 'versioning'],
|
|
747
|
+
}));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
706
750
|
return {
|
|
707
751
|
available: true,
|
|
708
752
|
repoFingerprint,
|
|
@@ -1594,4 +1638,137 @@ function normalizeTarget(target) {
|
|
|
1594
1638
|
const trimmed = target?.trim();
|
|
1595
1639
|
return trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
1596
1640
|
}
|
|
1641
|
+
// ─── Step 12: Additional brownfield sources ──────────────────────────────────
|
|
1642
|
+
const CI_WORKFLOW_DIRS = ['.github/workflows', '.gitlab'];
|
|
1643
|
+
const CI_FILES = ['.gitlab-ci.yml', 'Jenkinsfile', '.circleci/config.yml'];
|
|
1644
|
+
const CONTRIBUTING_FILES = ['CONTRIBUTING.md', 'CONTRIBUTING'];
|
|
1645
|
+
const CHANGELOG_FILES = ['CHANGELOG.md', 'CHANGELOG', 'HISTORY.md'];
|
|
1646
|
+
const DOCKER_FILES = ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', 'compose.yml', 'compose.yaml'];
|
|
1647
|
+
const ENV_EXAMPLE_FILES = ['.env.example', '.env.sample', '.env.template'];
|
|
1648
|
+
const ADR_DIRS = ['doc/adr', 'docs/adr', 'doc/decisions', 'docs/decisions', 'adr'];
|
|
1649
|
+
function extractAdditionalBrownfieldSeeds(cwd, target) {
|
|
1650
|
+
const seeds = [];
|
|
1651
|
+
const sources = [];
|
|
1652
|
+
// CI/CD workflows
|
|
1653
|
+
for (const dir of CI_WORKFLOW_DIRS) {
|
|
1654
|
+
const fullPath = path.join(cwd, dir);
|
|
1655
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
|
1656
|
+
sources.push('ci_workflows');
|
|
1657
|
+
try {
|
|
1658
|
+
const files = fs.readdirSync(fullPath).filter((f) => f.endsWith('.yml') || f.endsWith('.yaml'));
|
|
1659
|
+
if (files.length > 0) {
|
|
1660
|
+
seeds.push(createSeed({
|
|
1661
|
+
text: `CI/CD: ${files.length} workflow(s) in ${dir}/`,
|
|
1662
|
+
seedKind: 'convention',
|
|
1663
|
+
sourceKind: 'ci_config',
|
|
1664
|
+
sourceRef: dir,
|
|
1665
|
+
confidence: 'medium',
|
|
1666
|
+
tags: ['bootstrap', 'ci'],
|
|
1667
|
+
relatedPaths: target ? [target] : undefined,
|
|
1668
|
+
}));
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
catch { /* skip unreadable */ }
|
|
1672
|
+
break;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
for (const file of CI_FILES) {
|
|
1676
|
+
if (fs.existsSync(path.join(cwd, file))) {
|
|
1677
|
+
if (!sources.includes('ci_workflows'))
|
|
1678
|
+
sources.push('ci_config');
|
|
1679
|
+
seeds.push(createSeed({
|
|
1680
|
+
text: `CI/CD config: ${file}`,
|
|
1681
|
+
seedKind: 'convention',
|
|
1682
|
+
sourceKind: 'ci_config',
|
|
1683
|
+
sourceRef: file,
|
|
1684
|
+
confidence: 'medium',
|
|
1685
|
+
tags: ['bootstrap', 'ci'],
|
|
1686
|
+
}));
|
|
1687
|
+
break;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
// CONTRIBUTING.md
|
|
1691
|
+
const contributingPath = findFirstExisting(cwd, CONTRIBUTING_FILES);
|
|
1692
|
+
if (contributingPath) {
|
|
1693
|
+
sources.push('contributing');
|
|
1694
|
+
seeds.push(createSeed({
|
|
1695
|
+
text: `Contributing guide found: ${path.basename(contributingPath)}`,
|
|
1696
|
+
seedKind: 'convention',
|
|
1697
|
+
sourceKind: 'contributing',
|
|
1698
|
+
sourceRef: path.basename(contributingPath),
|
|
1699
|
+
confidence: 'medium',
|
|
1700
|
+
tags: ['bootstrap', 'contributing'],
|
|
1701
|
+
}));
|
|
1702
|
+
}
|
|
1703
|
+
// CHANGELOG
|
|
1704
|
+
const changelogPath = findFirstExisting(cwd, CHANGELOG_FILES);
|
|
1705
|
+
if (changelogPath) {
|
|
1706
|
+
sources.push('changelog');
|
|
1707
|
+
seeds.push(createSeed({
|
|
1708
|
+
text: `Changelog found: ${path.basename(changelogPath)}`,
|
|
1709
|
+
seedKind: 'convention',
|
|
1710
|
+
sourceKind: 'changelog',
|
|
1711
|
+
sourceRef: path.basename(changelogPath),
|
|
1712
|
+
confidence: 'low',
|
|
1713
|
+
tags: ['bootstrap', 'changelog'],
|
|
1714
|
+
}));
|
|
1715
|
+
}
|
|
1716
|
+
// Docker
|
|
1717
|
+
for (const file of DOCKER_FILES) {
|
|
1718
|
+
if (fs.existsSync(path.join(cwd, file))) {
|
|
1719
|
+
sources.push('docker');
|
|
1720
|
+
seeds.push(createSeed({
|
|
1721
|
+
text: `Docker config: ${file}`,
|
|
1722
|
+
seedKind: 'convention',
|
|
1723
|
+
sourceKind: 'docker',
|
|
1724
|
+
sourceRef: file,
|
|
1725
|
+
confidence: 'medium',
|
|
1726
|
+
tags: ['bootstrap', 'docker', 'infrastructure'],
|
|
1727
|
+
}));
|
|
1728
|
+
break;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
// .env.example
|
|
1732
|
+
const envPath = findFirstExisting(cwd, ENV_EXAMPLE_FILES);
|
|
1733
|
+
if (envPath) {
|
|
1734
|
+
sources.push('env_example');
|
|
1735
|
+
try {
|
|
1736
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
1737
|
+
const varCount = content.split(/\r?\n/).filter((l) => l.includes('=') && !l.startsWith('#')).length;
|
|
1738
|
+
seeds.push(createSeed({
|
|
1739
|
+
text: `Environment template: ${path.basename(envPath)} (${varCount} variables)`,
|
|
1740
|
+
seedKind: 'convention',
|
|
1741
|
+
sourceKind: 'env_example',
|
|
1742
|
+
sourceRef: path.basename(envPath),
|
|
1743
|
+
confidence: 'medium',
|
|
1744
|
+
tags: ['bootstrap', 'env', 'configuration'],
|
|
1745
|
+
}));
|
|
1746
|
+
}
|
|
1747
|
+
catch { /* skip unreadable */ }
|
|
1748
|
+
}
|
|
1749
|
+
// ADR (Architecture Decision Records)
|
|
1750
|
+
for (const dir of ADR_DIRS) {
|
|
1751
|
+
const fullPath = path.join(cwd, dir);
|
|
1752
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
|
1753
|
+
sources.push('adr');
|
|
1754
|
+
try {
|
|
1755
|
+
const files = fs.readdirSync(fullPath).filter((f) => f.endsWith('.md'));
|
|
1756
|
+
if (files.length > 0) {
|
|
1757
|
+
seeds.push(createSeed({
|
|
1758
|
+
text: `Architecture Decision Records: ${files.length} ADR(s) in ${dir}/`,
|
|
1759
|
+
seedKind: 'convention',
|
|
1760
|
+
sourceKind: 'adr',
|
|
1761
|
+
sourceRef: dir,
|
|
1762
|
+
confidence: 'high',
|
|
1763
|
+
tags: ['bootstrap', 'adr', 'architecture'],
|
|
1764
|
+
relatedPaths: target ? [target] : undefined,
|
|
1765
|
+
}));
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
catch { /* skip unreadable */ }
|
|
1769
|
+
break;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return { seeds, sources: [...new Set(sources)] };
|
|
1773
|
+
}
|
|
1597
1774
|
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { memoryExists } from './io.js';
|
|
6
|
+
const CLAUDE_DESKTOP_TOOLS = [
|
|
7
|
+
{ name: 'bclaw_session_start', description: 'Open a Brainclaw session and surface Claude-targeted tasks plus compact execution context.' },
|
|
8
|
+
{ name: 'bclaw_get_context', description: 'Retrieve ranked Brainclaw project context for the current task or path.' },
|
|
9
|
+
{ name: 'bclaw_list_surface_tasks', description: 'List queued or completed Brainclaw tasks delegated to Claude Desktop.' },
|
|
10
|
+
{ name: 'bclaw_update_surface_task', description: 'Mark a delegated Claude Desktop task as in progress or completed.' },
|
|
11
|
+
];
|
|
12
|
+
export function buildClaudeDesktopExtension(options = {}) {
|
|
13
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
14
|
+
if (!memoryExists(cwd)) {
|
|
15
|
+
throw new Error('Project memory not initialized. Run `brainclaw init` first.');
|
|
16
|
+
}
|
|
17
|
+
const workspaceDir = path.resolve(options.workspaceDir ?? path.join(cwd, 'internal-docs', 'desktop-extensions', 'claude-desktop-brainclaw'));
|
|
18
|
+
const outputFile = path.resolve(options.outputFile ?? path.join(cwd, 'internal-docs', 'desktop-extensions', 'brainclaw-claude-desktop.mcpb'));
|
|
19
|
+
if (outputFile.startsWith(`${workspaceDir}${path.sep}`) || outputFile === workspaceDir) {
|
|
20
|
+
throw new Error('The .mcpb output file must live outside the extension workspace directory.');
|
|
21
|
+
}
|
|
22
|
+
const projectRoot = path.resolve(options.projectRoot ?? cwd);
|
|
23
|
+
const runtimeRoot = path.resolve(options.runtimeRootOverride ?? resolveRuntimeRoot());
|
|
24
|
+
const packageRoot = path.resolve(options.packageRootOverride ?? findPackageRoot(runtimeRoot));
|
|
25
|
+
const metadata = readPackageMetadata(packageRoot);
|
|
26
|
+
const copiedDependencies = options.dependenciesOverride ?? resolveRuntimeDependencies(packageRoot);
|
|
27
|
+
fs.rmSync(workspaceDir, { recursive: true, force: true });
|
|
28
|
+
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
29
|
+
fs.cpSync(runtimeRoot, path.join(workspaceDir, 'runtime'), { recursive: true });
|
|
30
|
+
fs.mkdirSync(path.join(workspaceDir, 'server'), { recursive: true });
|
|
31
|
+
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
32
|
+
for (const dep of copiedDependencies) {
|
|
33
|
+
const sourceDir = path.join(packageRoot, 'node_modules', dep);
|
|
34
|
+
if (!fs.existsSync(sourceDir)) {
|
|
35
|
+
throw new Error(`Missing runtime dependency for Claude Desktop extension: ${dep}`);
|
|
36
|
+
}
|
|
37
|
+
fs.cpSync(sourceDir, path.join(workspaceDir, 'node_modules', dep), { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
const version = metadata.version ?? '0.0.0';
|
|
40
|
+
const manifestPath = path.join(workspaceDir, 'manifest.json');
|
|
41
|
+
const entryPointPath = path.join(workspaceDir, 'server', 'index.js');
|
|
42
|
+
const packageJsonPath = path.join(workspaceDir, 'package.json');
|
|
43
|
+
fs.writeFileSync(entryPointPath, buildServerEntryPoint(), 'utf-8');
|
|
44
|
+
fs.writeFileSync(packageJsonPath, `${JSON.stringify({
|
|
45
|
+
name: 'brainclaw-claude-desktop-extension',
|
|
46
|
+
private: true,
|
|
47
|
+
type: 'module',
|
|
48
|
+
version,
|
|
49
|
+
}, null, 2)}\n`, 'utf-8');
|
|
50
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(buildManifest(metadata, version, projectRoot), null, 2)}\n`, 'utf-8');
|
|
51
|
+
const packed = options.pack !== false ? packClaudeDesktopExtension(workspaceDir, outputFile) : false;
|
|
52
|
+
return {
|
|
53
|
+
workspaceDir,
|
|
54
|
+
outputFile,
|
|
55
|
+
packed,
|
|
56
|
+
manifestPath,
|
|
57
|
+
entryPointPath,
|
|
58
|
+
packageRoot,
|
|
59
|
+
runtimeRoot,
|
|
60
|
+
projectRoot,
|
|
61
|
+
copiedDependencies,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function buildServerEntryPoint() {
|
|
65
|
+
return `import process from 'node:process';
|
|
66
|
+
|
|
67
|
+
const projectRoot = process.env.BRAINCLAW_PROJECT_ROOT?.trim();
|
|
68
|
+
if (projectRoot) {
|
|
69
|
+
process.chdir(projectRoot);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const { runMcp } = await import('../runtime/commands/mcp.js');
|
|
73
|
+
runMcp();
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
function buildManifest(metadata, version, projectRoot) {
|
|
77
|
+
return {
|
|
78
|
+
manifest_version: '0.3',
|
|
79
|
+
name: 'brainclaw-claude-desktop',
|
|
80
|
+
display_name: 'Brainclaw Project Memory',
|
|
81
|
+
version,
|
|
82
|
+
description: 'Brainclaw local project memory and delegated task inbox for Claude Desktop.',
|
|
83
|
+
author: {
|
|
84
|
+
name: 'Brainclaw',
|
|
85
|
+
...(metadata.homepage ? { url: metadata.homepage } : {}),
|
|
86
|
+
},
|
|
87
|
+
...(typeof metadata.repository === 'string'
|
|
88
|
+
? { repository: { type: 'git', url: metadata.repository } }
|
|
89
|
+
: metadata.repository
|
|
90
|
+
? { repository: metadata.repository }
|
|
91
|
+
: {}),
|
|
92
|
+
...(metadata.homepage ? { homepage: metadata.homepage } : {}),
|
|
93
|
+
server: {
|
|
94
|
+
type: 'node',
|
|
95
|
+
entry_point: 'server/index.js',
|
|
96
|
+
mcp_config: {
|
|
97
|
+
command: 'node',
|
|
98
|
+
args: ['${__dirname}/server/index.js'],
|
|
99
|
+
env: {
|
|
100
|
+
BRAINCLAW_PROJECT_ROOT: '${user_config.project_root}',
|
|
101
|
+
BRAINCLAW_SKIP_SETUP_REQUIREMENT: '1',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
tools: CLAUDE_DESKTOP_TOOLS,
|
|
106
|
+
compatibility: {
|
|
107
|
+
platforms: ['win32', 'darwin'],
|
|
108
|
+
runtimes: {
|
|
109
|
+
node: '>=20.0.0',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
user_config: {
|
|
113
|
+
project_root: {
|
|
114
|
+
type: 'directory',
|
|
115
|
+
title: 'Project Root',
|
|
116
|
+
description: 'Brainclaw project root that Claude Desktop should operate on.',
|
|
117
|
+
required: true,
|
|
118
|
+
default: projectRoot,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function resolveRuntimeRoot() {
|
|
124
|
+
return path.resolve(fileURLToPath(new URL('..', import.meta.url)));
|
|
125
|
+
}
|
|
126
|
+
function findPackageRoot(startDir) {
|
|
127
|
+
let current = startDir;
|
|
128
|
+
while (true) {
|
|
129
|
+
const candidate = path.join(current, 'package.json');
|
|
130
|
+
if (fs.existsSync(candidate)) {
|
|
131
|
+
return current;
|
|
132
|
+
}
|
|
133
|
+
const parent = path.dirname(current);
|
|
134
|
+
if (parent === current) {
|
|
135
|
+
throw new Error(`Could not locate package.json from ${startDir}`);
|
|
136
|
+
}
|
|
137
|
+
current = parent;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function readPackageMetadata(packageRoot) {
|
|
141
|
+
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
142
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
143
|
+
}
|
|
144
|
+
function resolveRuntimeDependencies(packageRoot) {
|
|
145
|
+
const metadata = readPackageMetadata(packageRoot);
|
|
146
|
+
return Object.keys(metadata.dependencies ?? {});
|
|
147
|
+
}
|
|
148
|
+
function packClaudeDesktopExtension(workspaceDir, outputFile) {
|
|
149
|
+
fs.rmSync(outputFile, { force: true });
|
|
150
|
+
const pythonCommand = resolveAvailableCommand(process.platform === 'win32'
|
|
151
|
+
? ['python', 'py', 'python3']
|
|
152
|
+
: ['python3', 'python', 'py']);
|
|
153
|
+
if (pythonCommand) {
|
|
154
|
+
const script = [
|
|
155
|
+
'import os, sys, zipfile',
|
|
156
|
+
'src, dest = sys.argv[1], sys.argv[2]',
|
|
157
|
+
'with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as zf:',
|
|
158
|
+
' for root, _, files in os.walk(src):',
|
|
159
|
+
' for name in files:',
|
|
160
|
+
' full = os.path.join(root, name)',
|
|
161
|
+
' rel = os.path.relpath(full, src)',
|
|
162
|
+
' zf.write(full, rel)',
|
|
163
|
+
].join('; ');
|
|
164
|
+
const result = spawnSync(pythonCommand, ['-c', script, workspaceDir, outputFile], {
|
|
165
|
+
encoding: 'utf-8',
|
|
166
|
+
});
|
|
167
|
+
if (result.status === 0) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (process.platform === 'win32') {
|
|
172
|
+
const shell = resolveAvailableCommand(['pwsh', 'powershell']);
|
|
173
|
+
if (!shell) {
|
|
174
|
+
throw new Error('Could not find Python or PowerShell to create the Claude Desktop .mcpb archive.');
|
|
175
|
+
}
|
|
176
|
+
const command = `Compress-Archive -Path (Join-Path '${escapePowerShellPath(workspaceDir)}' '*') -DestinationPath '${escapePowerShellPath(outputFile)}' -Force`;
|
|
177
|
+
const result = spawnSync(shell, ['-NoProfile', '-Command', command], {
|
|
178
|
+
encoding: 'utf-8',
|
|
179
|
+
});
|
|
180
|
+
if (result.status === 0) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const zipCommand = resolveAvailableCommand(['zip']);
|
|
185
|
+
if (zipCommand) {
|
|
186
|
+
const result = spawnSync(zipCommand, ['-qr', outputFile, '.'], {
|
|
187
|
+
cwd: workspaceDir,
|
|
188
|
+
encoding: 'utf-8',
|
|
189
|
+
});
|
|
190
|
+
if (result.status === 0) {
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
throw new Error('Failed to create a .mcpb archive. Install Python 3, PowerShell, or zip.');
|
|
195
|
+
}
|
|
196
|
+
function resolveAvailableCommand(candidates) {
|
|
197
|
+
for (const candidate of candidates) {
|
|
198
|
+
const result = spawnSync(candidate, ['--version'], { encoding: 'utf-8' });
|
|
199
|
+
if (result.status === 0) {
|
|
200
|
+
return candidate;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
function escapePowerShellPath(value) {
|
|
206
|
+
return value.replace(/'/g, "''");
|
|
207
|
+
}
|
|
208
|
+
export function renderClaudeDesktopExtensionSummary(result) {
|
|
209
|
+
const lines = [
|
|
210
|
+
'Claude Desktop extension scaffold ready.',
|
|
211
|
+
`Workspace: ${path.relative(process.cwd(), result.workspaceDir) || result.workspaceDir}`,
|
|
212
|
+
`Manifest: ${path.relative(process.cwd(), result.manifestPath) || result.manifestPath}`,
|
|
213
|
+
`Server entry: ${path.relative(process.cwd(), result.entryPointPath) || result.entryPointPath}`,
|
|
214
|
+
];
|
|
215
|
+
if (result.packed) {
|
|
216
|
+
lines.push(`Package: ${path.relative(process.cwd(), result.outputFile) || result.outputFile}`);
|
|
217
|
+
lines.push('Install in Claude Desktop via Developer -> Extensions -> Install Extension.');
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
lines.push('Archive packing skipped (--no-pack).');
|
|
221
|
+
}
|
|
222
|
+
return lines.join('\n');
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=claude-desktop-extension.js.map
|
package/dist/core/context.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import { loadConfig } from './config.js';
|
|
2
3
|
import { resolveCrossProjectLinks, loadCrossProjectState } from './cross-project.js';
|
|
3
4
|
import { buildContextDiff } from './context-diff.js';
|
|
4
|
-
import { resolveStoreChain } from './store-resolution.js';
|
|
5
|
+
import { resolveContextStoreCwd, resolveStoreChain } from './store-resolution.js';
|
|
5
6
|
import { findAgentIdentityByName, resolveCurrentAgentIdentity } from './agent-registry.js';
|
|
6
7
|
import { hasReusableBootstrapProfile, runBootstrapProfile, selectDerivedSignals } from './bootstrap.js';
|
|
7
8
|
import { buildAgentToolingContext } from './agent-context.js';
|
|
@@ -18,26 +19,28 @@ import { isTrapActive, listOperationalTraps } from './traps.js';
|
|
|
18
19
|
import { buildEstimationReport } from '../commands/estimation-report.js';
|
|
19
20
|
export const CONTEXT_SCHEMA_VERSION = '1.2';
|
|
20
21
|
export function buildContext(options = {}) {
|
|
21
|
-
const
|
|
22
|
-
const
|
|
22
|
+
const requestedCwd = options.cwd ?? process.cwd();
|
|
23
|
+
const contextCwd = resolveContextStoreCwd(requestedCwd, options.target);
|
|
24
|
+
const state = loadState(contextCwd);
|
|
25
|
+
const config = loadConfig(contextCwd);
|
|
23
26
|
// Resolve parent stores for multi-store merge (walk-up from cwd)
|
|
24
|
-
const storeChain = resolveStoreChain(
|
|
27
|
+
const storeChain = resolveStoreChain(contextCwd);
|
|
25
28
|
const profile = options.profile ?? config.profile ?? 'dev';
|
|
26
29
|
const projectMode = config.project_mode ?? 'auto';
|
|
27
30
|
const projectStrategy = config.projects?.strategy ?? 'manual';
|
|
28
31
|
const currentHost = resolveCurrentHostId();
|
|
29
|
-
const memoryVersion = getVisibleMemoryVersion({ cwd:
|
|
30
|
-
const target = options.target
|
|
32
|
+
const memoryVersion = getVisibleMemoryVersion({ cwd: contextCwd, hostId: options.host, allHosts: options.allHosts });
|
|
33
|
+
const target = normalizeContextTarget(options.target, requestedCwd, contextCwd);
|
|
31
34
|
const project = options.project?.trim() || inferProjectFromTarget(target, config);
|
|
32
35
|
const agent = options.agent?.trim() || config.current_agent?.trim();
|
|
33
36
|
const currentAgentIdentity = agent
|
|
34
|
-
? (options.agent?.trim() ? findAgentIdentityByName(agent,
|
|
37
|
+
? (options.agent?.trim() ? findAgentIdentityByName(agent, contextCwd) : resolveCurrentAgentIdentity(contextCwd))
|
|
35
38
|
: undefined;
|
|
36
39
|
const profileMaxItems = { compact: 6, copilot: 5, quick: 3 };
|
|
37
40
|
const maxItems = options.maxItems ?? profileMaxItems[profile] ?? 8;
|
|
38
41
|
const maxChars = options.maxChars && options.maxChars > 0 ? options.maxChars : undefined;
|
|
39
42
|
// Instructions will be resolved after parent-store merge below (line ~460)
|
|
40
|
-
const rankingLookup = buildReputationRankingLookup(
|
|
43
|
+
const rankingLookup = buildReputationRankingLookup(contextCwd);
|
|
41
44
|
const profileSections = {
|
|
42
45
|
compact: ['plan', 'constraint'],
|
|
43
46
|
copilot: ['constraint', 'trap'],
|
|
@@ -123,7 +126,7 @@ export function buildContext(options = {}) {
|
|
|
123
126
|
},
|
|
124
127
|
});
|
|
125
128
|
}
|
|
126
|
-
for (const trap of listOperationalTraps({ hostId: options.host, includeAllHosts: options.allHosts },
|
|
129
|
+
for (const trap of listOperationalTraps({ hostId: options.host, includeAllHosts: options.allHosts }, contextCwd).filter((entry) => isTrapActive(entry))) {
|
|
127
130
|
items.push({
|
|
128
131
|
id: trap.id,
|
|
129
132
|
section: 'trap',
|
|
@@ -157,7 +160,7 @@ export function buildContext(options = {}) {
|
|
|
157
160
|
const runtimeNotes = listRuntimeNotes({
|
|
158
161
|
hostId: options.host,
|
|
159
162
|
includeAllHosts: options.allHosts,
|
|
160
|
-
},
|
|
163
|
+
}, contextCwd);
|
|
161
164
|
for (const note of runtimeNotes) {
|
|
162
165
|
if (project && note.project && note.project !== project) {
|
|
163
166
|
continue;
|
|
@@ -191,7 +194,7 @@ export function buildContext(options = {}) {
|
|
|
191
194
|
});
|
|
192
195
|
}
|
|
193
196
|
if (options.includePending) {
|
|
194
|
-
for (const p of listCandidates('pending',
|
|
197
|
+
for (const p of listCandidates('pending', contextCwd)) {
|
|
195
198
|
const meta = [`${p.type}`, `stars:${p.star_count ?? 0}`, `uses:${p.usage_count ?? 0}`];
|
|
196
199
|
if (p.author_id)
|
|
197
200
|
meta.push(`author_id:${p.author_id}`);
|
|
@@ -294,7 +297,7 @@ export function buildContext(options = {}) {
|
|
|
294
297
|
catch { /* non-fatal */ }
|
|
295
298
|
}
|
|
296
299
|
// Merge instructions from all stores in the chain
|
|
297
|
-
const allInstructions = [...loadInstructions(
|
|
300
|
+
const allInstructions = [...loadInstructions(contextCwd)];
|
|
298
301
|
for (const parentStore of storeChain.slice(1)) {
|
|
299
302
|
try {
|
|
300
303
|
const parentInstrs = loadInstructions(parentStore.cwd);
|
|
@@ -330,34 +333,34 @@ export function buildContext(options = {}) {
|
|
|
330
333
|
.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
|
|
331
334
|
.slice(0, maxItems);
|
|
332
335
|
const selected = maxChars ? applyCharBudget(ranked, maxChars) : ranked;
|
|
333
|
-
const resumeSummary = buildCurrentAgentResumeSummary(
|
|
336
|
+
const resumeSummary = buildCurrentAgentResumeSummary(contextCwd);
|
|
334
337
|
const scopedActivity = buildScopedActivity({
|
|
335
338
|
target,
|
|
336
339
|
project,
|
|
337
340
|
state,
|
|
338
341
|
runtimeNotes,
|
|
339
|
-
pendingCandidates: listCandidates('pending',
|
|
342
|
+
pendingCandidates: listCandidates('pending', contextCwd),
|
|
340
343
|
});
|
|
341
344
|
const memoryDensity = classifyMemoryDensity(selected.length);
|
|
342
345
|
const bootstrapEnabled = options.bootstrap !== false;
|
|
343
|
-
let bootstrapAvailable = hasReusableBootstrapProfile(target,
|
|
346
|
+
let bootstrapAvailable = hasReusableBootstrapProfile(target, contextCwd);
|
|
344
347
|
let derivedSignals;
|
|
345
348
|
if (bootstrapEnabled && (options.refreshBootstrap || memoryDensity === 'low')) {
|
|
346
349
|
const bootstrap = runBootstrapProfile({
|
|
347
350
|
target,
|
|
348
351
|
refresh: options.refreshBootstrap,
|
|
349
|
-
cwd:
|
|
352
|
+
cwd: contextCwd,
|
|
350
353
|
});
|
|
351
354
|
bootstrapAvailable = bootstrap.profile.seed_count > 0;
|
|
352
355
|
if (memoryDensity === 'low') {
|
|
353
|
-
const signals = selectDerivedSignals(target, 5,
|
|
356
|
+
const signals = selectDerivedSignals(target, 5, contextCwd);
|
|
354
357
|
if (signals.length > 0) {
|
|
355
358
|
derivedSignals = signals;
|
|
356
359
|
}
|
|
357
360
|
}
|
|
358
361
|
}
|
|
359
362
|
else if (bootstrapEnabled && bootstrapAvailable && memoryDensity === 'low') {
|
|
360
|
-
const signals = selectDerivedSignals(target, 5,
|
|
363
|
+
const signals = selectDerivedSignals(target, 5, contextCwd);
|
|
361
364
|
if (signals.length > 0) {
|
|
362
365
|
derivedSignals = signals;
|
|
363
366
|
}
|
|
@@ -365,7 +368,7 @@ export function buildContext(options = {}) {
|
|
|
365
368
|
const executionSensitive = isExecutionSensitiveTarget(target);
|
|
366
369
|
const derivedUsesExecution = derivedSignals?.some((signal) => signal.source_kind === 'machine') ?? false;
|
|
367
370
|
const derivedUsesTooling = derivedSignals?.some((signal) => signal.source_kind === 'skill' || signal.source_kind === 'mcp') ?? false;
|
|
368
|
-
const rawAgentTooling = buildAgentToolingContext({ cwd:
|
|
371
|
+
const rawAgentTooling = buildAgentToolingContext({ cwd: contextCwd });
|
|
369
372
|
const actionableAgentRules = rawAgentTooling.agents_rules.length > 0;
|
|
370
373
|
const blockingTooling = rawAgentTooling.mcp_servers.some((server) => server.availability === 'missing_command');
|
|
371
374
|
const shouldExposeExecution = memoryDensity === 'low' || executionSensitive || derivedUsesExecution;
|
|
@@ -375,7 +378,7 @@ export function buildContext(options = {}) {
|
|
|
375
378
|
|| actionableAgentRules
|
|
376
379
|
|| blockingTooling;
|
|
377
380
|
const executionContext = shouldExposeExecution
|
|
378
|
-
? compactExecutionContext(buildExecutionContext({ cwd:
|
|
381
|
+
? compactExecutionContext(buildExecutionContext({ cwd: contextCwd }))
|
|
379
382
|
: undefined;
|
|
380
383
|
const agentTooling = shouldExposeAgentTooling
|
|
381
384
|
? summariseAgentTooling(rawAgentTooling)
|
|
@@ -385,7 +388,7 @@ export function buildContext(options = {}) {
|
|
|
385
388
|
if (currentAgentIdentity || agent) {
|
|
386
389
|
const agentName = agent;
|
|
387
390
|
const agentId = currentAgentIdentity?.agent_id;
|
|
388
|
-
const allClaims = [...listClaims(
|
|
391
|
+
const allClaims = [...listClaims(contextCwd), ...parentStoreClaims];
|
|
389
392
|
const activeClaims = allClaims.filter((c) => c.status === 'active' && (agentId ? c.agent_id === agentId : c.agent === agentName));
|
|
390
393
|
const claimPlanIds = new Set(activeClaims.map((c) => c.plan_id).filter(Boolean));
|
|
391
394
|
const inProgressPlans = state.plan_items.filter((p) => p.status === 'in_progress' &&
|
|
@@ -399,7 +402,7 @@ export function buildContext(options = {}) {
|
|
|
399
402
|
}
|
|
400
403
|
// Cross-project items (subscriber links — read-only, always injected, bypass scoring)
|
|
401
404
|
const crossProjectItems = [];
|
|
402
|
-
for (const link of resolveCrossProjectLinks(
|
|
405
|
+
for (const link of resolveCrossProjectLinks(contextCwd)) {
|
|
403
406
|
if (!link.available)
|
|
404
407
|
continue;
|
|
405
408
|
try {
|
|
@@ -448,7 +451,7 @@ export function buildContext(options = {}) {
|
|
|
448
451
|
context_diff: options.sinceSession
|
|
449
452
|
? buildContextDiff({
|
|
450
453
|
session: options.sinceSession,
|
|
451
|
-
cwd:
|
|
454
|
+
cwd: contextCwd,
|
|
452
455
|
includeItems: true,
|
|
453
456
|
})
|
|
454
457
|
: undefined,
|
|
@@ -460,7 +463,7 @@ export function buildContext(options = {}) {
|
|
|
460
463
|
: undefined,
|
|
461
464
|
estimation_calibration: (() => {
|
|
462
465
|
try {
|
|
463
|
-
const report = buildEstimationReport({ agent, cwd:
|
|
466
|
+
const report = buildEstimationReport({ agent, cwd: contextCwd });
|
|
464
467
|
return report.summary.with_both >= 3 ? report.summary.calibration_hint : undefined;
|
|
465
468
|
}
|
|
466
469
|
catch {
|
|
@@ -1053,6 +1056,26 @@ function tokenise(input) {
|
|
|
1053
1056
|
.map((x) => x.trim())
|
|
1054
1057
|
.filter(Boolean);
|
|
1055
1058
|
}
|
|
1059
|
+
function normalizeContextTarget(target, requestedCwd, contextCwd) {
|
|
1060
|
+
const trimmed = target?.trim() ?? '';
|
|
1061
|
+
if (!trimmed) {
|
|
1062
|
+
return '';
|
|
1063
|
+
}
|
|
1064
|
+
if (path.resolve(requestedCwd) === path.resolve(contextCwd)) {
|
|
1065
|
+
return trimmed;
|
|
1066
|
+
}
|
|
1067
|
+
if (!(path.isAbsolute(trimmed) || trimmed.includes('/') || trimmed.includes('\\') || trimmed.startsWith('.'))) {
|
|
1068
|
+
return trimmed;
|
|
1069
|
+
}
|
|
1070
|
+
const absoluteTarget = path.isAbsolute(trimmed)
|
|
1071
|
+
? path.resolve(trimmed)
|
|
1072
|
+
: path.resolve(requestedCwd, trimmed);
|
|
1073
|
+
const relativeToContext = path.relative(contextCwd, absoluteTarget);
|
|
1074
|
+
if (relativeToContext.startsWith('..') || path.isAbsolute(relativeToContext)) {
|
|
1075
|
+
return trimmed;
|
|
1076
|
+
}
|
|
1077
|
+
return relativeToContext.split(path.sep).join('/');
|
|
1078
|
+
}
|
|
1056
1079
|
function matchesPath(pattern, target) {
|
|
1057
1080
|
if (pattern === target)
|
|
1058
1081
|
return true;
|