@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 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.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 |",
@@ -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",
@@ -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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projitive/mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Projitive MCP Server for project and task discovery/update",
5
5
  "license": "ISC",
6
6
  "author": "",