@projitive/mcp 1.1.2 → 2.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.
@@ -0,0 +1,68 @@
1
+ import { MIGRATION_STEPS } from "./steps.js";
2
+ function setStoreSchemaVersion(db, nextVersion) {
3
+ const statement = db.prepare(`
4
+ INSERT INTO meta (key, value)
5
+ VALUES (?, ?)
6
+ ON CONFLICT(key) DO UPDATE SET value=excluded.value
7
+ `);
8
+ statement.run(["store_schema_version", String(nextVersion)]);
9
+ statement.free();
10
+ }
11
+ function writeMigrationHistory(db, migrationId, fromVersion, toVersion, checksum, status, startedAt, finishedAt, errorMessage) {
12
+ const statement = db.prepare(`
13
+ INSERT INTO migration_history (
14
+ id,
15
+ from_version,
16
+ to_version,
17
+ checksum,
18
+ started_at,
19
+ finished_at,
20
+ status,
21
+ error_message
22
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
23
+ `);
24
+ statement.run([
25
+ migrationId,
26
+ fromVersion,
27
+ toVersion,
28
+ checksum,
29
+ startedAt,
30
+ finishedAt,
31
+ status,
32
+ errorMessage,
33
+ ]);
34
+ statement.free();
35
+ }
36
+ export function runPendingMigrations(db, currentVersion, targetVersion) {
37
+ let versionCursor = currentVersion;
38
+ if (versionCursor >= targetVersion) {
39
+ return;
40
+ }
41
+ const steps = MIGRATION_STEPS
42
+ .filter((item) => item.fromVersion >= currentVersion && item.toVersion <= targetVersion)
43
+ .sort((a, b) => a.fromVersion - b.fromVersion);
44
+ for (const step of steps) {
45
+ if (step.fromVersion !== versionCursor) {
46
+ throw new Error(`Migration chain broken at version ${versionCursor}. Missing step ${versionCursor} -> ${step.toVersion}.`);
47
+ }
48
+ const startedAt = new Date().toISOString();
49
+ try {
50
+ db.exec("BEGIN TRANSACTION;");
51
+ step.up(db);
52
+ setStoreSchemaVersion(db, step.toVersion);
53
+ const finishedAt = new Date().toISOString();
54
+ writeMigrationHistory(db, step.id, step.fromVersion, step.toVersion, step.checksum, "SUCCESS", startedAt, finishedAt, null);
55
+ db.exec("COMMIT;");
56
+ versionCursor = step.toVersion;
57
+ }
58
+ catch (error) {
59
+ db.exec("ROLLBACK;");
60
+ const finishedAt = new Date().toISOString();
61
+ writeMigrationHistory(db, step.id, step.fromVersion, step.toVersion, step.checksum, "FAILED", startedAt, finishedAt, error instanceof Error ? error.message : String(error));
62
+ throw error;
63
+ }
64
+ }
65
+ if (versionCursor !== targetVersion) {
66
+ throw new Error(`Migration target not reached. expected=${targetVersion}, actual=${versionCursor}`);
67
+ }
68
+ }
@@ -0,0 +1,55 @@
1
+ function hasColumn(db, tableName, columnName) {
2
+ const result = db.exec(`PRAGMA table_info(${tableName});`);
3
+ if (result.length === 0) {
4
+ return false;
5
+ }
6
+ const rows = result[0].values;
7
+ return rows.some((row) => String(row[1]) === columnName);
8
+ }
9
+ function ensureRecordVersionColumns(db) {
10
+ const tables = ["tasks", "roadmaps", "meta", "view_state"];
11
+ for (const tableName of tables) {
12
+ if (!hasColumn(db, tableName, "record_version")) {
13
+ db.exec(`ALTER TABLE ${tableName} ADD COLUMN record_version INTEGER NOT NULL DEFAULT 1;`);
14
+ }
15
+ }
16
+ }
17
+ function ensurePerformanceIndexes(db) {
18
+ db.exec(`
19
+ CREATE INDEX IF NOT EXISTS idx_tasks_status_updated
20
+ ON tasks(status, updated_at DESC);
21
+ CREATE INDEX IF NOT EXISTS idx_tasks_updated
22
+ ON tasks(updated_at DESC);
23
+ CREATE INDEX IF NOT EXISTS idx_roadmaps_updated
24
+ ON roadmaps(updated_at DESC);
25
+ `);
26
+ }
27
+ export const MIGRATION_STEPS = [
28
+ {
29
+ id: "20260313_baseline_v1",
30
+ fromVersion: 0,
31
+ toVersion: 1,
32
+ checksum: "baseline-v1",
33
+ up: () => {
34
+ // Baseline marker for pre-versioned stores.
35
+ },
36
+ },
37
+ {
38
+ id: "20260313_add_record_version_v2",
39
+ fromVersion: 1,
40
+ toVersion: 2,
41
+ checksum: "add-record-version-v2",
42
+ up: (db) => {
43
+ ensureRecordVersionColumns(db);
44
+ },
45
+ },
46
+ {
47
+ id: "20260313_add_indexes_v3",
48
+ fromVersion: 2,
49
+ toVersion: 3,
50
+ checksum: "add-indexes-v3",
51
+ up: (db) => {
52
+ ensurePerformanceIndexes(db);
53
+ },
54
+ },
55
+ ];
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,117 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const MESSAGE_TEMPLATE_ENV = "PROJITIVE_MESSAGE_TEMPLATE_PATH";
4
+ const CONTENT_TEMPLATE_TOKEN = "{{content}}";
5
+ function baseToolTemplateMarkdown() {
6
+ return [
7
+ "# {{tool_name}}",
8
+ "",
9
+ "## Summary",
10
+ "{{summary}}",
11
+ "",
12
+ "## Evidence",
13
+ "{{evidence}}",
14
+ "",
15
+ "## Agent Guidance",
16
+ "{{guidance}}",
17
+ "",
18
+ "## Next Call",
19
+ "{{next_call}}",
20
+ "",
21
+ "## Raw Response",
22
+ "{{content}}",
23
+ ].join("\n");
24
+ }
25
+ function contextGuideTemplateExtra() {
26
+ return [
27
+ "",
28
+ "## Common Tool Guides To Read First",
29
+ "- ./CLAUDE.md",
30
+ "- ./AGENTS.md",
31
+ "- ./.github/copilot-instructions.md",
32
+ "- ./.cursorrules",
33
+ "- ./.github/instructions/*",
34
+ "- ./.cursor/rules/*",
35
+ ];
36
+ }
37
+ function idleDiscoveryTemplateExtra() {
38
+ return [
39
+ "",
40
+ "## Idle Discovery Checklist (When No Actionable Task)",
41
+ "- Scan backlog comments: TODO / FIXME / HACK / XXX.",
42
+ "- Check lint gaps and create executable fix tasks.",
43
+ "- Check test quality gaps (missing tests, flaky tests, low-value coverage).",
44
+ "- Learn current project architecture and consolidate/update design docs in designs/.",
45
+ "- Re-run {{tool_name}} after creating 1-3 focused TODO tasks.",
46
+ ];
47
+ }
48
+ function commitReminderTemplateExtra() {
49
+ return [
50
+ "",
51
+ "## Commit Reminder",
52
+ "- After this update, create a commit to keep progress auditable.",
53
+ "- Recommended format: type(scope): summary",
54
+ "- Example: feat(task): complete TASK-0007 validation flow",
55
+ "- Footer suggestion: Refs: TASK-0007, ROADMAP-0002",
56
+ ];
57
+ }
58
+ export function getDefaultToolTemplateMarkdown(toolName) {
59
+ const base = baseToolTemplateMarkdown().split("\n");
60
+ if (toolName === "taskNext") {
61
+ return [...base, ...idleDiscoveryTemplateExtra()].join("\n");
62
+ }
63
+ if (toolName === "projectContext" || toolName === "taskContext" || toolName === "roadmapContext") {
64
+ return [...base, ...contextGuideTemplateExtra()].join("\n");
65
+ }
66
+ if (toolName === "taskUpdate" || toolName === "roadmapUpdate") {
67
+ return [...base, ...commitReminderTemplateExtra()].join("\n");
68
+ }
69
+ return base.join("\n");
70
+ }
71
+ function loadTemplateFile(templatePath) {
72
+ try {
73
+ const content = fs.readFileSync(templatePath, "utf-8").trim();
74
+ return content.length > 0 ? content : undefined;
75
+ }
76
+ catch {
77
+ return undefined;
78
+ }
79
+ }
80
+ function ensureTemplateFile(templatePath, toolName) {
81
+ const existing = loadTemplateFile(templatePath);
82
+ if (existing) {
83
+ return existing;
84
+ }
85
+ fs.mkdirSync(path.dirname(templatePath), { recursive: true });
86
+ const generated = getDefaultToolTemplateMarkdown(toolName);
87
+ fs.writeFileSync(templatePath, `${generated}\n`, "utf-8");
88
+ return generated;
89
+ }
90
+ function resolveTemplateTarget(toolName) {
91
+ const configuredPath = process.env[MESSAGE_TEMPLATE_ENV]?.trim();
92
+ if (!configuredPath) {
93
+ return path.resolve(process.cwd(), ".projitive", "templates", "tools", `${toolName}.md`);
94
+ }
95
+ const absolutePath = path.resolve(configuredPath);
96
+ try {
97
+ const stat = fs.statSync(absolutePath);
98
+ if (stat.isDirectory()) {
99
+ return path.join(absolutePath, `${toolName}.md`);
100
+ }
101
+ return absolutePath;
102
+ }
103
+ catch {
104
+ const ext = path.extname(absolutePath).toLowerCase();
105
+ if (ext === ".md") {
106
+ return absolutePath;
107
+ }
108
+ return path.join(absolutePath, `${toolName}.md`);
109
+ }
110
+ }
111
+ function loadMessageTemplate(toolName) {
112
+ const templatePath = resolveTemplateTarget(toolName);
113
+ return ensureTemplateFile(templatePath, toolName);
114
+ }
1
115
  export function asText(markdown) {
2
116
  return {
3
117
  content: [{ type: "text", text: markdown }],
@@ -49,17 +163,49 @@ export function lintSection(lines) {
49
163
  export function nextCallSection(nextCall) {
50
164
  return section("Next Call", nextCall ? [nextCall] : []);
51
165
  }
166
+ function toSectionText(section) {
167
+ if (!section) {
168
+ return "- (none)";
169
+ }
170
+ return withFallback(section.lines).join("\n");
171
+ }
172
+ function resolveSection(payload, title) {
173
+ return payload.sections.find((item) => item.title === title);
174
+ }
175
+ function buildToolTemplateVariables(payload, classicMarkdown) {
176
+ return {
177
+ tool_name: payload.toolName,
178
+ content: classicMarkdown,
179
+ summary: toSectionText(resolveSection(payload, "Summary")),
180
+ evidence: toSectionText(resolveSection(payload, "Evidence")),
181
+ guidance: toSectionText(resolveSection(payload, "Agent Guidance")),
182
+ next_call: toSectionText(resolveSection(payload, "Next Call")),
183
+ };
184
+ }
185
+ function applyTemplateVariables(template, variables) {
186
+ let rendered = template;
187
+ for (const [key, value] of Object.entries(variables)) {
188
+ rendered = rendered.split(`{{${key}}}`).join(value);
189
+ }
190
+ if (!rendered.includes(variables.content) && !template.includes(CONTENT_TEMPLATE_TOKEN)) {
191
+ rendered = `${rendered}\n\n${variables.content}`;
192
+ }
193
+ return rendered.trimEnd();
194
+ }
52
195
  export function renderToolResponseMarkdown(payload) {
53
196
  const body = payload.sections.flatMap((section) => [
54
197
  `## ${section.title}`,
55
198
  ...withFallback(section.lines),
56
199
  "",
57
200
  ]);
58
- return [
201
+ const classicMarkdown = [
59
202
  `# ${payload.toolName}`,
60
203
  "",
61
204
  ...body,
62
205
  ].join("\n").trimEnd();
206
+ const template = loadMessageTemplate(payload.toolName);
207
+ const variables = buildToolTemplateVariables(payload, classicMarkdown);
208
+ return applyTemplateVariables(template, variables);
63
209
  }
64
210
  export function renderErrorMarkdown(toolName, cause, nextSteps, retryExample) {
65
211
  return renderToolResponseMarkdown({