@geraldmaron/construct 1.0.21 → 1.0.24
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 +23 -7
- package/bin/construct +206 -228
- package/bin/construct-postinstall.mjs +25 -15
- package/lib/acp/server.mjs +113 -0
- package/lib/agent-instructions/inject.mjs +94 -0
- package/lib/auto-docs.mjs +10 -2
- package/lib/cli-commands.mjs +43 -15
- package/lib/comment-lint.mjs +115 -0
- package/lib/completions.mjs +7 -1
- package/lib/config/schema.mjs +4 -0
- package/lib/decisions/enforced-baseline.json +2 -0
- package/lib/docs-verify.mjs +15 -18
- package/lib/doctor/cli.mjs +8 -1
- package/lib/document-export.mjs +124 -0
- package/lib/embed/daemon.mjs +1 -1
- package/lib/embed/inbox.mjs +31 -10
- package/lib/embed/recommendation-store.mjs +7 -289
- package/lib/embed/reconcile.mjs +2 -2
- package/lib/features.mjs +11 -11
- package/lib/git-hooks-path.mjs +61 -0
- package/lib/home-namespace.mjs +60 -0
- package/lib/hooks/ci-status-check.mjs +62 -40
- package/lib/hooks/config-protection.mjs +4 -4
- package/lib/hooks/orchestration-dispatch-guard.mjs +153 -0
- package/lib/hooks/pre-push-gate.mjs +15 -6
- package/lib/hooks/session-reflect.mjs +5 -1
- package/lib/hooks/stop-notify.mjs +32 -17
- package/lib/hooks/stop-typecheck.mjs +7 -2
- package/lib/host-capabilities.mjs +24 -8
- package/lib/host-disposition.mjs +76 -0
- package/lib/ingest/provider-extract.mjs +1 -1
- package/lib/init-docs.mjs +1 -1
- package/lib/init-unified.mjs +320 -219
- package/lib/init-update.mjs +4 -84
- package/lib/init.mjs +9 -25
- package/lib/install/stage-project.mjs +8 -2
- package/lib/intake/git-queue.mjs +195 -0
- package/lib/intake/queue.mjs +9 -16
- package/lib/intent-classifier.mjs +1 -1
- package/lib/knowledge/search.mjs +52 -3
- package/lib/mcp/server.mjs +57 -14
- package/lib/mcp/tools/memory.mjs +2 -2
- package/lib/mcp/tools/orchestration-run.mjs +125 -0
- package/lib/mcp/tools/storage.mjs +2 -3
- package/lib/mcp-catalog.json +3 -3
- package/lib/mcp-manager.mjs +59 -3
- package/lib/model-registry.mjs +40 -33
- package/lib/observation-store.mjs +39 -163
- package/lib/opencode-config.mjs +1 -1
- package/lib/orchestration/events.mjs +66 -0
- package/lib/orchestration/runtime.mjs +72 -10
- package/lib/orchestration/worker.mjs +69 -21
- package/lib/orchestration-policy.mjs +27 -3
- package/lib/parity.mjs +46 -24
- package/lib/policy/unified-gates.mjs +96 -0
- package/lib/project-init-shared.mjs +0 -173
- package/lib/reconcile/adapter-prune.mjs +105 -0
- package/lib/reconcile/agent-instructions-rewrap.mjs +98 -0
- package/lib/reconcile/gitignore-coverage.mjs +88 -0
- package/lib/reconcile/index.mjs +169 -0
- package/lib/reconcile/legacy-doctrine-strip.mjs +139 -0
- package/lib/reconcile/legacy-guide-decommit.mjs +67 -0
- package/lib/reconcile/legacy-skills-cleanup.mjs +200 -0
- package/lib/reconcile/mcp-entry-reconcile.mjs +142 -0
- package/lib/runtime/uv-bootstrap.mjs +27 -3
- package/lib/schema-infer.mjs +16 -2
- package/lib/server/csrf.mjs +14 -2
- package/lib/server/index.mjs +95 -0
- package/lib/service-manager.mjs +59 -252
- package/lib/setup-prompts.mjs +2 -1
- package/lib/setup.mjs +104 -480
- package/lib/status.mjs +3 -6
- package/lib/storage/admin.mjs +48 -325
- package/lib/storage/backend.mjs +10 -43
- package/lib/storage/hybrid-query.mjs +15 -196
- package/lib/storage/sync.mjs +36 -177
- package/lib/storage/vector-client.mjs +256 -235
- package/lib/strategy-store.mjs +35 -286
- package/lib/template-registry.mjs +73 -0
- package/lib/term-format.mjs +75 -0
- package/lib/uninstall/uninstall.mjs +180 -7
- package/lib/worker/entrypoint.mjs +6 -14
- package/package.json +7 -6
- package/personas/construct.md +7 -8
- package/platforms/claude/settings.template.json +30 -11
- package/platforms/opencode/config.template.json +2 -2
- package/rules/common/neurodivergent-output.md +66 -0
- package/rules/common/tool-invisibility.md +37 -0
- package/scripts/sync-specialists.mjs +427 -107
- package/skills/operating/orchestration-reference.md +2 -16
- package/specialists/policy-inventory.json +14 -0
- package/specialists/prompts/cx-accessibility.md +2 -6
- package/specialists/prompts/cx-ai-engineer.md +0 -4
- package/specialists/prompts/cx-architect.md +3 -5
- package/specialists/prompts/cx-business-strategist.md +0 -5
- package/specialists/prompts/cx-data-analyst.md +0 -4
- package/specialists/prompts/cx-data-engineer.md +0 -4
- package/specialists/prompts/cx-debugger.md +2 -6
- package/specialists/prompts/cx-designer.md +0 -8
- package/specialists/prompts/cx-devil-advocate.md +2 -2
- package/specialists/prompts/cx-docs-keeper.md +0 -13
- package/specialists/prompts/cx-engineer.md +0 -13
- package/specialists/prompts/cx-evaluator.md +2 -2
- package/specialists/prompts/cx-explorer.md +4 -5
- package/specialists/prompts/cx-legal-compliance.md +4 -5
- package/specialists/prompts/cx-operations.md +0 -5
- package/specialists/prompts/cx-orchestrator.md +0 -4
- package/specialists/prompts/cx-platform-engineer.md +0 -8
- package/specialists/prompts/cx-product-manager.md +0 -8
- package/specialists/prompts/cx-qa.md +3 -12
- package/specialists/prompts/cx-rd-lead.md +0 -5
- package/specialists/prompts/cx-release-manager.md +0 -8
- package/specialists/prompts/cx-researcher.md +5 -29
- package/specialists/prompts/cx-reviewer.md +2 -6
- package/specialists/prompts/cx-security.md +2 -11
- package/specialists/prompts/cx-sre.md +0 -15
- package/specialists/prompts/cx-test-automation.md +0 -4
- package/specialists/prompts/cx-trace-reviewer.md +2 -2
- package/specialists/prompts/cx-ux-researcher.md +0 -4
- package/specialists/registry.json +28 -36
- package/templates/distribution/run.mjs +36 -7
- package/templates/docs/accessibility-audit.md +56 -0
- package/templates/docs/architecture-review.md +59 -0
- package/templates/docs/code-review-report.md +46 -0
- package/templates/docs/construct_guide.md +15 -15
- package/templates/docs/debug-investigation.md +53 -0
- package/templates/docs/qa-report.md +48 -0
- package/templates/docs/security-audit-report.md +48 -0
- package/templates/docs/task-packet.md +49 -0
- package/templates/docs/verdict.md +40 -0
- package/db/schema/001_init.sql +0 -40
- package/db/schema/002_pgvector.sql +0 -182
- package/db/schema/003_intake.sql +0 -47
- package/db/schema/003_observation_reconciliation.sql +0 -14
- package/db/schema/004_recommendations.sql +0 -46
- package/db/schema/005_strategy.sql +0 -21
- package/db/schema/006_graph.sql +0 -24
- package/db/schema/007_tags.sql +0 -30
- package/db/schema/008_skill_usage.sql +0 -24
- package/db/schema/009_scheduler.sql +0 -14
- package/db/schema/010_cx_scores.sql +0 -51
- package/lib/intake/postgres-queue.mjs +0 -240
- package/lib/server/static/index.html +0 -1
- package/lib/services/local-postgres.mjs +0 -15
- package/lib/storage/backup.mjs +0 -347
- package/lib/storage/migrations.mjs +0 -187
- package/lib/storage/postgres-backup.mjs +0 -124
- package/lib/storage/sql-store.mjs +0 -45
- package/lib/storage/store-version.mjs +0 -115
- package/lib/storage/vector-store.mjs +0 -100
package/lib/init-update.mjs
CHANGED
|
@@ -21,38 +21,6 @@ const dryRun = args.includes("--dry-run");
|
|
|
21
21
|
const cwdArg = args.find((arg) => arg.startsWith("--cwd="));
|
|
22
22
|
const cwd = cwdArg ? path.resolve(cwdArg.split("=")[1]) : process.cwd();
|
|
23
23
|
|
|
24
|
-
const REQUIRED_AGENT_SECTIONS = [
|
|
25
|
-
"Operating hierarchy",
|
|
26
|
-
"Start-of-session rules",
|
|
27
|
-
"Maintenance rules",
|
|
28
|
-
"End-of-session rules",
|
|
29
|
-
"Verification rules",
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
async function loadAgentsTemplate(projectName) {
|
|
33
|
-
const modulePath = path.join(import.meta.dirname, "project-init-shared.mjs");
|
|
34
|
-
const module = await import(modulePath);
|
|
35
|
-
if (!module.buildAgentsGuide) {
|
|
36
|
-
throw new Error("buildAgentsGuide not available from project-init-shared.mjs");
|
|
37
|
-
}
|
|
38
|
-
return module.buildAgentsGuide(projectName);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function inferProjectName(targetDir) {
|
|
42
|
-
const packagePath = path.join(targetDir, "package.json");
|
|
43
|
-
if (fs.existsSync(packagePath)) {
|
|
44
|
-
try {
|
|
45
|
-
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
46
|
-
if (pkg.name) return pkg.name;
|
|
47
|
-
} catch { /* fall through */ }
|
|
48
|
-
}
|
|
49
|
-
return path.basename(targetDir);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function findMissingAgentsSections(content) {
|
|
53
|
-
return REQUIRED_AGENT_SECTIONS.filter((section) => !content.toLowerCase().includes(section.toLowerCase()));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
24
|
function ensureProposalDir(targetDir) {
|
|
57
25
|
const dir = path.join(targetDir, ".cx", "proposals");
|
|
58
26
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -66,37 +34,6 @@ function writeProposal(targetDir, fileName, content) {
|
|
|
66
34
|
return proposalPath;
|
|
67
35
|
}
|
|
68
36
|
|
|
69
|
-
function buildAgentsProposal(existingContent, template, missingSections) {
|
|
70
|
-
const missingList = missingSections.map((section) => `- ${section}`).join("\n");
|
|
71
|
-
return [
|
|
72
|
-
"<!--",
|
|
73
|
-
"Construct init:update proposal",
|
|
74
|
-
"Review this file and merge the needed sections into AGENTS.md manually.",
|
|
75
|
-
"The original AGENTS.md was not modified.",
|
|
76
|
-
"-->",
|
|
77
|
-
"",
|
|
78
|
-
"# Proposed AGENTS.md Update",
|
|
79
|
-
"",
|
|
80
|
-
"## Why this proposal exists",
|
|
81
|
-
"",
|
|
82
|
-
"Your current `AGENTS.md` is missing these required sections:",
|
|
83
|
-
missingList,
|
|
84
|
-
"",
|
|
85
|
-
"## Current AGENTS.md",
|
|
86
|
-
"",
|
|
87
|
-
"```md",
|
|
88
|
-
existingContent.trimEnd(),
|
|
89
|
-
"```",
|
|
90
|
-
"",
|
|
91
|
-
"## Current Construct template",
|
|
92
|
-
"",
|
|
93
|
-
"```md",
|
|
94
|
-
template.trimEnd(),
|
|
95
|
-
"```",
|
|
96
|
-
"",
|
|
97
|
-
].join("\n");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
37
|
function buildWorkflowProposal(existingContent) {
|
|
101
38
|
const updatedContent = existingContent.replace(
|
|
102
39
|
/(\s+- run: node bin\/construct doctor\s*\n)/,
|
|
@@ -207,29 +144,12 @@ export function resolveTemplateConflict(conflict, resolution, targetDir) {
|
|
|
207
144
|
async function main() {
|
|
208
145
|
console.log(`Preparing Construct standards update for ${cwd}`);
|
|
209
146
|
|
|
210
|
-
const agentsPath = path.join(cwd, "AGENTS.md");
|
|
211
|
-
if (!fs.existsSync(agentsPath)) {
|
|
212
|
-
console.error("AGENTS.md not found. Run `construct init` first.");
|
|
213
|
-
process.exit(1);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const projectName = inferProjectName(cwd);
|
|
217
|
-
const existingAgents = fs.readFileSync(agentsPath, "utf8");
|
|
218
|
-
const template = await loadAgentsTemplate(projectName);
|
|
219
|
-
const missingSections = findMissingAgentsSections(existingAgents);
|
|
220
|
-
|
|
221
147
|
const planned = [];
|
|
222
148
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
write() {
|
|
228
|
-
return writeProposal(cwd, "AGENTS.md.construct-update.md", buildAgentsProposal(existingAgents, template, missingSections));
|
|
229
|
-
},
|
|
230
|
-
relativePath: proposalPath,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
149
|
+
// Construct's guidance in AGENTS.md/CLAUDE.md is owned by the versioned
|
|
150
|
+
// CONSTRUCT INTEGRATION marker block, kept current by `construct sync`
|
|
151
|
+
// (ADR-0027 §2/§4). init:update scope is opt-in standards a project owner
|
|
152
|
+
// merges by hand: CI checks and template conflicts.
|
|
233
153
|
|
|
234
154
|
const workflowPath = path.join(cwd, ".github", "workflows", "ci.yml");
|
|
235
155
|
if (fs.existsSync(workflowPath)) {
|
package/lib/init.mjs
CHANGED
|
@@ -15,7 +15,6 @@ import fs from "node:fs";
|
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
|
-
buildAgentsGuide,
|
|
19
18
|
buildContextJson,
|
|
20
19
|
buildContextMarkdown,
|
|
21
20
|
buildPlanTemplate,
|
|
@@ -43,14 +42,9 @@ function inferProjectName(targetPath) {
|
|
|
43
42
|
function main() {
|
|
44
43
|
const projectName = inferProjectName(target);
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
skipped,
|
|
50
|
-
filePath: path.join(target, "AGENTS.md"),
|
|
51
|
-
content: buildAgentsGuide(projectName),
|
|
52
|
-
generator: "construct/init",
|
|
53
|
-
});
|
|
45
|
+
// AGENTS.md is user-owned (ADR-0027 §2); Construct contributes only the fenced
|
|
46
|
+
// integration block via injectIntoAgentFile, never a doctrine body here.
|
|
47
|
+
|
|
54
48
|
writeStampedIfMissing({
|
|
55
49
|
targetRoot: target,
|
|
56
50
|
created,
|
|
@@ -75,22 +69,12 @@ function main() {
|
|
|
75
69
|
content: buildContextMarkdown(),
|
|
76
70
|
generator: "construct/init",
|
|
77
71
|
});
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
generator: "construct/init",
|
|
85
|
-
});
|
|
86
|
-
writeStampedIfMissing({
|
|
87
|
-
targetRoot: target,
|
|
88
|
-
created,
|
|
89
|
-
skipped,
|
|
90
|
-
filePath: path.join(target, ".cx", "inbox", ".gitkeep"),
|
|
91
|
-
content: "",
|
|
92
|
-
generator: "construct/init",
|
|
93
|
-
});
|
|
72
|
+
// `.cx/` already exists from the context writes above and is gitignored in full
|
|
73
|
+
// (ADR-0027), so a keep file there could never be tracked; `.cx/inbox/` is the local
|
|
74
|
+
// intake drop zone the watcher polls. Create the directories; write no dead keep file.
|
|
75
|
+
|
|
76
|
+
fs.mkdirSync(path.join(target, ".cx", "inbox"), { recursive: true });
|
|
77
|
+
created.push(".cx/inbox/");
|
|
94
78
|
|
|
95
79
|
process.stdout.write(`\nConstruct init → ${target}\n\n`);
|
|
96
80
|
if (created.length) {
|
|
@@ -14,7 +14,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
14
14
|
import { existsSync, copyFileSync, writeFileSync, mkdirSync, chmodSync } from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
|
|
17
|
-
export function stageProjectAdapters({ projectRoot, packageRoot, pkgVersion, log }) {
|
|
17
|
+
export function stageProjectAdapters({ projectRoot, packageRoot, pkgVersion, log, hosts = null }) {
|
|
18
18
|
if (!projectRoot) throw new Error('stageProjectAdapters: projectRoot is required');
|
|
19
19
|
if (!packageRoot) throw new Error('stageProjectAdapters: packageRoot is required');
|
|
20
20
|
const emit = typeof log === 'function' ? log : () => {};
|
|
@@ -30,8 +30,14 @@ export function stageProjectAdapters({ projectRoot, packageRoot, pkgVersion, log
|
|
|
30
30
|
return { staged: true, synced: false };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// A `hosts` array restricts which adapter sets sync writes (construct-4xy6);
|
|
34
|
+
// null/empty leaves sync writing every host, preserving the postinstall path.
|
|
35
|
+
|
|
36
|
+
const syncArgs = [syncScript, '--project'];
|
|
37
|
+
if (Array.isArray(hosts) && hosts.length > 0) syncArgs.push(`--hosts=${hosts.join(',')}`);
|
|
38
|
+
|
|
33
39
|
emit(`syncing project adapters into ${projectRoot}/.claude/`);
|
|
34
|
-
const result = spawnSync(process.execPath,
|
|
40
|
+
const result = spawnSync(process.execPath, syncArgs, {
|
|
35
41
|
cwd: projectRoot,
|
|
36
42
|
stdio: 'inherit',
|
|
37
43
|
env: { ...process.env, CONSTRUCT_PROJECT_ROOT: projectRoot },
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/intake/git-queue.mjs — Git-backed adapter for the IntakeQueue interface.
|
|
3
|
+
*
|
|
4
|
+
* This implementation replaces the PostgresIntakeQueue for team/enterprise
|
|
5
|
+
* modes. It uses the filesystem and Git for state synchronization and
|
|
6
|
+
* conflict resolution, removing the dependency on a central database server.
|
|
7
|
+
*
|
|
8
|
+
* Structure:
|
|
9
|
+
* .cx/team-inbox/
|
|
10
|
+
* pending/ - JSON files for unclaimed tasks
|
|
11
|
+
* claimed/ - Subdirectories per worker containing claimed task files
|
|
12
|
+
* processed/ - Completed tasks
|
|
13
|
+
* skipped/ - Skipped tasks
|
|
14
|
+
* quarantine/ - Tasks needing human review
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import { execSync } from 'node:child_process';
|
|
20
|
+
import { shouldQuarantine } from './quarantine.mjs';
|
|
21
|
+
|
|
22
|
+
function slugify(value) {
|
|
23
|
+
return String(value || 'untitled')
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
26
|
+
.replace(/^-+|-+$/g, '')
|
|
27
|
+
.slice(0, 60) || 'untitled';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let counter = 0;
|
|
31
|
+
function timestamp() {
|
|
32
|
+
counter = (counter + 1) % 1000;
|
|
33
|
+
const c = String(counter).padStart(3, '0');
|
|
34
|
+
return `${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 23)}-${c}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class GitIntakeQueue {
|
|
38
|
+
constructor({ project, rootDir = process.cwd() } = {}) {
|
|
39
|
+
this.project = project;
|
|
40
|
+
this.inboxRoot = path.join(rootDir, '.cx', 'team-inbox');
|
|
41
|
+
this._ensureDirs();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_ensureDirs() {
|
|
45
|
+
['pending', 'claimed', 'processed', 'skipped', 'quarantine'].forEach(dir => {
|
|
46
|
+
fs.mkdirSync(path.join(this.inboxRoot, dir), { recursive: true });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_gitAddAndCommit(filePath, message) {
|
|
51
|
+
try {
|
|
52
|
+
execSync(`git add "${filePath}"`, { stdio: 'ignore' });
|
|
53
|
+
execSync(`git commit -m "${message}"`, { stdio: 'ignore' });
|
|
54
|
+
} catch (err) {
|
|
55
|
+
// It's okay if commit fails because of no changes
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async enqueue(entry) {
|
|
60
|
+
const ts = timestamp();
|
|
61
|
+
const slug = slugify(path.basename(entry.intake.sourcePath, path.extname(entry.intake.sourcePath)));
|
|
62
|
+
const id = `${ts}-${slug}`;
|
|
63
|
+
const triage = entry.triage || {};
|
|
64
|
+
|
|
65
|
+
const quarantineDecision = shouldQuarantine(triage);
|
|
66
|
+
const subDir = quarantineDecision.quarantine ? 'quarantine' : 'pending';
|
|
67
|
+
const filePath = path.join(this.inboxRoot, subDir, `${id}.json`);
|
|
68
|
+
|
|
69
|
+
const data = {
|
|
70
|
+
id,
|
|
71
|
+
project: this.project,
|
|
72
|
+
status: subDir === 'quarantine' ? 'quarantined' : 'pending',
|
|
73
|
+
createdAt: new Date().toISOString(),
|
|
74
|
+
...entry
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
78
|
+
this._gitAddAndCommit(filePath, `Enqueue task ${id}`);
|
|
79
|
+
|
|
80
|
+
return { id, route: subDir === 'quarantine' ? 'quarantine' : 'pending', reason: quarantineDecision.reason };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async listPending({ limit = 100 } = {}) {
|
|
84
|
+
const pendingDir = path.join(this.inboxRoot, 'pending');
|
|
85
|
+
return fs.readdirSync(pendingDir)
|
|
86
|
+
.filter(f => f.endsWith('.json'))
|
|
87
|
+
.slice(0, limit)
|
|
88
|
+
.map(f => JSON.parse(fs.readFileSync(path.join(pendingDir, f), 'utf8')));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async count() {
|
|
92
|
+
const pendingDir = path.join(this.inboxRoot, 'pending');
|
|
93
|
+
return fs.readdirSync(pendingDir).filter(f => f.endsWith('.json')).length;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async read(id) {
|
|
97
|
+
for (const dir of ['pending', 'claimed', 'processed', 'skipped', 'quarantine']) {
|
|
98
|
+
// Note: 'claimed' has subdirs, we'd need to search recursively for claimed.
|
|
99
|
+
// For simplicity in this implementation, we assume we know where it is or search pending first.
|
|
100
|
+
const searchPath = dir === 'claimed'
|
|
101
|
+
? path.join(this.inboxRoot, dir) // needs recursive search
|
|
102
|
+
: path.join(this.inboxRoot, dir, `${id}.json`);
|
|
103
|
+
|
|
104
|
+
if (dir !== 'claimed' && fs.existsSync(searchPath)) {
|
|
105
|
+
return JSON.parse(fs.readFileSync(searchPath, 'utf8'));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async claim({ claimedBy }) {
|
|
112
|
+
if (!claimedBy) throw new Error('claim: claimedBy is required');
|
|
113
|
+
|
|
114
|
+
// 1. Pull latest to minimize conflicts
|
|
115
|
+
try { execSync('git pull --rebase', { stdio: 'ignore' }); } catch (e) {}
|
|
116
|
+
|
|
117
|
+
const pendingDir = path.join(this.inboxRoot, 'pending');
|
|
118
|
+
const files = fs.readdirSync(pendingDir).filter(f => f.endsWith('.json')).sort();
|
|
119
|
+
|
|
120
|
+
if (files.length === 0) return null;
|
|
121
|
+
|
|
122
|
+
const fileName = files[0];
|
|
123
|
+
const pendingPath = path.join(pendingDir, fileName);
|
|
124
|
+
const workerDir = path.join(this.inboxRoot, 'claimed', claimedBy);
|
|
125
|
+
fs.mkdirSync(workerDir, { recursive: true });
|
|
126
|
+
const claimedPath = path.join(workerDir, fileName);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
// Atomic move on filesystem
|
|
130
|
+
fs.renameSync(pendingPath, claimedPath);
|
|
131
|
+
|
|
132
|
+
// Update metadata
|
|
133
|
+
const data = JSON.parse(fs.readFileSync(claimedPath, 'utf8'));
|
|
134
|
+
data.status = 'claimed';
|
|
135
|
+
data.claimedBy = claimedBy;
|
|
136
|
+
data.claimedAt = new Date().toISOString();
|
|
137
|
+
fs.writeFileSync(claimedPath, JSON.stringify(data, null, 2));
|
|
138
|
+
|
|
139
|
+
// 2. Commit and Push
|
|
140
|
+
execSync(`git add .cx/team-inbox/pending/${fileName} .cx/team-inbox/claimed/${claimedBy}/${fileName}`, { stdio: 'ignore' });
|
|
141
|
+
execSync(`git commit -m "Claim task ${data.id} by ${claimedBy}"`, { stdio: 'ignore' });
|
|
142
|
+
execSync('git push', { stdio: 'ignore' });
|
|
143
|
+
|
|
144
|
+
return data;
|
|
145
|
+
} catch (err) {
|
|
146
|
+
// If push fails, someone else probably claimed it first or Git is out of sync.
|
|
147
|
+
// We should ideally rollback the local move, but for this first pass
|
|
148
|
+
// we'll let the next 'git pull --rebase' fix the state.
|
|
149
|
+
console.error(`Failed to claim ${fileName}: ${err.message}`);
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async markProcessed(id, { processedBy = 'unknown', notes = '' } = {}) {
|
|
155
|
+
// Locate the file (might be in claimed/<worker>/<id>.json or pending/<id>.json)
|
|
156
|
+
// For simplicity, search all subdirs.
|
|
157
|
+
let foundPath = null;
|
|
158
|
+
let currentDir = null;
|
|
159
|
+
|
|
160
|
+
const dirs = ['pending', 'claimed']; // only mark as processed if pending or claimed
|
|
161
|
+
for (const d of dirs) {
|
|
162
|
+
const dirPath = path.join(this.inboxRoot, d);
|
|
163
|
+
if (d === 'claimed') {
|
|
164
|
+
const workers = fs.readdirSync(dirPath);
|
|
165
|
+
for (const w of workers) {
|
|
166
|
+
const p = path.join(dirPath, w, `${id}.json`);
|
|
167
|
+
if (fs.existsSync(p)) { foundPath = p; currentDir = path.join(d, w); break; }
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
const p = path.join(dirPath, `${id}.json`);
|
|
171
|
+
if (fs.existsSync(p)) { foundPath = p; currentDir = d; break; }
|
|
172
|
+
}
|
|
173
|
+
if (foundPath) break;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!foundPath) throw new Error(`markProcessed: no entry ${id} found`);
|
|
177
|
+
|
|
178
|
+
const data = JSON.parse(fs.readFileSync(foundPath, 'utf8'));
|
|
179
|
+
data.status = 'processed';
|
|
180
|
+
data.processedBy = processedBy;
|
|
181
|
+
data.processedAt = new Date().toISOString();
|
|
182
|
+
data.notes = notes;
|
|
183
|
+
|
|
184
|
+
const processedPath = path.join(this.inboxRoot, 'processed', `${id}.json`);
|
|
185
|
+
fs.renameSync(foundPath, processedPath);
|
|
186
|
+
fs.writeFileSync(processedPath, JSON.stringify(data, null, 2));
|
|
187
|
+
|
|
188
|
+
this._gitAddAndCommit(this.inboxRoot, `Mark task ${id} as processed`);
|
|
189
|
+
try { execSync('git push', { stdio: 'ignore' }); } catch (e) {}
|
|
190
|
+
|
|
191
|
+
return { id };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// markSkipped and reopen would follow similar logic...
|
|
195
|
+
}
|
package/lib/intake/queue.mjs
CHANGED
|
@@ -36,11 +36,10 @@
|
|
|
36
36
|
import path from 'node:path';
|
|
37
37
|
|
|
38
38
|
import { FilesystemIntakeQueue, queueRoot, pendingDir, processedDir, skippedDir } from './filesystem-queue.mjs';
|
|
39
|
-
import {
|
|
39
|
+
import { GitIntakeQueue } from './git-queue.mjs';
|
|
40
40
|
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
41
|
-
import { createSqlClient } from '../storage/backend.mjs';
|
|
42
41
|
|
|
43
|
-
export { FilesystemIntakeQueue,
|
|
42
|
+
export { FilesystemIntakeQueue, GitIntakeQueue, queueRoot, pendingDir, processedDir, skippedDir };
|
|
44
43
|
|
|
45
44
|
export const INTAKE_QUEUE_BACKEND_ENV_KEY = 'CONSTRUCT_INTAKE_QUEUE_BACKEND';
|
|
46
45
|
export const INTAKE_PROJECT_ENV_KEY = 'CONSTRUCT_PROJECT_NAME';
|
|
@@ -48,9 +47,9 @@ export const INTAKE_TENANT_ENV_KEY = 'CONSTRUCT_TENANT_ID';
|
|
|
48
47
|
|
|
49
48
|
function resolveBackend(env) {
|
|
50
49
|
const override = env?.[INTAKE_QUEUE_BACKEND_ENV_KEY];
|
|
51
|
-
if (override === 'filesystem' || override === '
|
|
50
|
+
if (override === 'filesystem' || override === 'git') return override;
|
|
52
51
|
const mode = getDeploymentMode(env);
|
|
53
|
-
return mode === 'solo' ? 'filesystem' : '
|
|
52
|
+
return mode === 'solo' ? 'filesystem' : 'git';
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
function resolveProject(rootDir, env) {
|
|
@@ -62,22 +61,16 @@ function resolveProject(rootDir, env) {
|
|
|
62
61
|
/**
|
|
63
62
|
* Create an IntakeQueue instance for the given project root.
|
|
64
63
|
* Backend selection: CONSTRUCT_INTAKE_QUEUE_BACKEND override wins; otherwise
|
|
65
|
-
* solo mode → filesystem, team/enterprise →
|
|
66
|
-
* backend
|
|
67
|
-
* CONSTRUCT_PROJECT_NAME / CONSTRUCT_TENANT_ID to scope queries; an
|
|
68
|
-
* explicit `sql` and `project` in opts override both.
|
|
64
|
+
* solo mode → filesystem, team/enterprise → git. The git
|
|
65
|
+
* backend uses the filesystem and git for state synchronization.
|
|
69
66
|
*/
|
|
70
67
|
export function createIntakeQueue(rootDir, env = process.env, opts = {}) {
|
|
71
68
|
const backend = opts.backend || resolveBackend(env);
|
|
72
69
|
if (backend === 'filesystem') return new FilesystemIntakeQueue(rootDir);
|
|
73
|
-
if (backend === 'postgres') {
|
|
74
|
-
|
|
75
|
-
if (!sql) {
|
|
76
|
-
throw new Error('Postgres intake queue requires a configured DATABASE_URL (or sql client). Run `construct init` or set CONSTRUCT_INTAKE_QUEUE_BACKEND=filesystem.');
|
|
77
|
-
}
|
|
70
|
+
if (backend === 'git' || backend === 'postgres') {
|
|
71
|
+
// We treat 'postgres' as an alias for 'git' now to avoid breaking existing configs
|
|
78
72
|
const project = opts.project ?? resolveProject(rootDir, env);
|
|
79
|
-
|
|
80
|
-
return new PostgresIntakeQueue({ sql, project, tenantId });
|
|
73
|
+
return new GitIntakeQueue({ project, rootDir });
|
|
81
74
|
}
|
|
82
75
|
throw new Error(`Unknown intake queue backend: ${backend}`);
|
|
83
76
|
}
|
|
@@ -150,7 +150,7 @@ async function defaultModelCaller({ system, user }) {
|
|
|
150
150
|
headers: {
|
|
151
151
|
Authorization: `Bearer ${orKey}`,
|
|
152
152
|
'content-type': 'application/json',
|
|
153
|
-
'HTTP-Referer': 'https://github.com/construct',
|
|
153
|
+
'HTTP-Referer': 'https://github.com/geraldmaron/construct',
|
|
154
154
|
},
|
|
155
155
|
body: JSON.stringify({
|
|
156
156
|
model: modelId.replace(/^openrouter\//, ''),
|
package/lib/knowledge/search.mjs
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* 3. docs/README.md — docs index + contract
|
|
12
12
|
* 4. .cx/knowledge/ — operator-written internal docs
|
|
13
13
|
* 5. Any *.md in docs/cookbook/ — task-oriented recipes
|
|
14
|
+
* 6. <projectRoot>/.cx/knowledge/** — the cwd project's knowledge tree,
|
|
15
|
+
* including `external/research/` written by `construct knowledge add`,
|
|
16
|
+
* so foreign-project queries surface project content alongside bundled docs.
|
|
14
17
|
*
|
|
15
18
|
* Retrieval strategy:
|
|
16
19
|
* Token-based BM25-like scoring over 200-char chunks. Returns top-K chunks
|
|
@@ -30,8 +33,17 @@ const REPO_ROOT = join(MODULE_DIR, '..', '..');
|
|
|
30
33
|
|
|
31
34
|
// ─── Source catalogue ────────────────────────────────────────────────────────
|
|
32
35
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Priority-ordered list of source files to search.
|
|
38
|
+
*
|
|
39
|
+
* `projectRoot` covers the case where Construct is invoked from a foreign
|
|
40
|
+
* project (cwd != construct repo): the project's own `.cx/knowledge/**` —
|
|
41
|
+
* including the `external/research/` tree written by `construct knowledge add
|
|
42
|
+
* --source=research` — joins the source set, so freshly added project knowledge
|
|
43
|
+
* surfaces alongside (and ahead of) the bundled Construct docs. When
|
|
44
|
+
* projectRoot equals repoRoot or is absent, only the bundled set is searched.
|
|
45
|
+
*/
|
|
46
|
+
function buildSourceList(repoRoot = REPO_ROOT, projectRoot = null) {
|
|
35
47
|
const sources = [];
|
|
36
48
|
|
|
37
49
|
const priority = [
|
|
@@ -92,9 +104,45 @@ function buildSourceList(repoRoot = REPO_ROOT) {
|
|
|
92
104
|
}
|
|
93
105
|
}
|
|
94
106
|
|
|
107
|
+
// Project knowledge — the cwd project's `.cx/knowledge/**`. Searched only
|
|
108
|
+
// when the project is distinct from the construct repo (the latter's own
|
|
109
|
+
// .cx/knowledge is already covered above as repoRoot's internal knowledge).
|
|
110
|
+
// Priority 1 puts project content on the same scoring tier as core docs so a
|
|
111
|
+
// freshly added project finding outranks bundled framework material on tied
|
|
112
|
+
// matches; the `source: 'project'` tag lets consumers distinguish origin.
|
|
113
|
+
|
|
114
|
+
if (projectRoot && projectRoot !== repoRoot) {
|
|
115
|
+
const projectKnowledgeRoot = join(projectRoot, '.cx', 'knowledge');
|
|
116
|
+
if (existsSync(projectKnowledgeRoot)) {
|
|
117
|
+
for (const file of walkMarkdown(projectKnowledgeRoot)) {
|
|
118
|
+
sources.push({
|
|
119
|
+
path: file,
|
|
120
|
+
rel: relative(projectRoot, file),
|
|
121
|
+
priority: 1,
|
|
122
|
+
source: 'project',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
95
128
|
return sources;
|
|
96
129
|
}
|
|
97
130
|
|
|
131
|
+
// Recursive .md walk used for the project-knowledge tree, where `external/`
|
|
132
|
+
// nests one subdirectory deeper than the flat internal/reference/how-tos layout.
|
|
133
|
+
|
|
134
|
+
function walkMarkdown(root) {
|
|
135
|
+
const out = [];
|
|
136
|
+
let entries;
|
|
137
|
+
try { entries = readdirSync(root, { withFileTypes: true }); } catch { return out; }
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
const full = join(root, entry.name);
|
|
140
|
+
if (entry.isDirectory()) out.push(...walkMarkdown(full));
|
|
141
|
+
else if (entry.isFile() && entry.name.endsWith('.md')) out.push(full);
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
98
146
|
// ─── Chunking ────────────────────────────────────────────────────────────────
|
|
99
147
|
|
|
100
148
|
const CHUNK_CHARS = 400;
|
|
@@ -272,7 +320,7 @@ export function knowledgeSearch({ query, topK = 5, minScore = 0.1, repoRoot, roo
|
|
|
272
320
|
|
|
273
321
|
const root = repoRoot ?? REPO_ROOT;
|
|
274
322
|
const dataDir = rootDir ?? (process.env.CX_DATA_DIR?.trim() || homedir());
|
|
275
|
-
const sources = buildSourceList(root);
|
|
323
|
+
const sources = buildSourceList(root, rootDir);
|
|
276
324
|
|
|
277
325
|
// Build corpus from docs + operator knowledge
|
|
278
326
|
const allChunks = [];
|
|
@@ -318,6 +366,7 @@ export function knowledgeSearch({ query, topK = 5, minScore = 0.1, repoRoot, roo
|
|
|
318
366
|
text: chunk.text,
|
|
319
367
|
heading: chunk.heading,
|
|
320
368
|
file: chunk.source.rel,
|
|
369
|
+
origin: chunk.source.source || 'bundled',
|
|
321
370
|
score: Math.round(score * 100) / 100,
|
|
322
371
|
lineStart: chunk.lineStart,
|
|
323
372
|
}));
|
package/lib/mcp/server.mjs
CHANGED
|
@@ -723,19 +723,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
723
723
|
required: ['query'],
|
|
724
724
|
},
|
|
725
725
|
},
|
|
726
|
-
{
|
|
727
|
-
name: 'rovo_search',
|
|
728
|
-
description: 'Cross-system semantic search via Atlassian Rovo. Searches Jira, Confluence, and other accessible sources. Returns excerpts with source attribution. Does NOT store results in the observation store. Use for broad queries across all sources, not just focal projects.',
|
|
729
|
-
inputSchema: {
|
|
730
|
-
type: 'object',
|
|
731
|
-
properties: {
|
|
732
|
-
query: { type: 'string', description: 'Search query (e.g., "Iverson reliability", "DR failover").' },
|
|
733
|
-
top_k: { type: 'number', description: 'Max results (default: 10, max: 50).' },
|
|
734
|
-
sources: { type: 'string', description: 'Comma-separated source filter (e.g., "jira,confluence").' },
|
|
735
|
-
},
|
|
736
|
-
required: ['query'],
|
|
737
|
-
},
|
|
738
|
-
},
|
|
739
726
|
{
|
|
740
727
|
name: 'efficiency_snapshot',
|
|
741
728
|
description: 'Returns the read-efficiency snapshot for the current session — repeated reads, large reads, hot-spot files, and recommendations. Use this when investigating why a session feels slow or to surface optimization opportunities.',
|
|
@@ -939,6 +926,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
939
926
|
properties: { cwd: { type: 'string', description: 'Project root (default: server cwd).' } },
|
|
940
927
|
},
|
|
941
928
|
},
|
|
929
|
+
{
|
|
930
|
+
name: 'document_export',
|
|
931
|
+
description: 'Convert a markdown file into a distributable PDF, DOCX, or HTML document via Pandoc (DOCX/HTML) and Typst (PDF engine), per ADR-0024. Both engines are optional system binaries discovered at runtime — the tool returns a structured "install <binary>" error when tooling is absent, never crashes. Use `detect_only=true` to check availability without running an export.',
|
|
932
|
+
inputSchema: {
|
|
933
|
+
type: 'object',
|
|
934
|
+
required: ['format'],
|
|
935
|
+
properties: {
|
|
936
|
+
input_path: { type: 'string', description: 'Absolute path to the markdown source file (required unless detect_only=true).' },
|
|
937
|
+
output_path: { type: 'string', description: 'Optional absolute output path; defaults to <input>.<format> next to the source.' },
|
|
938
|
+
format: { type: 'string', enum: ['pdf', 'docx', 'html'], description: 'Target format.' },
|
|
939
|
+
detect_only: { type: 'boolean', description: 'When true, report binary availability without exporting. Default false.' },
|
|
940
|
+
},
|
|
941
|
+
},
|
|
942
|
+
},
|
|
942
943
|
{
|
|
943
944
|
name: 'knowledge_search',
|
|
944
945
|
description: 'Search Construct\'s own documentation, knowledge base, and distilled embed observations. Call this immediately — no approval needed — when the user asks what Construct is, how a feature works, what commands exist, or anything about Construct\'s architecture or configuration. Also searches embed observations from GitHub, Jira, and other configured sources. Returns relevant excerpts with source file and heading.',
|
|
@@ -1176,6 +1177,38 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
1176
1177
|
},
|
|
1177
1178
|
},
|
|
1178
1179
|
},
|
|
1180
|
+
{
|
|
1181
|
+
name: 'orchestration_run',
|
|
1182
|
+
description: 'EXECUTE a real multi-specialist orchestration run via the local Construct daemon and return per-specialist output — the executing counterpart to workflow_invoke (which only plans). For MCP hosts with no subagent primitive (VS Code/Copilot, Cursor), this is how you actually run a specialist chain: the engine owns orchestration, this tool is the thin client (ADR-0022). Requires a running daemon (`construct dashboard`); if it is unreachable the tool fails fast with how to start it rather than silently degrading to a single-persona pass. Real specialist output requires the daemon to run the `provider` worker backend (a provider key configured); the default `inline` backend prepares tasks only.',
|
|
1183
|
+
inputSchema: {
|
|
1184
|
+
type: 'object',
|
|
1185
|
+
required: ['request'],
|
|
1186
|
+
properties: {
|
|
1187
|
+
request: { type: 'string', description: 'Natural-language description of the work to orchestrate (e.g. "refactor the auth module and review it for security").' },
|
|
1188
|
+
workflow_type: { type: 'string', description: 'Optional workflow type to shape the plan (e.g. architecture-review, risk-review).' },
|
|
1189
|
+
requested_strategy: { type: 'string', enum: ['orchestrated', 'prompt-only', 'auto'], description: 'Execution strategy (default auto).' },
|
|
1190
|
+
worker_backend: { type: 'string', enum: ['inline', 'provider'], description: 'provider executes specialists (needs a provider key); inline prepares only. Default: daemon config.' },
|
|
1191
|
+
host: { type: 'string', description: 'Host/IDE identifier (advisory).' },
|
|
1192
|
+
host_model: { type: 'string', description: 'Model the host uses, for model resolution.' },
|
|
1193
|
+
host_provider: { type: 'string', description: 'Provider family the host uses, for model resolution.' },
|
|
1194
|
+
file_count: { type: 'number', description: 'Optional planning hint: number of files in scope.' },
|
|
1195
|
+
module_count: { type: 'number', description: 'Optional planning hint: number of modules in scope.' },
|
|
1196
|
+
wait: { type: 'boolean', description: 'Wait for the run to reach a terminal state and return task output (default true). false returns the runId immediately to poll with orchestration_status.' },
|
|
1197
|
+
timeout_ms: { type: 'number', description: 'Max time to wait when wait=true (default 120000). On timeout the runId is returned to poll.' },
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
name: 'orchestration_status',
|
|
1203
|
+
description: 'Inspect orchestration runs on the local Construct daemon: pass run_id for the full record (status, per-task status/executor/output/error), or omit it for a list of recent runs. Fails fast if the daemon is unreachable.',
|
|
1204
|
+
inputSchema: {
|
|
1205
|
+
type: 'object',
|
|
1206
|
+
properties: {
|
|
1207
|
+
run_id: { type: 'string', description: 'Run id to fetch. Omit to list recent runs.' },
|
|
1208
|
+
limit: { type: 'number', description: 'Max runs to list when run_id is omitted (default 20).' },
|
|
1209
|
+
},
|
|
1210
|
+
},
|
|
1211
|
+
},
|
|
1179
1212
|
],
|
|
1180
1213
|
}));
|
|
1181
1214
|
|
|
@@ -1227,7 +1260,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1227
1260
|
else if (name === 'session_search') result = sessionSearch(args);
|
|
1228
1261
|
else if (name === 'session_save') result = sessionSave(args);
|
|
1229
1262
|
else if (name === 'memory_search') result = await memorySearch(args);
|
|
1230
|
-
else if (name === 'memory_add_observations') result = memoryAddObservations(args);
|
|
1263
|
+
else if (name === 'memory_add_observations') result = await memoryAddObservations(args);
|
|
1231
1264
|
else if (name === 'memory_create_entities') result = memoryCreateEntities(args);
|
|
1232
1265
|
else if (name === 'memory_recent') result = memoryRecent(args);
|
|
1233
1266
|
else if (name === 'rovo_search') result = await rovoSearch(args);
|
|
@@ -1241,6 +1274,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1241
1274
|
const { knowledgeSearch } = await import('../knowledge/search.mjs');
|
|
1242
1275
|
result = knowledgeSearch({ query: args.query, topK: args.top_k, repoRoot: args.repo_root, rootDir: args.root_dir });
|
|
1243
1276
|
}
|
|
1277
|
+
else if (name === 'document_export') {
|
|
1278
|
+
const { detect, exportMarkdown } = await import('../document-export.mjs');
|
|
1279
|
+
if (args.detect_only) {
|
|
1280
|
+
result = detect(args.format);
|
|
1281
|
+
} else {
|
|
1282
|
+
result = exportMarkdown({ inputPath: args.input_path, outputPath: args.output_path, format: args.format });
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1244
1285
|
else if (name === 'profile_show') result = profileShow(args);
|
|
1245
1286
|
else if (name === 'profile_list') result = profileList();
|
|
1246
1287
|
else if (name === 'profile_drafts') result = profileDrafts(args);
|
|
@@ -1258,6 +1299,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1258
1299
|
else if (name === 'workflow_invoke') result = await workflowInvoke(args);
|
|
1259
1300
|
else if (name === 'capability_describe') result = capabilityDescribe(args);
|
|
1260
1301
|
else if (name === 'construct_execution_resolve') result = executionResolve(args);
|
|
1302
|
+
else if (name === 'orchestration_run') { const m = await import('./tools/orchestration-run.mjs'); result = await m.orchestrationRun(args); }
|
|
1303
|
+
else if (name === 'orchestration_status') { const m = await import('./tools/orchestration-run.mjs'); result = await m.orchestrationStatus(args); }
|
|
1261
1304
|
else result = { error: `Unknown tool: ${name}` };
|
|
1262
1305
|
} catch (err) {
|
|
1263
1306
|
result = { error: err.message ?? String(err) };
|