engimcp 1.0.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/CHANGELOG.md +43 -0
- package/LICENSE +21 -0
- package/README.md +345 -0
- package/dist/audit/auditLog.d.ts +9 -0
- package/dist/audit/auditLog.js +20 -0
- package/dist/audit/auditLog.js.map +1 -0
- package/dist/bom/bomService.d.ts +23 -0
- package/dist/bom/bomService.js +44 -0
- package/dist/bom/bomService.js.map +1 -0
- package/dist/config/projectConfig.d.ts +16 -0
- package/dist/config/projectConfig.js +16 -0
- package/dist/config/projectConfig.js.map +1 -0
- package/dist/config/schema.d.ts +16 -0
- package/dist/config/schema.js +21 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/context/contextPack.d.ts +31 -0
- package/dist/context/contextPack.js +142 -0
- package/dist/context/contextPack.js.map +1 -0
- package/dist/context/tokenBudget.d.ts +1 -0
- package/dist/context/tokenBudget.js +4 -0
- package/dist/context/tokenBudget.js.map +1 -0
- package/dist/decisions/decisionService.d.ts +21 -0
- package/dist/decisions/decisionService.js +62 -0
- package/dist/decisions/decisionService.js.map +1 -0
- package/dist/dev/smokeProjectStatus.d.ts +1 -0
- package/dist/dev/smokeProjectStatus.js +9 -0
- package/dist/dev/smokeProjectStatus.js.map +1 -0
- package/dist/documents/documentService.d.ts +83 -0
- package/dist/documents/documentService.js +371 -0
- package/dist/documents/documentService.js.map +1 -0
- package/dist/documents/frontmatter.d.ts +14 -0
- package/dist/documents/frontmatter.js +30 -0
- package/dist/documents/frontmatter.js.map +1 -0
- package/dist/documents/headings.d.ts +6 -0
- package/dist/documents/headings.js +15 -0
- package/dist/documents/headings.js.map +1 -0
- package/dist/documents/markdownParser.d.ts +2 -0
- package/dist/documents/markdownParser.js +3 -0
- package/dist/documents/markdownParser.js.map +1 -0
- package/dist/documents/sectionPatch.d.ts +7 -0
- package/dist/documents/sectionPatch.js +73 -0
- package/dist/documents/sectionPatch.js.map +1 -0
- package/dist/documents/templates.d.ts +1 -0
- package/dist/documents/templates.js +62 -0
- package/dist/documents/templates.js.map +1 -0
- package/dist/filesystem/filesystemService.d.ts +168 -0
- package/dist/filesystem/filesystemService.js +606 -0
- package/dist/filesystem/filesystemService.js.map +1 -0
- package/dist/git/gitAdapter.d.ts +25 -0
- package/dist/git/gitAdapter.js +99 -0
- package/dist/git/gitAdapter.js.map +1 -0
- package/dist/graph/graphBuilder.d.ts +27 -0
- package/dist/graph/graphBuilder.js +126 -0
- package/dist/graph/graphBuilder.js.map +1 -0
- package/dist/graph/impact.d.ts +18 -0
- package/dist/graph/impact.js +53 -0
- package/dist/graph/impact.js.map +1 -0
- package/dist/graph/relations.d.ts +7 -0
- package/dist/graph/relations.js +28 -0
- package/dist/graph/relations.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/errors.d.ts +5 -0
- package/dist/mcp/errors.js +11 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/prompts.d.ts +2 -0
- package/dist/mcp/prompts.js +39 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/resources.d.ts +2 -0
- package/dist/mcp/resources.js +36 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/tools.d.ts +2 -0
- package/dist/mcp/tools.js +431 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/project/pathSafety.d.ts +6 -0
- package/dist/project/pathSafety.js +137 -0
- package/dist/project/pathSafety.js.map +1 -0
- package/dist/project/projectInit.d.ts +11 -0
- package/dist/project/projectInit.js +81 -0
- package/dist/project/projectInit.js.map +1 -0
- package/dist/project/projectService.d.ts +36 -0
- package/dist/project/projectService.js +60 -0
- package/dist/project/projectService.js.map +1 -0
- package/dist/project/writeGuards.d.ts +3 -0
- package/dist/project/writeGuards.js +46 -0
- package/dist/project/writeGuards.js.map +1 -0
- package/dist/requirements/requirementService.d.ts +19 -0
- package/dist/requirements/requirementService.js +72 -0
- package/dist/requirements/requirementService.js.map +1 -0
- package/dist/runtime/options.d.ts +7 -0
- package/dist/runtime/options.js +34 -0
- package/dist/runtime/options.js.map +1 -0
- package/dist/search/ftsIndex.d.ts +29 -0
- package/dist/search/ftsIndex.js +96 -0
- package/dist/search/ftsIndex.js.map +1 -0
- package/dist/search/searchService.d.ts +20 -0
- package/dist/search/searchService.js +17 -0
- package/dist/search/searchService.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +16 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/sqlite.d.ts +15 -0
- package/dist/storage/sqlite.js +80 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/tasks/taskService.d.ts +17 -0
- package/dist/tasks/taskService.js +57 -0
- package/dist/tasks/taskService.js.map +1 -0
- package/dist/utils/atomicWrite.d.ts +1 -0
- package/dist/utils/atomicWrite.js +10 -0
- package/dist/utils/atomicWrite.js.map +1 -0
- package/dist/utils/errors.d.ts +1 -0
- package/dist/utils/errors.js +2 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/ids.d.ts +3 -0
- package/dist/utils/ids.js +25 -0
- package/dist/utils/ids.js.map +1 -0
- package/dist/validation/checks.d.ts +1 -0
- package/dist/validation/checks.js +2 -0
- package/dist/validation/checks.js.map +1 -0
- package/dist/validation/validator.d.ts +18 -0
- package/dist/validation/validator.js +131 -0
- package/dist/validation/validator.js.map +1 -0
- package/dist/verification/testReportService.d.ts +19 -0
- package/dist/verification/testReportService.js +42 -0
- package/dist/verification/testReportService.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +49 -0
- package/templates/design_doc.md +24 -0
- package/templates/edr.md +58 -0
- package/templates/requirement.md +30 -0
- package/templates/task.md +22 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { access, mkdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { atomicWrite } from "../utils/atomicWrite.js";
|
|
4
|
+
import { assertAbsoluteRoot } from "./pathSafety.js";
|
|
5
|
+
import { EngiMcpError } from "../mcp/errors.js";
|
|
6
|
+
export async function initProject(input) {
|
|
7
|
+
const root = assertAbsoluteRoot(input.root);
|
|
8
|
+
const createdPaths = [];
|
|
9
|
+
const warnings = [];
|
|
10
|
+
await mkdir(root, { recursive: true });
|
|
11
|
+
const projectConfigPath = path.join(root, "project.yaml");
|
|
12
|
+
if ((await exists(projectConfigPath)) && !input.force) {
|
|
13
|
+
throw new EngiMcpError("PROJECT_EXISTS", "project.yaml already exists.");
|
|
14
|
+
}
|
|
15
|
+
const files = [
|
|
16
|
+
{
|
|
17
|
+
relativePath: "project.yaml",
|
|
18
|
+
content: `project:
|
|
19
|
+
id: ${path.basename(root)}
|
|
20
|
+
name: ${path.basename(root)}
|
|
21
|
+
schema_version: 1.0.0
|
|
22
|
+
source_of_truth: markdown
|
|
23
|
+
paths:
|
|
24
|
+
docs: docs
|
|
25
|
+
templates: templates
|
|
26
|
+
`
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
relativePath: "docs/README.md",
|
|
30
|
+
content: `---
|
|
31
|
+
id: DOC-PROJECT-README
|
|
32
|
+
kind: overview
|
|
33
|
+
status: draft
|
|
34
|
+
version: 0.1.0
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# Project Overview
|
|
38
|
+
`
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
relativePath: "templates/design_doc.md",
|
|
42
|
+
content: `---
|
|
43
|
+
id: DOC-EXAMPLE
|
|
44
|
+
kind: design_doc
|
|
45
|
+
status: draft
|
|
46
|
+
version: 0.1.0
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
# Document Title
|
|
50
|
+
|
|
51
|
+
## Purpose
|
|
52
|
+
`
|
|
53
|
+
}
|
|
54
|
+
];
|
|
55
|
+
for (const file of files) {
|
|
56
|
+
const target = path.join(root, file.relativePath);
|
|
57
|
+
if ((await exists(target)) && !input.force) {
|
|
58
|
+
warnings.push(`Skipped existing file: ${file.relativePath}`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
await atomicWrite(target, file.content);
|
|
62
|
+
createdPaths.push(file.relativePath);
|
|
63
|
+
}
|
|
64
|
+
await mkdir(path.join(root, ".engimcp"), { recursive: true });
|
|
65
|
+
createdPaths.push(".engimcp/");
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
created_paths: createdPaths,
|
|
69
|
+
warnings
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function exists(filePath) {
|
|
73
|
+
try {
|
|
74
|
+
await access(filePath);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=projectInit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectInit.js","sourceRoot":"","sources":["../../src/project/projectInit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAchD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAuB;IACvD,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEvC,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAC1D,IAAI,CAAC,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACtD,MAAM,IAAI,YAAY,CAAC,gBAAgB,EAAE,8BAA8B,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,KAAK,GAAqD;QAC9D;YACE,YAAY,EAAE,cAAc;YAC5B,OAAO,EAAE;QACP,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;UACjB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;;;;;;CAM5B;SACI;QACD;YACE,YAAY,EAAE,gBAAgB;YAC9B,OAAO,EAAE;;;;;;;;CAQd;SACI;QACD;YACE,YAAY,EAAE,yBAAyB;YACvC,OAAO,EAAE;;;;;;;;;;CAUd;SACI;KACF,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YAC3C,QAAQ,CAAC,IAAI,CAAC,0BAA0B,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;YAC7D,SAAS;QACX,CAAC;QACD,MAAM,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACxC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAE/B,OAAO;QACL,EAAE,EAAE,IAAI;QACR,aAAa,EAAE,YAAY;QAC3B,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,QAAgB;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface ProjectStatusInput {
|
|
2
|
+
root: string;
|
|
3
|
+
include_validation_summary?: boolean;
|
|
4
|
+
include_git_status?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface ProjectStatus {
|
|
7
|
+
project_id: string;
|
|
8
|
+
documents: number;
|
|
9
|
+
requirements: number;
|
|
10
|
+
decisions: number;
|
|
11
|
+
tasks_open: number;
|
|
12
|
+
validation?: {
|
|
13
|
+
errors: number;
|
|
14
|
+
warnings: number;
|
|
15
|
+
};
|
|
16
|
+
git?: {
|
|
17
|
+
is_git_repo: boolean;
|
|
18
|
+
dirty: boolean;
|
|
19
|
+
summary: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface ProjectMapInput {
|
|
23
|
+
root: string;
|
|
24
|
+
kind?: string[];
|
|
25
|
+
max_depth?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ProjectMapItem {
|
|
28
|
+
id?: string;
|
|
29
|
+
path: string;
|
|
30
|
+
kind?: string;
|
|
31
|
+
status?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function getProjectStatus(input: ProjectStatusInput): Promise<ProjectStatus>;
|
|
34
|
+
export declare function getProjectMap(input: ProjectMapInput): Promise<{
|
|
35
|
+
items: ProjectMapItem[];
|
|
36
|
+
}>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readProjectConfig } from "../config/projectConfig.js";
|
|
3
|
+
import { buildDocumentRegistry } from "../documents/documentService.js";
|
|
4
|
+
import { getGitStatus } from "../git/gitAdapter.js";
|
|
5
|
+
import { ensureIndex } from "../storage/sqlite.js";
|
|
6
|
+
import { validateProject } from "../validation/validator.js";
|
|
7
|
+
import { assertAbsoluteRoot } from "./pathSafety.js";
|
|
8
|
+
export async function getProjectStatus(input) {
|
|
9
|
+
const root = assertAbsoluteRoot(input.root);
|
|
10
|
+
const projectConfig = await readProjectConfig(root);
|
|
11
|
+
await ensureIndex(root);
|
|
12
|
+
const registry = await buildDocumentRegistry(root);
|
|
13
|
+
const status = {
|
|
14
|
+
project_id: projectConfig.project.id ?? path.basename(root),
|
|
15
|
+
documents: registry.documents.length,
|
|
16
|
+
requirements: registry.documents.filter((document) => isRequirement(document.kind, document.id))
|
|
17
|
+
.length,
|
|
18
|
+
decisions: registry.documents.filter((document) => isDecision(document.kind, document.id))
|
|
19
|
+
.length,
|
|
20
|
+
tasks_open: registry.documents.filter((document) => document.kind === "task" && document.status !== "done" && document.status !== "cancelled").length
|
|
21
|
+
};
|
|
22
|
+
if (input.include_validation_summary ?? true) {
|
|
23
|
+
const validation = await validateProject({ root, severity: "warning" });
|
|
24
|
+
status.validation = {
|
|
25
|
+
errors: validation.errors.length,
|
|
26
|
+
warnings: validation.warnings.length
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (input.include_git_status ?? true) {
|
|
30
|
+
status.git = await getGitStatus(root);
|
|
31
|
+
}
|
|
32
|
+
return status;
|
|
33
|
+
}
|
|
34
|
+
export async function getProjectMap(input) {
|
|
35
|
+
const root = assertAbsoluteRoot(input.root);
|
|
36
|
+
const registry = await buildDocumentRegistry(root);
|
|
37
|
+
const allowedKinds = input.kind && input.kind.length > 0 ? new Set(input.kind) : undefined;
|
|
38
|
+
const maxDepth = input.max_depth;
|
|
39
|
+
const items = registry.documents
|
|
40
|
+
.filter((document) => !allowedKinds || (document.kind && allowedKinds.has(document.kind)))
|
|
41
|
+
.filter((document) => maxDepth === undefined || pathDepth(document.path) <= maxDepth)
|
|
42
|
+
.map((document) => ({
|
|
43
|
+
id: document.id,
|
|
44
|
+
path: document.path,
|
|
45
|
+
kind: document.kind,
|
|
46
|
+
status: document.status
|
|
47
|
+
}))
|
|
48
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
49
|
+
return { items };
|
|
50
|
+
}
|
|
51
|
+
function isRequirement(kind, id) {
|
|
52
|
+
return kind === "requirement" || kind === "requirements" || /^(FR|NFR|SEC|AC)-\d+/.test(id ?? "");
|
|
53
|
+
}
|
|
54
|
+
function isDecision(kind, id) {
|
|
55
|
+
return kind === "decision" || kind === "decision-log" || /^EDR-\d+/.test(id ?? "");
|
|
56
|
+
}
|
|
57
|
+
function pathDepth(relativePath) {
|
|
58
|
+
return relativePath.split(/[\\/]+/).filter(Boolean).length;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=projectService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectService.js","sourceRoot":"","sources":["../../src/project/projectService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function assertProjectWritable(root: string): Promise<void>;
|
|
2
|
+
export declare function assertNoGitConflictMarkers(content: string, displayPath: string): void;
|
|
3
|
+
export declare function assertPathHasNoGitConflictMarkers(absolutePath: string, displayPath: string): Promise<void>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { lstat, readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readProjectConfig } from "../config/projectConfig.js";
|
|
4
|
+
import { EngiMcpError } from "../mcp/errors.js";
|
|
5
|
+
import { getRuntimeOptions } from "../runtime/options.js";
|
|
6
|
+
export async function assertProjectWritable(root) {
|
|
7
|
+
if (getRuntimeOptions().readOnly) {
|
|
8
|
+
throw new EngiMcpError("READ_ONLY", "Project is in read-only mode.");
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const config = await readProjectConfig(root);
|
|
12
|
+
if (config.mcp?.read_only_mode) {
|
|
13
|
+
throw new EngiMcpError("READ_ONLY", "Project is in read-only mode.");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (error instanceof EngiMcpError) {
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function assertNoGitConflictMarkers(content, displayPath) {
|
|
23
|
+
if (/^<<<<<<< .*\r?\n[\s\S]*?^=======\r?\n[\s\S]*?^>>>>>>> .*$/m.test(content)) {
|
|
24
|
+
throw new EngiMcpError("GIT_CONFLICT_PRESENT", `Refusing to overwrite ${displayPath} because it contains Git conflict markers.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function assertPathHasNoGitConflictMarkers(absolutePath, displayPath) {
|
|
28
|
+
let fileStat;
|
|
29
|
+
try {
|
|
30
|
+
fileStat = await lstat(absolutePath);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (fileStat.isDirectory()) {
|
|
36
|
+
for (const entry of await readdir(absolutePath, { withFileTypes: true })) {
|
|
37
|
+
await assertPathHasNoGitConflictMarkers(path.join(absolutePath, entry.name), path.posix.join(displayPath, entry.name));
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (!fileStat.isFile()) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
assertNoGitConflictMarkers(await readFile(absolutePath, "utf8"), displayPath);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=writeGuards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writeGuards.js","sourceRoot":"","sources":["../../src/project/writeGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAY;IACtD,IAAI,iBAAiB,EAAE,CAAC,QAAQ,EAAE,CAAC;QACjC,MAAM,IAAI,YAAY,CAAC,WAAW,EAAE,+BAA+B,CAAC,CAAC;IACvE,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,MAAM,CAAC,GAAG,EAAE,cAAc,EAAE,CAAC;YAC/B,MAAM,IAAI,YAAY,CAAC,WAAW,EAAE,+BAA+B,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,OAAe,EAAE,WAAmB;IAC7E,IAAI,4DAA4D,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/E,MAAM,IAAI,YAAY,CACpB,sBAAsB,EACtB,yBAAyB,WAAW,4CAA4C,CACjF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iCAAiC,CACrD,YAAoB,EACpB,WAAmB;IAEnB,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IAED,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;QAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,OAAO,CAAC,YAAY,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YACzE,MAAM,iCAAiC,CACrC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,EACnC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,CACzC,CAAC;QACJ,CAAC;QACD,OAAO;IACT,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QACvB,OAAO;IACT,CAAC;IAED,0BAA0B,CAAC,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface CreateRequirementInput {
|
|
2
|
+
root: string;
|
|
3
|
+
requirement_type: "functional" | "non_functional" | "security" | "acceptance";
|
|
4
|
+
title: string;
|
|
5
|
+
statement: string;
|
|
6
|
+
priority: "must" | "should" | "could" | "wont";
|
|
7
|
+
rationale?: string;
|
|
8
|
+
related?: string[];
|
|
9
|
+
dry_run?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function createRequirement(input: CreateRequirementInput): Promise<{
|
|
12
|
+
id: string;
|
|
13
|
+
path: string;
|
|
14
|
+
content: string;
|
|
15
|
+
} | {
|
|
16
|
+
id: string;
|
|
17
|
+
path: string;
|
|
18
|
+
content?: undefined;
|
|
19
|
+
}>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { writeAuditLog } from "../audit/auditLog.js";
|
|
2
|
+
import { buildDocumentRegistry } from "../documents/documentService.js";
|
|
3
|
+
import { serializeDocument } from "../documents/frontmatter.js";
|
|
4
|
+
import { assertAbsoluteRoot, resolveSafePath } from "../project/pathSafety.js";
|
|
5
|
+
import { assertProjectWritable } from "../project/writeGuards.js";
|
|
6
|
+
import { atomicWrite } from "../utils/atomicWrite.js";
|
|
7
|
+
import { nextSequentialId, slugify } from "../utils/ids.js";
|
|
8
|
+
export async function createRequirement(input) {
|
|
9
|
+
const root = assertAbsoluteRoot(input.root);
|
|
10
|
+
await assertProjectWritable(root);
|
|
11
|
+
const registry = await buildDocumentRegistry(root);
|
|
12
|
+
const id = nextSequentialId(registry.byId.keys(), requirementPrefix(input.requirement_type));
|
|
13
|
+
const relativePath = `docs/requirements/${id}-${slugify(input.title)}.md`;
|
|
14
|
+
const frontmatter = {
|
|
15
|
+
id,
|
|
16
|
+
kind: "requirement",
|
|
17
|
+
requirement_type: input.requirement_type,
|
|
18
|
+
status: "draft",
|
|
19
|
+
priority: input.priority,
|
|
20
|
+
version: "0.1.0",
|
|
21
|
+
statement: input.statement,
|
|
22
|
+
rationale: input.rationale,
|
|
23
|
+
relates_to: input.related ?? [],
|
|
24
|
+
verified_by: [],
|
|
25
|
+
decided_by: []
|
|
26
|
+
};
|
|
27
|
+
const body = `# ${id}: ${input.title}
|
|
28
|
+
|
|
29
|
+
## Statement
|
|
30
|
+
|
|
31
|
+
${input.statement}
|
|
32
|
+
|
|
33
|
+
## Rationale
|
|
34
|
+
|
|
35
|
+
${input.rationale ?? ""}
|
|
36
|
+
|
|
37
|
+
## Acceptance Criteria
|
|
38
|
+
|
|
39
|
+
- [ ] Define verification.
|
|
40
|
+
|
|
41
|
+
## Links
|
|
42
|
+
|
|
43
|
+
- Related: ${(input.related ?? []).join(", ")}
|
|
44
|
+
`;
|
|
45
|
+
const content = serializeDocument(frontmatter, body);
|
|
46
|
+
if (input.dry_run) {
|
|
47
|
+
return { id, path: relativePath, content };
|
|
48
|
+
}
|
|
49
|
+
await atomicWrite(await resolveSafePath(root, relativePath), content);
|
|
50
|
+
await writeAuditLog({
|
|
51
|
+
root,
|
|
52
|
+
tool: "engi_requirement_create",
|
|
53
|
+
target: id,
|
|
54
|
+
operation: "create",
|
|
55
|
+
result: "ok",
|
|
56
|
+
diff_summary: "requirement created"
|
|
57
|
+
});
|
|
58
|
+
return { id, path: relativePath };
|
|
59
|
+
}
|
|
60
|
+
function requirementPrefix(type) {
|
|
61
|
+
if (type === "non_functional") {
|
|
62
|
+
return "NFR";
|
|
63
|
+
}
|
|
64
|
+
if (type === "security") {
|
|
65
|
+
return "SEC";
|
|
66
|
+
}
|
|
67
|
+
if (type === "acceptance") {
|
|
68
|
+
return "AC";
|
|
69
|
+
}
|
|
70
|
+
return "FR";
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=requirementService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirementService.js","sourceRoot":"","sources":["../../src/requirements/requirementService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAa5D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAA6B;IACnE,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,EAAE,GAAG,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,iBAAiB,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC7F,MAAM,YAAY,GAAG,qBAAqB,EAAE,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;IAC1E,MAAM,WAAW,GAAG;QAClB,EAAE;QACF,IAAI,EAAE,aAAa;QACnB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,MAAM,EAAE,OAAO;QACf,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,UAAU,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE;QAC/B,WAAW,EAAE,EAAE;QACf,UAAU,EAAE,EAAE;KACf,CAAC;IACF,MAAM,IAAI,GAAG,KAAK,EAAE,KAAK,KAAK,CAAC,KAAK;;;;EAIpC,KAAK,CAAC,SAAS;;;;EAIf,KAAK,CAAC,SAAS,IAAI,EAAE;;;;;;;;aAQV,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;CAC5C,CAAC;IACA,MAAM,OAAO,GAAG,iBAAiB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAErD,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,WAAW,CAAC,MAAM,eAAe,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;IACtE,MAAM,aAAa,CAAC;QAClB,IAAI;QACJ,IAAI,EAAE,yBAAyB;QAC/B,MAAM,EAAE,EAAE;QACV,SAAS,EAAE,QAAQ;QACnB,MAAM,EAAE,IAAI;QACZ,YAAY,EAAE,qBAAqB;KACpC,CAAC,CAAC;IAEH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAgD;IACzE,IAAI,IAAI,KAAK,gBAAgB,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface RuntimeOptions {
|
|
2
|
+
root?: string;
|
|
3
|
+
readOnly: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function parseRuntimeOptions(argv: readonly string[]): RuntimeOptions;
|
|
6
|
+
export declare function configureRuntimeOptions(options: RuntimeOptions): void;
|
|
7
|
+
export declare function getRuntimeOptions(): RuntimeOptions;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
let runtimeOptions = {
|
|
3
|
+
readOnly: process.env.ENGIMCP_READ_ONLY === "1" || process.env.ENGIMCP_READ_ONLY === "true"
|
|
4
|
+
};
|
|
5
|
+
export function parseRuntimeOptions(argv) {
|
|
6
|
+
const options = { readOnly: false };
|
|
7
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
8
|
+
const arg = argv[index];
|
|
9
|
+
if (arg === "--read-only") {
|
|
10
|
+
options.readOnly = true;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (arg === "--root") {
|
|
14
|
+
const root = argv[index + 1];
|
|
15
|
+
if (!root) {
|
|
16
|
+
throw new Error("--root requires a path.");
|
|
17
|
+
}
|
|
18
|
+
options.root = path.resolve(root);
|
|
19
|
+
index += 1;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (arg.startsWith("--root=")) {
|
|
23
|
+
options.root = path.resolve(arg.slice("--root=".length));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return options;
|
|
27
|
+
}
|
|
28
|
+
export function configureRuntimeOptions(options) {
|
|
29
|
+
runtimeOptions = { ...options };
|
|
30
|
+
}
|
|
31
|
+
export function getRuntimeOptions() {
|
|
32
|
+
return runtimeOptions;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/runtime/options.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,IAAI,cAAc,GAAmB;IACnC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,KAAK,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,KAAK,MAAM;CAC5F,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,IAAuB;IACzD,MAAM,OAAO,GAAmB,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAEpD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,GAAG,KAAK,aAAa,EAAE,CAAC;YAC1B,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;YACxB,SAAS;QACX,CAAC;QACD,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YAC7B,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;YAC7C,CAAC;YACD,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAClC,KAAK,IAAI,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QACD,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,OAAuB;IAC7D,cAAc,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,cAAc,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface FullTextIndexDocument {
|
|
2
|
+
id?: string;
|
|
3
|
+
path: string;
|
|
4
|
+
kind?: string;
|
|
5
|
+
status?: string;
|
|
6
|
+
tags: string[];
|
|
7
|
+
frontmatter: Record<string, unknown>;
|
|
8
|
+
content: string;
|
|
9
|
+
lowerContent: string;
|
|
10
|
+
}
|
|
11
|
+
export interface FullTextSearchOptions {
|
|
12
|
+
query: string;
|
|
13
|
+
kind?: string[];
|
|
14
|
+
status?: string[];
|
|
15
|
+
tags?: string[];
|
|
16
|
+
frontmatter?: Record<string, unknown>;
|
|
17
|
+
limit?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface FullTextSearchResult {
|
|
20
|
+
id?: string;
|
|
21
|
+
path: string;
|
|
22
|
+
score: number;
|
|
23
|
+
snippet: string;
|
|
24
|
+
}
|
|
25
|
+
export interface FullTextIndex {
|
|
26
|
+
documents: FullTextIndexDocument[];
|
|
27
|
+
search(options: FullTextSearchOptions): FullTextSearchResult[];
|
|
28
|
+
}
|
|
29
|
+
export declare function rebuildFullTextIndex(rootInput: string): Promise<FullTextIndex>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { buildDocumentRegistry } from "../documents/documentService.js";
|
|
3
|
+
import { assertAbsoluteRoot } from "../project/pathSafety.js";
|
|
4
|
+
export async function rebuildFullTextIndex(rootInput) {
|
|
5
|
+
const root = assertAbsoluteRoot(rootInput);
|
|
6
|
+
const registry = await buildDocumentRegistry(root);
|
|
7
|
+
const documents = await Promise.all(registry.documents.map(async (document) => {
|
|
8
|
+
const content = await readFile(document.absolutePath, "utf8");
|
|
9
|
+
return {
|
|
10
|
+
id: document.id,
|
|
11
|
+
path: document.path,
|
|
12
|
+
kind: document.kind,
|
|
13
|
+
status: document.status,
|
|
14
|
+
tags: extractStringList(document.frontmatter.tags),
|
|
15
|
+
frontmatter: document.frontmatter,
|
|
16
|
+
content,
|
|
17
|
+
lowerContent: content.toLowerCase()
|
|
18
|
+
};
|
|
19
|
+
}));
|
|
20
|
+
return {
|
|
21
|
+
documents,
|
|
22
|
+
search(options) {
|
|
23
|
+
return searchIndex(documents, options);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function searchIndex(documents, options) {
|
|
28
|
+
const terms = normalizedTerms(options.query);
|
|
29
|
+
const kindFilter = options.kind ? new Set(options.kind) : undefined;
|
|
30
|
+
const statusFilter = options.status ? new Set(options.status) : undefined;
|
|
31
|
+
const tagFilter = options.tags ? new Set(options.tags) : undefined;
|
|
32
|
+
const results = [];
|
|
33
|
+
for (const document of documents) {
|
|
34
|
+
if (kindFilter && (!document.kind || !kindFilter.has(document.kind))) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (statusFilter && (!document.status || !statusFilter.has(document.status))) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (tagFilter && !document.tags.some((tag) => tagFilter.has(tag))) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (options.frontmatter && !frontmatterMatches(document.frontmatter, options.frontmatter)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const textScore = terms.reduce((sum, term) => sum + countOccurrences(document.lowerContent, term), 0);
|
|
47
|
+
const idScore = terms.some((term) => document.id?.toLowerCase().includes(term)) ? 5 : 0;
|
|
48
|
+
const totalScore = textScore + idScore;
|
|
49
|
+
if (totalScore === 0) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
results.push({
|
|
53
|
+
id: document.id,
|
|
54
|
+
path: document.path,
|
|
55
|
+
score: totalScore,
|
|
56
|
+
snippet: makeSnippet(document.content, terms)
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return results
|
|
60
|
+
.sort((left, right) => right.score - left.score || left.path.localeCompare(right.path))
|
|
61
|
+
.slice(0, options.limit ?? 20);
|
|
62
|
+
}
|
|
63
|
+
function frontmatterMatches(frontmatter, filters) {
|
|
64
|
+
return Object.entries(filters).every(([key, expected]) => {
|
|
65
|
+
const actual = frontmatter[key];
|
|
66
|
+
if (Array.isArray(actual)) {
|
|
67
|
+
return actual.some((item) => item === expected);
|
|
68
|
+
}
|
|
69
|
+
return actual === expected;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function extractStringList(value) {
|
|
73
|
+
if (Array.isArray(value)) {
|
|
74
|
+
return value.filter((item) => typeof item === "string");
|
|
75
|
+
}
|
|
76
|
+
return typeof value === "string" ? [value] : [];
|
|
77
|
+
}
|
|
78
|
+
function normalizedTerms(query) {
|
|
79
|
+
return query
|
|
80
|
+
.toLowerCase()
|
|
81
|
+
.split(/[^a-z0-9]+/)
|
|
82
|
+
.filter(Boolean);
|
|
83
|
+
}
|
|
84
|
+
function countOccurrences(content, term) {
|
|
85
|
+
return content.split(term).length - 1;
|
|
86
|
+
}
|
|
87
|
+
function makeSnippet(content, terms) {
|
|
88
|
+
const lowerContent = content.toLowerCase();
|
|
89
|
+
const index = terms.map((term) => lowerContent.indexOf(term)).find((position) => position >= 0) ?? 0;
|
|
90
|
+
const start = Math.max(0, index - 60);
|
|
91
|
+
return content
|
|
92
|
+
.slice(start, start + 160)
|
|
93
|
+
.replace(/\s+/g, " ")
|
|
94
|
+
.trim();
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=ftsIndex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ftsIndex.js","sourceRoot":"","sources":["../../src/search/ftsIndex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface SearchProjectInput {
|
|
2
|
+
root: string;
|
|
3
|
+
query: string;
|
|
4
|
+
filters?: {
|
|
5
|
+
kind?: string[];
|
|
6
|
+
status?: string[];
|
|
7
|
+
tags?: string[];
|
|
8
|
+
frontmatter?: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
limit?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface SearchResult {
|
|
13
|
+
id?: string;
|
|
14
|
+
path: string;
|
|
15
|
+
score: number;
|
|
16
|
+
snippet: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function searchProject(input: SearchProjectInput): Promise<{
|
|
19
|
+
results: SearchResult[];
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { assertAbsoluteRoot } from "../project/pathSafety.js";
|
|
2
|
+
import { rebuildFullTextIndex } from "./ftsIndex.js";
|
|
3
|
+
export async function searchProject(input) {
|
|
4
|
+
const root = assertAbsoluteRoot(input.root);
|
|
5
|
+
const index = await rebuildFullTextIndex(root);
|
|
6
|
+
return {
|
|
7
|
+
results: index.search({
|
|
8
|
+
query: input.query,
|
|
9
|
+
kind: input.filters?.kind,
|
|
10
|
+
status: input.filters?.status,
|
|
11
|
+
tags: input.filters?.tags,
|
|
12
|
+
frontmatter: input.filters?.frontmatter,
|
|
13
|
+
limit: input.limit
|
|
14
|
+
})
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=searchService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"searchService.js","sourceRoot":"","sources":["../../src/search/searchService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAqBrD,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAyB;IAEzB,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,MAAM,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAE/C,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI;YACzB,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM;YAC7B,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI;YACzB,WAAW,EAAE,KAAK,CAAC,OAAO,EAAE,WAAW;YACvC,KAAK,EAAE,KAAK,CAAC,KAAK;SACnB,CAAC;KACH,CAAC;AACJ,CAAC"}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { registerPrompts } from "./mcp/prompts.js";
|
|
3
|
+
import { registerResources } from "./mcp/resources.js";
|
|
4
|
+
import { registerTools } from "./mcp/tools.js";
|
|
5
|
+
import { ENGI_MCP_VERSION } from "./version.js";
|
|
6
|
+
export function createServer() {
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: "engimcp",
|
|
9
|
+
version: ENGI_MCP_VERSION
|
|
10
|
+
});
|
|
11
|
+
registerTools(server);
|
|
12
|
+
registerResources(server);
|
|
13
|
+
registerPrompts(server);
|
|
14
|
+
return server;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,gBAAgB;KAC1B,CAAC,CAAC;IAEH,aAAa,CAAC,MAAM,CAAC,CAAC;IACtB,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,eAAe,CAAC,MAAM,CAAC,CAAC;IAExB,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface RebuildIndexResult {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
path: string;
|
|
4
|
+
documents: number;
|
|
5
|
+
relations: number;
|
|
6
|
+
validation_issues: number;
|
|
7
|
+
}
|
|
8
|
+
export interface EnsureIndexResult {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
path: string;
|
|
11
|
+
rebuilt: boolean;
|
|
12
|
+
index?: RebuildIndexResult;
|
|
13
|
+
}
|
|
14
|
+
export declare function ensureIndex(root: string): Promise<EnsureIndexResult>;
|
|
15
|
+
export declare function rebuildIndex(root: string): Promise<RebuildIndexResult>;
|