@projitive/mcp 1.0.8 → 1.1.2
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/output/package.json +4 -1
- package/output/source/{helpers/catch → common}/catch.js +6 -6
- package/output/source/{helpers/catch → common}/catch.test.js +6 -6
- package/output/source/common/errors.js +120 -0
- package/output/source/{helpers/files → common}/files.js +1 -1
- package/output/source/{helpers/files → common}/files.test.js +1 -1
- package/output/source/common/index.js +9 -0
- package/output/source/{helpers/linter/codes.js → common/linter.js} +13 -0
- package/output/source/{helpers/markdown → common}/markdown.test.js +1 -1
- package/output/source/{helpers/response → common}/response.test.js +1 -1
- package/output/source/common/types.js +7 -0
- package/output/source/common/utils.js +39 -0
- package/output/source/index.js +8 -196
- package/output/source/index.test.js +110 -0
- package/output/source/prompts/index.js +9 -0
- package/output/source/prompts/quickStart.js +94 -0
- package/output/source/prompts/taskDiscovery.js +190 -0
- package/output/source/prompts/taskExecution.js +161 -0
- package/output/source/resources/designs.js +108 -0
- package/output/source/resources/designs.test.js +154 -0
- package/output/source/resources/governance.js +40 -0
- package/output/source/resources/index.js +6 -0
- package/output/source/resources/readme.test.js +167 -0
- package/output/source/{reports.js → resources/reports.js} +5 -3
- package/output/source/resources/reports.test.js +149 -0
- package/output/source/tools/index.js +8 -0
- package/output/source/{projitive.js → tools/project.js} +4 -6
- package/output/source/tools/project.test.js +322 -0
- package/output/source/{roadmap.js → tools/roadmap.js} +4 -7
- package/output/source/tools/roadmap.test.js +103 -0
- package/output/source/{tasks.js → tools/task.js} +470 -23
- package/output/source/tools/task.test.js +473 -0
- package/output/source/types.js +56 -0
- package/package.json +4 -1
- package/output/source/design-context.js +0 -515
- package/output/source/designs.js +0 -38
- package/output/source/helpers/artifacts/index.js +0 -1
- package/output/source/helpers/catch/index.js +0 -1
- package/output/source/helpers/files/index.js +0 -1
- package/output/source/helpers/index.js +0 -6
- package/output/source/helpers/linter/index.js +0 -2
- package/output/source/helpers/linter/linter.js +0 -6
- package/output/source/helpers/markdown/index.js +0 -1
- package/output/source/helpers/response/index.js +0 -1
- package/output/source/projitive.test.js +0 -111
- package/output/source/roadmap.test.js +0 -11
- package/output/source/tasks.test.js +0 -152
- /package/output/source/{helpers/artifacts → common}/artifacts.js +0 -0
- /package/output/source/{helpers/artifacts → common}/artifacts.test.js +0 -0
- /package/output/source/{helpers/linter → common}/linter.test.js +0 -0
- /package/output/source/{helpers/markdown → common}/markdown.js +0 -0
- /package/output/source/{helpers/response → common}/response.js +0 -0
- /package/output/source/{readme.js → resources/readme.js} +0 -0
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { discoverProjects, hasProjectMarker, initializeProjectStructure, resolveGovernanceDir } from "./projitive.js";
|
|
6
|
-
const tempPaths = [];
|
|
7
|
-
async function createTempDir() {
|
|
8
|
-
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "projitive-mcp-test-"));
|
|
9
|
-
tempPaths.push(dir);
|
|
10
|
-
return dir;
|
|
11
|
-
}
|
|
12
|
-
afterEach(async () => {
|
|
13
|
-
await Promise.all(tempPaths.splice(0).map(async (dir) => {
|
|
14
|
-
await fs.rm(dir, { recursive: true, force: true });
|
|
15
|
-
}));
|
|
16
|
-
});
|
|
17
|
-
describe("projitive module", () => {
|
|
18
|
-
it("does not treat marker directory as a valid project marker", async () => {
|
|
19
|
-
const root = await createTempDir();
|
|
20
|
-
const dirMarkerPath = path.join(root, ".projitive");
|
|
21
|
-
await fs.mkdir(dirMarkerPath, { recursive: true });
|
|
22
|
-
const hasMarker = await hasProjectMarker(root);
|
|
23
|
-
expect(hasMarker).toBe(false);
|
|
24
|
-
});
|
|
25
|
-
it("resolves governance dir by walking upwards for .projitive", async () => {
|
|
26
|
-
const root = await createTempDir();
|
|
27
|
-
const governanceDir = path.join(root, "repo", "governance");
|
|
28
|
-
const deepDir = path.join(governanceDir, "nested", "module");
|
|
29
|
-
await fs.mkdir(deepDir, { recursive: true });
|
|
30
|
-
await fs.writeFile(path.join(governanceDir, ".projitive"), "", "utf-8");
|
|
31
|
-
const resolved = await resolveGovernanceDir(deepDir);
|
|
32
|
-
expect(resolved).toBe(governanceDir);
|
|
33
|
-
});
|
|
34
|
-
it("resolves nested default governance dir when input path is project root", async () => {
|
|
35
|
-
const root = await createTempDir();
|
|
36
|
-
const projectRoot = path.join(root, "repo");
|
|
37
|
-
const governanceDir = path.join(projectRoot, ".projitive");
|
|
38
|
-
await fs.mkdir(governanceDir, { recursive: true });
|
|
39
|
-
await fs.writeFile(path.join(governanceDir, ".projitive"), "", "utf-8");
|
|
40
|
-
const resolved = await resolveGovernanceDir(projectRoot);
|
|
41
|
-
expect(resolved).toBe(governanceDir);
|
|
42
|
-
});
|
|
43
|
-
it("resolves nested custom governance dir when input path is project root", async () => {
|
|
44
|
-
const root = await createTempDir();
|
|
45
|
-
const projectRoot = path.join(root, "repo");
|
|
46
|
-
const governanceDir = path.join(projectRoot, "governance");
|
|
47
|
-
await fs.mkdir(governanceDir, { recursive: true });
|
|
48
|
-
await fs.writeFile(path.join(governanceDir, ".projitive"), "", "utf-8");
|
|
49
|
-
const resolved = await resolveGovernanceDir(projectRoot);
|
|
50
|
-
expect(resolved).toBe(governanceDir);
|
|
51
|
-
});
|
|
52
|
-
it("discovers projects by marker file", async () => {
|
|
53
|
-
const root = await createTempDir();
|
|
54
|
-
const p1 = path.join(root, "a");
|
|
55
|
-
const p2 = path.join(root, "b", "c");
|
|
56
|
-
await fs.mkdir(p1, { recursive: true });
|
|
57
|
-
await fs.mkdir(p2, { recursive: true });
|
|
58
|
-
await fs.writeFile(path.join(p1, ".projitive"), "", "utf-8");
|
|
59
|
-
await fs.writeFile(path.join(p2, ".projitive"), "", "utf-8");
|
|
60
|
-
const projects = await discoverProjects(root, 4);
|
|
61
|
-
expect(projects).toContain(p1);
|
|
62
|
-
expect(projects).toContain(p2);
|
|
63
|
-
});
|
|
64
|
-
it("discovers nested default governance directory under project root", async () => {
|
|
65
|
-
const root = await createTempDir();
|
|
66
|
-
const projectRoot = path.join(root, "app");
|
|
67
|
-
const governanceDir = path.join(projectRoot, ".projitive");
|
|
68
|
-
await fs.mkdir(governanceDir, { recursive: true });
|
|
69
|
-
await fs.writeFile(path.join(governanceDir, ".projitive"), "", "utf-8");
|
|
70
|
-
const projects = await discoverProjects(root, 3);
|
|
71
|
-
expect(projects).toContain(governanceDir);
|
|
72
|
-
});
|
|
73
|
-
it("discovers nested custom governance directory under project root", async () => {
|
|
74
|
-
const root = await createTempDir();
|
|
75
|
-
const projectRoot = path.join(root, "app");
|
|
76
|
-
const governanceDir = path.join(projectRoot, "governance");
|
|
77
|
-
await fs.mkdir(governanceDir, { recursive: true });
|
|
78
|
-
await fs.writeFile(path.join(governanceDir, ".projitive"), "", "utf-8");
|
|
79
|
-
const projects = await discoverProjects(root, 3);
|
|
80
|
-
expect(projects).toContain(governanceDir);
|
|
81
|
-
});
|
|
82
|
-
it("initializes governance structure under default .projitive directory", async () => {
|
|
83
|
-
const root = await createTempDir();
|
|
84
|
-
const initialized = await initializeProjectStructure(root);
|
|
85
|
-
expect(initialized.governanceDir).toBe(path.join(root, ".projitive"));
|
|
86
|
-
const expectedPaths = [
|
|
87
|
-
path.join(root, ".projitive", ".projitive"),
|
|
88
|
-
path.join(root, ".projitive", "README.md"),
|
|
89
|
-
path.join(root, ".projitive", "roadmap.md"),
|
|
90
|
-
path.join(root, ".projitive", "tasks.md"),
|
|
91
|
-
path.join(root, ".projitive", "hooks", "task_no_actionable.md"),
|
|
92
|
-
path.join(root, ".projitive", "designs"),
|
|
93
|
-
path.join(root, ".projitive", "reports"),
|
|
94
|
-
path.join(root, ".projitive", "hooks"),
|
|
95
|
-
];
|
|
96
|
-
await Promise.all(expectedPaths.map(async (targetPath) => {
|
|
97
|
-
await expect(fs.access(targetPath)).resolves.toBeUndefined();
|
|
98
|
-
}));
|
|
99
|
-
});
|
|
100
|
-
it("overwrites template files when force is enabled", async () => {
|
|
101
|
-
const root = await createTempDir();
|
|
102
|
-
const governanceDir = path.join(root, ".projitive");
|
|
103
|
-
const readmePath = path.join(governanceDir, "README.md");
|
|
104
|
-
await initializeProjectStructure(root);
|
|
105
|
-
await fs.writeFile(readmePath, "custom-content", "utf-8");
|
|
106
|
-
const initialized = await initializeProjectStructure(root, ".projitive", true);
|
|
107
|
-
const readmeContent = await fs.readFile(readmePath, "utf-8");
|
|
108
|
-
expect(readmeContent).toContain("Projitive Governance Workspace");
|
|
109
|
-
expect(initialized.files.find((item) => item.path === readmePath)?.action).toBe("updated");
|
|
110
|
-
});
|
|
111
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { collectRoadmapLintSuggestions } from "./roadmap.js";
|
|
3
|
-
import { normalizeTask } from "./tasks.js";
|
|
4
|
-
describe("roadmap lint rendering alignment", () => {
|
|
5
|
-
it("renders roadmap lint in code-prefixed markdown lines", () => {
|
|
6
|
-
const lint = collectRoadmapLintSuggestions(["ROADMAP-0001"], [
|
|
7
|
-
normalizeTask({ id: "TASK-0001", title: "x", status: "TODO", roadmapRefs: [] }),
|
|
8
|
-
]);
|
|
9
|
-
expect(lint.some((line) => line.startsWith("- [ROADMAP_TASK_REFS_EMPTY]"))).toBe(true);
|
|
10
|
-
});
|
|
11
|
-
});
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { TASKS_END, TASKS_START, collectTaskLintSuggestions, isValidTaskId, normalizeTask, parseTasksBlock, rankActionableTaskCandidates, resolveNoTaskDiscoveryGuidance, renderTaskSeedTemplate, renderTasksMarkdown, taskPriority, toTaskUpdatedAtMs, validateTransition, } from "./tasks.js";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
function buildCandidate(partial) {
|
|
7
|
-
const task = normalizeTask({
|
|
8
|
-
id: partial.id,
|
|
9
|
-
title: partial.title,
|
|
10
|
-
status: partial.status,
|
|
11
|
-
updatedAt: partial.task?.updatedAt ?? "2026-01-01T00:00:00.000Z",
|
|
12
|
-
});
|
|
13
|
-
return {
|
|
14
|
-
governanceDir: partial.governanceDir ?? "/workspace/a",
|
|
15
|
-
tasksPath: partial.tasksPath ?? "/workspace/a/tasks.md",
|
|
16
|
-
task,
|
|
17
|
-
projectScore: partial.projectScore ?? 1,
|
|
18
|
-
projectLatestUpdatedAt: partial.projectLatestUpdatedAt ?? "2026-01-01T00:00:00.000Z",
|
|
19
|
-
taskUpdatedAtMs: partial.taskUpdatedAtMs ?? toTaskUpdatedAtMs(task.updatedAt),
|
|
20
|
-
taskPriority: partial.taskPriority ?? taskPriority(task.status),
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
describe("tasks module", () => {
|
|
24
|
-
it("parses markdown task block and normalizes task fields", async () => {
|
|
25
|
-
const markdown = [
|
|
26
|
-
"# Tasks",
|
|
27
|
-
TASKS_START,
|
|
28
|
-
"## TASK-0001 | TODO | hello",
|
|
29
|
-
"- owner: alice",
|
|
30
|
-
"- summary: first task",
|
|
31
|
-
"- updatedAt: 2026-02-17T00:00:00.000Z",
|
|
32
|
-
"- roadmapRefs: ROADMAP-0001",
|
|
33
|
-
"- links:",
|
|
34
|
-
" - ./designs/example.md",
|
|
35
|
-
TASKS_END,
|
|
36
|
-
].join("\n");
|
|
37
|
-
const tasks = await parseTasksBlock(markdown);
|
|
38
|
-
expect(tasks).toHaveLength(1);
|
|
39
|
-
expect(tasks[0].id).toBe("TASK-0001");
|
|
40
|
-
expect(tasks[0].status).toBe("TODO");
|
|
41
|
-
expect(tasks[0].roadmapRefs).toEqual(["ROADMAP-0001"]);
|
|
42
|
-
expect(tasks[0].links).toEqual(["./designs/example.md"]);
|
|
43
|
-
});
|
|
44
|
-
it("renders markdown containing markers", () => {
|
|
45
|
-
const task = normalizeTask({ id: "TASK-0002", title: "render", status: "IN_PROGRESS" });
|
|
46
|
-
const markdown = renderTasksMarkdown([task]);
|
|
47
|
-
expect(markdown.includes(TASKS_START)).toBe(true);
|
|
48
|
-
expect(markdown.includes(TASKS_END)).toBe(true);
|
|
49
|
-
expect(markdown.includes("## TASK-0002 | IN_PROGRESS | render")).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
it("validates task IDs", () => {
|
|
52
|
-
expect(isValidTaskId("TASK-0001")).toBe(true);
|
|
53
|
-
expect(isValidTaskId("TASK-001")).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
it("allows and rejects expected transitions", () => {
|
|
56
|
-
expect(validateTransition("TODO", "IN_PROGRESS")).toBe(true);
|
|
57
|
-
expect(validateTransition("IN_PROGRESS", "DONE")).toBe(true);
|
|
58
|
-
expect(validateTransition("DONE", "IN_PROGRESS")).toBe(false);
|
|
59
|
-
});
|
|
60
|
-
it("assigns priority for actionable statuses", () => {
|
|
61
|
-
expect(taskPriority("IN_PROGRESS")).toBe(2);
|
|
62
|
-
expect(taskPriority("TODO")).toBe(1);
|
|
63
|
-
expect(taskPriority("BLOCKED")).toBe(0);
|
|
64
|
-
});
|
|
65
|
-
it("returns zero timestamp for invalid date", () => {
|
|
66
|
-
expect(toTaskUpdatedAtMs("invalid")).toBe(0);
|
|
67
|
-
});
|
|
68
|
-
it("ranks by project score, then task priority, then recency", () => {
|
|
69
|
-
const candidates = [
|
|
70
|
-
buildCandidate({ id: "TASK-0001", title: "A", status: "TODO", projectScore: 2 }),
|
|
71
|
-
buildCandidate({ id: "TASK-0002", title: "B", status: "IN_PROGRESS", projectScore: 2 }),
|
|
72
|
-
buildCandidate({ id: "TASK-0003", title: "C", status: "IN_PROGRESS", projectScore: 3 }),
|
|
73
|
-
];
|
|
74
|
-
const ranked = rankActionableTaskCandidates(candidates);
|
|
75
|
-
expect(ranked[0].task.id).toBe("TASK-0003");
|
|
76
|
-
expect(ranked[1].task.id).toBe("TASK-0002");
|
|
77
|
-
expect(ranked[2].task.id).toBe("TASK-0001");
|
|
78
|
-
});
|
|
79
|
-
it("renders lint lines with stable code prefix", () => {
|
|
80
|
-
const task = normalizeTask({
|
|
81
|
-
id: "TASK-0001",
|
|
82
|
-
title: "lint",
|
|
83
|
-
status: "IN_PROGRESS",
|
|
84
|
-
owner: "",
|
|
85
|
-
roadmapRefs: [],
|
|
86
|
-
});
|
|
87
|
-
const lint = collectTaskLintSuggestions([task]);
|
|
88
|
-
expect(lint.some((line) => line.startsWith("- [TASK_IN_PROGRESS_OWNER_EMPTY]"))).toBe(true);
|
|
89
|
-
expect(lint.some((line) => line.startsWith("- [TASK_ROADMAP_REFS_EMPTY]"))).toBe(true);
|
|
90
|
-
});
|
|
91
|
-
it("scopes outside-marker lint to provided task IDs", () => {
|
|
92
|
-
const tasks = [
|
|
93
|
-
normalizeTask({ id: "TASK-0001", title: "A", status: "TODO", roadmapRefs: ["ROADMAP-0001"] }),
|
|
94
|
-
normalizeTask({ id: "TASK-0002", title: "B", status: "TODO", roadmapRefs: ["ROADMAP-0001"] }),
|
|
95
|
-
];
|
|
96
|
-
const markdown = [
|
|
97
|
-
"# Tasks",
|
|
98
|
-
"TASK-0002 outside",
|
|
99
|
-
"TASK-0003 outside",
|
|
100
|
-
TASKS_START,
|
|
101
|
-
"## TASK-0001 | TODO | A",
|
|
102
|
-
"- owner: (none)",
|
|
103
|
-
"- summary: (none)",
|
|
104
|
-
"- updatedAt: 2026-02-18T00:00:00.000Z",
|
|
105
|
-
"- roadmapRefs: ROADMAP-0001",
|
|
106
|
-
"- links:",
|
|
107
|
-
" - (none)",
|
|
108
|
-
"## TASK-0002 | TODO | B",
|
|
109
|
-
"- owner: (none)",
|
|
110
|
-
"- summary: (none)",
|
|
111
|
-
"- updatedAt: 2026-02-18T00:00:00.000Z",
|
|
112
|
-
"- roadmapRefs: ROADMAP-0001",
|
|
113
|
-
"- links:",
|
|
114
|
-
" - (none)",
|
|
115
|
-
TASKS_END,
|
|
116
|
-
].join("\n");
|
|
117
|
-
const scoped = collectTaskLintSuggestions(tasks, markdown, new Set(["TASK-0001"]));
|
|
118
|
-
const scopedOutside = scoped.find((line) => line.includes("TASK IDs found outside marker block"));
|
|
119
|
-
expect(scopedOutside).toBeUndefined();
|
|
120
|
-
const all = collectTaskLintSuggestions(tasks, markdown);
|
|
121
|
-
const allOutside = all.find((line) => line.includes("TASK IDs found outside marker block"));
|
|
122
|
-
expect(allOutside).toContain("TASK-0002");
|
|
123
|
-
expect(allOutside).toContain("TASK-0003");
|
|
124
|
-
});
|
|
125
|
-
it("renders seed task template with provided roadmap ref", () => {
|
|
126
|
-
const lines = renderTaskSeedTemplate("ROADMAP-0099");
|
|
127
|
-
const markdown = lines.join("\n");
|
|
128
|
-
expect(markdown).toContain("## TASK-0001 | TODO | Define initial executable objective");
|
|
129
|
-
expect(markdown).toContain("- roadmapRefs: ROADMAP-0099");
|
|
130
|
-
expect(markdown).toContain("- links:");
|
|
131
|
-
expect(markdown).not.toContain("- hooks:");
|
|
132
|
-
});
|
|
133
|
-
it("uses default no-task guidance when hook file is absent", async () => {
|
|
134
|
-
const guidance = await resolveNoTaskDiscoveryGuidance("/path/that/does/not/exist");
|
|
135
|
-
expect(guidance.length).toBeGreaterThan(3);
|
|
136
|
-
expect(guidance.some((line) => line.includes("TODO/FIXME/HACK"))).toBe(true);
|
|
137
|
-
});
|
|
138
|
-
it("uses hook checklist when task_no_actionable hook exists", async () => {
|
|
139
|
-
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "projitive-mcp-test-"));
|
|
140
|
-
const hooksDir = path.join(dir, "hooks");
|
|
141
|
-
await fs.mkdir(hooksDir, { recursive: true });
|
|
142
|
-
await fs.writeFile(path.join(hooksDir, "task_no_actionable.md"), [
|
|
143
|
-
"Objective:",
|
|
144
|
-
"- custom-item-1",
|
|
145
|
-
"- custom-item-2",
|
|
146
|
-
].join("\n"), "utf-8");
|
|
147
|
-
const guidance = await resolveNoTaskDiscoveryGuidance(dir);
|
|
148
|
-
expect(guidance).toContain("- custom-item-1");
|
|
149
|
-
expect(guidance).toContain("- custom-item-2");
|
|
150
|
-
await fs.rm(dir, { recursive: true, force: true });
|
|
151
|
-
});
|
|
152
|
-
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|