@projitive/mcp 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -1
- package/output/index.js +1 -0
- package/output/projitive.js +140 -0
- package/output/projitive.test.js +29 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Language: English | [简体中文](README_CN.md)
|
|
4
4
|
|
|
5
|
-
**Current Spec Version: projitive-spec v1.0.0 | MCP Version: 1.0.
|
|
5
|
+
**Current Spec Version: projitive-spec v1.0.0 | MCP Version: 1.0.1**
|
|
6
6
|
|
|
7
7
|
Projitive MCP server (semantic interface edition) helps agents discover projects, select tasks, locate evidence, and execute under governance workflows.
|
|
8
8
|
|
|
@@ -109,6 +109,34 @@ node /absolute/path/to/packages/mcp/output/index.js
|
|
|
109
109
|
|
|
110
110
|
### Discovery Layer
|
|
111
111
|
|
|
112
|
+
#### `projectInit`
|
|
113
|
+
|
|
114
|
+
- **Purpose**: manually initialize governance directory structure for a project (default `.projitive`).
|
|
115
|
+
- **Input**: `rootPath?`, `governanceDir?`, `force?`
|
|
116
|
+
- **Output Example (Markdown)**:
|
|
117
|
+
|
|
118
|
+
```markdown
|
|
119
|
+
# projectInit
|
|
120
|
+
|
|
121
|
+
## Summary
|
|
122
|
+
- rootPath: /workspace/proj-a
|
|
123
|
+
- governanceDir: /workspace/proj-a/.projitive
|
|
124
|
+
- markerPath: /workspace/proj-a/.projitive/.projitive
|
|
125
|
+
- force: false
|
|
126
|
+
|
|
127
|
+
## Evidence
|
|
128
|
+
- createdFiles: 4
|
|
129
|
+
- updatedFiles: 0
|
|
130
|
+
- skippedFiles: 0
|
|
131
|
+
|
|
132
|
+
## Agent Guidance
|
|
133
|
+
- If files were skipped and you want to overwrite templates, rerun with force=true.
|
|
134
|
+
- Continue with projectContext and taskList for execution.
|
|
135
|
+
|
|
136
|
+
## Next Call
|
|
137
|
+
- projectContext(projectPath="/workspace/proj-a/.projitive")
|
|
138
|
+
```
|
|
139
|
+
|
|
112
140
|
#### `projectNext`
|
|
113
141
|
|
|
114
142
|
- **Purpose**: directly list recently actionable projects (ranked by actionable task count and recency).
|
package/output/index.js
CHANGED
|
@@ -46,6 +46,7 @@ function renderMethodCatalogMarkdown() {
|
|
|
46
46
|
"## Methods",
|
|
47
47
|
"| Group | Method | Role |",
|
|
48
48
|
"|---|---|---|",
|
|
49
|
+
"| Project | projectInit | initialize governance directory structure |",
|
|
49
50
|
"| Project | projectScan | discover governance projects by marker |",
|
|
50
51
|
"| Project | projectNext | rank actionable projects |",
|
|
51
52
|
"| Project | projectLocate | resolve nearest governance root |",
|
package/output/projitive.js
CHANGED
|
@@ -6,6 +6,7 @@ import { discoverGovernanceArtifacts } from "./helpers/files/index.js";
|
|
|
6
6
|
import { catchIt } from "./helpers/catch/index.js";
|
|
7
7
|
import { loadTasks } from "./tasks.js";
|
|
8
8
|
export const PROJECT_MARKER = ".projitive";
|
|
9
|
+
const DEFAULT_GOVERNANCE_DIR = ".projitive";
|
|
9
10
|
const ignoreNames = new Set(["node_modules", ".git", ".next", "dist", "build"]);
|
|
10
11
|
const DEFAULT_SCAN_DEPTH = 3;
|
|
11
12
|
const MAX_SCAN_DEPTH = 8;
|
|
@@ -17,6 +18,22 @@ function asText(markdown) {
|
|
|
17
18
|
function normalizePath(inputPath) {
|
|
18
19
|
return inputPath ? path.resolve(inputPath) : process.cwd();
|
|
19
20
|
}
|
|
21
|
+
function normalizeGovernanceDirName(input) {
|
|
22
|
+
const name = input?.trim() || DEFAULT_GOVERNANCE_DIR;
|
|
23
|
+
if (!name) {
|
|
24
|
+
throw new Error("governanceDir cannot be empty");
|
|
25
|
+
}
|
|
26
|
+
if (path.isAbsolute(name)) {
|
|
27
|
+
throw new Error("governanceDir must be a relative directory name");
|
|
28
|
+
}
|
|
29
|
+
if (name.includes("/") || name.includes("\\")) {
|
|
30
|
+
throw new Error("governanceDir must not contain path separators");
|
|
31
|
+
}
|
|
32
|
+
if (name === "." || name === "..") {
|
|
33
|
+
throw new Error("governanceDir must be a normal directory name");
|
|
34
|
+
}
|
|
35
|
+
return name;
|
|
36
|
+
}
|
|
20
37
|
function parseDepthFromEnv(rawDepth) {
|
|
21
38
|
if (typeof rawDepth !== "string" || rawDepth.trim().length === 0) {
|
|
22
39
|
return undefined;
|
|
@@ -134,7 +151,130 @@ export async function discoverProjects(rootPath, maxDepth) {
|
|
|
134
151
|
await walk(rootPath, 0);
|
|
135
152
|
return Array.from(new Set(results)).sort();
|
|
136
153
|
}
|
|
154
|
+
async function pathExists(targetPath) {
|
|
155
|
+
const accessResult = await catchIt(fs.access(targetPath));
|
|
156
|
+
return !accessResult.isError();
|
|
157
|
+
}
|
|
158
|
+
async function writeTextFile(targetPath, content, force) {
|
|
159
|
+
const exists = await pathExists(targetPath);
|
|
160
|
+
if (exists && !force) {
|
|
161
|
+
return { path: targetPath, action: "skipped" };
|
|
162
|
+
}
|
|
163
|
+
await fs.writeFile(targetPath, content, "utf-8");
|
|
164
|
+
return { path: targetPath, action: exists ? "updated" : "created" };
|
|
165
|
+
}
|
|
166
|
+
function defaultReadmeMarkdown(governanceDirName) {
|
|
167
|
+
return [
|
|
168
|
+
"# Projitive Governance Workspace",
|
|
169
|
+
"",
|
|
170
|
+
`This directory (\`${governanceDirName}/\`) is the governance root for this project.`,
|
|
171
|
+
"",
|
|
172
|
+
"## Conventions",
|
|
173
|
+
"- Keep roadmap/task/design/report files in markdown.",
|
|
174
|
+
"- Keep IDs stable (TASK-xxxx / ROADMAP-xxxx).",
|
|
175
|
+
"- Update report evidence before status transitions.",
|
|
176
|
+
].join("\n");
|
|
177
|
+
}
|
|
178
|
+
function defaultRoadmapMarkdown() {
|
|
179
|
+
return [
|
|
180
|
+
"# Roadmap",
|
|
181
|
+
"",
|
|
182
|
+
"## Active Milestones",
|
|
183
|
+
"- [ ] ROADMAP-0001: Bootstrap governance baseline (time: 2026-Q1)",
|
|
184
|
+
].join("\n");
|
|
185
|
+
}
|
|
186
|
+
function defaultTasksMarkdown() {
|
|
187
|
+
const updatedAt = new Date().toISOString();
|
|
188
|
+
return [
|
|
189
|
+
"# Tasks",
|
|
190
|
+
"",
|
|
191
|
+
"<!-- PROJITIVE:TASKS:START -->",
|
|
192
|
+
"## TASK-0001 | TODO | Bootstrap governance workspace",
|
|
193
|
+
"- owner: unassigned",
|
|
194
|
+
"- summary: Create initial governance artifacts and confirm task execution loop.",
|
|
195
|
+
`- updatedAt: ${updatedAt}`,
|
|
196
|
+
"- links:",
|
|
197
|
+
"- roadmapRefs: ROADMAP-0001",
|
|
198
|
+
"- hooks:",
|
|
199
|
+
"<!-- PROJITIVE:TASKS:END -->",
|
|
200
|
+
].join("\n");
|
|
201
|
+
}
|
|
202
|
+
export async function initializeProjectStructure(inputPath, governanceDir, force = false) {
|
|
203
|
+
const rootPath = normalizePath(inputPath);
|
|
204
|
+
const governanceDirName = normalizeGovernanceDirName(governanceDir);
|
|
205
|
+
const rootStat = await catchIt(fs.stat(rootPath));
|
|
206
|
+
if (rootStat.isError()) {
|
|
207
|
+
throw new Error(`Path not found: ${rootPath}`);
|
|
208
|
+
}
|
|
209
|
+
if (!rootStat.value.isDirectory()) {
|
|
210
|
+
throw new Error(`rootPath must be a directory: ${rootPath}`);
|
|
211
|
+
}
|
|
212
|
+
const governancePath = path.join(rootPath, governanceDirName);
|
|
213
|
+
const directories = [];
|
|
214
|
+
const requiredDirectories = [governancePath, path.join(governancePath, "designs"), path.join(governancePath, "reports"), path.join(governancePath, "hooks")];
|
|
215
|
+
for (const dirPath of requiredDirectories) {
|
|
216
|
+
const exists = await pathExists(dirPath);
|
|
217
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
218
|
+
directories.push({ path: dirPath, action: exists ? "skipped" : "created" });
|
|
219
|
+
}
|
|
220
|
+
const markerPath = path.join(governancePath, PROJECT_MARKER);
|
|
221
|
+
const files = await Promise.all([
|
|
222
|
+
writeTextFile(markerPath, "", force),
|
|
223
|
+
writeTextFile(path.join(governancePath, "README.md"), defaultReadmeMarkdown(governanceDirName), force),
|
|
224
|
+
writeTextFile(path.join(governancePath, "roadmap.md"), defaultRoadmapMarkdown(), force),
|
|
225
|
+
writeTextFile(path.join(governancePath, "tasks.md"), defaultTasksMarkdown(), force),
|
|
226
|
+
]);
|
|
227
|
+
return {
|
|
228
|
+
rootPath,
|
|
229
|
+
governanceDir: governancePath,
|
|
230
|
+
markerPath,
|
|
231
|
+
directories,
|
|
232
|
+
files,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
137
235
|
export function registerProjectTools(server) {
|
|
236
|
+
server.registerTool("projectInit", {
|
|
237
|
+
title: "Project Init",
|
|
238
|
+
description: "Initialize Projitive governance directory structure manually (default .projitive)",
|
|
239
|
+
inputSchema: {
|
|
240
|
+
rootPath: z.string().optional(),
|
|
241
|
+
governanceDir: z.string().optional(),
|
|
242
|
+
force: z.boolean().optional(),
|
|
243
|
+
},
|
|
244
|
+
}, async ({ rootPath, governanceDir, force }) => {
|
|
245
|
+
const initialized = await initializeProjectStructure(rootPath, governanceDir, force ?? false);
|
|
246
|
+
const filesByAction = {
|
|
247
|
+
created: initialized.files.filter((item) => item.action === "created"),
|
|
248
|
+
updated: initialized.files.filter((item) => item.action === "updated"),
|
|
249
|
+
skipped: initialized.files.filter((item) => item.action === "skipped"),
|
|
250
|
+
};
|
|
251
|
+
const markdown = [
|
|
252
|
+
"# projectInit",
|
|
253
|
+
"",
|
|
254
|
+
"## Summary",
|
|
255
|
+
`- rootPath: ${initialized.rootPath}`,
|
|
256
|
+
`- governanceDir: ${initialized.governanceDir}`,
|
|
257
|
+
`- markerPath: ${initialized.markerPath}`,
|
|
258
|
+
`- force: ${force === true ? "true" : "false"}`,
|
|
259
|
+
"",
|
|
260
|
+
"## Evidence",
|
|
261
|
+
`- createdFiles: ${filesByAction.created.length}`,
|
|
262
|
+
`- updatedFiles: ${filesByAction.updated.length}`,
|
|
263
|
+
`- skippedFiles: ${filesByAction.skipped.length}`,
|
|
264
|
+
"- directories:",
|
|
265
|
+
...initialized.directories.map((item) => ` - ${item.action}: ${item.path}`),
|
|
266
|
+
"- files:",
|
|
267
|
+
...initialized.files.map((item) => ` - ${item.action}: ${item.path}`),
|
|
268
|
+
"",
|
|
269
|
+
"## Agent Guidance",
|
|
270
|
+
"- If files were skipped and you want to overwrite templates, rerun with force=true.",
|
|
271
|
+
"- Continue with projectContext and taskList for execution.",
|
|
272
|
+
"",
|
|
273
|
+
"## Next Call",
|
|
274
|
+
`- projectContext(projectPath=\"${initialized.governanceDir}\")`,
|
|
275
|
+
].join("\n");
|
|
276
|
+
return asText(markdown);
|
|
277
|
+
});
|
|
138
278
|
server.registerTool("projectScan", {
|
|
139
279
|
title: "Project Scan",
|
|
140
280
|
description: "Scan filesystem and discover project governance roots marked by .projitive",
|
package/output/projitive.test.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { discoverProjects, hasProjectMarker, resolveGovernanceDir } from "./projitive.js";
|
|
5
|
+
import { discoverProjects, hasProjectMarker, initializeProjectStructure, resolveGovernanceDir } from "./projitive.js";
|
|
6
6
|
const tempPaths = [];
|
|
7
7
|
async function createTempDir() {
|
|
8
8
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "projitive-mcp-test-"));
|
|
@@ -43,4 +43,32 @@ describe("projitive module", () => {
|
|
|
43
43
|
expect(projects).toContain(p1);
|
|
44
44
|
expect(projects).toContain(p2);
|
|
45
45
|
});
|
|
46
|
+
it("initializes governance structure under default .projitive directory", async () => {
|
|
47
|
+
const root = await createTempDir();
|
|
48
|
+
const initialized = await initializeProjectStructure(root);
|
|
49
|
+
expect(initialized.governanceDir).toBe(path.join(root, ".projitive"));
|
|
50
|
+
const expectedPaths = [
|
|
51
|
+
path.join(root, ".projitive", ".projitive"),
|
|
52
|
+
path.join(root, ".projitive", "README.md"),
|
|
53
|
+
path.join(root, ".projitive", "roadmap.md"),
|
|
54
|
+
path.join(root, ".projitive", "tasks.md"),
|
|
55
|
+
path.join(root, ".projitive", "designs"),
|
|
56
|
+
path.join(root, ".projitive", "reports"),
|
|
57
|
+
path.join(root, ".projitive", "hooks"),
|
|
58
|
+
];
|
|
59
|
+
await Promise.all(expectedPaths.map(async (targetPath) => {
|
|
60
|
+
await expect(fs.access(targetPath)).resolves.toBeUndefined();
|
|
61
|
+
}));
|
|
62
|
+
});
|
|
63
|
+
it("overwrites template files when force is enabled", async () => {
|
|
64
|
+
const root = await createTempDir();
|
|
65
|
+
const governanceDir = path.join(root, ".projitive");
|
|
66
|
+
const readmePath = path.join(governanceDir, "README.md");
|
|
67
|
+
await initializeProjectStructure(root);
|
|
68
|
+
await fs.writeFile(readmePath, "custom-content", "utf-8");
|
|
69
|
+
const initialized = await initializeProjectStructure(root, ".projitive", true);
|
|
70
|
+
const readmeContent = await fs.readFile(readmePath, "utf-8");
|
|
71
|
+
expect(readmeContent).toContain("Projitive Governance Workspace");
|
|
72
|
+
expect(initialized.files.find((item) => item.path === readmePath)?.action).toBe("updated");
|
|
73
|
+
});
|
|
46
74
|
});
|