@aidemd-mcp/server 0.2.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/.aide/docs/.aide +128 -0
- package/.aide/docs/agent-readable-code.md +74 -0
- package/.aide/docs/aide-spec.md +201 -0
- package/.aide/docs/aide-template.md +110 -0
- package/.aide/docs/automated-qa.md +111 -0
- package/.aide/docs/cascading-alignment.md +107 -0
- package/.aide/docs/index.md +38 -0
- package/.aide/docs/plan-aide.md +77 -0
- package/.aide/docs/plan.aide +60 -0
- package/.aide/docs/progressive-disclosure.md +72 -0
- package/.aide/docs/todo-aide.md +77 -0
- package/.aide/intent.aide +256 -0
- package/.aide/plan.aide +169 -0
- package/.aide/todo.aide +47 -0
- package/.claude/.aide +246 -0
- package/.claude/commands/aide/align.md +15 -0
- package/.claude/commands/aide/build.md +17 -0
- package/.claude/commands/aide/fix.md +20 -0
- package/.claude/commands/aide/init.md +171 -0
- package/.claude/commands/aide/plan.md +25 -0
- package/.claude/commands/aide/qa.md +25 -0
- package/.claude/commands/aide/refactor.md +29 -0
- package/.claude/commands/aide/research.md +21 -0
- package/.claude/commands/aide/spec.md +24 -0
- package/.claude/commands/aide/synthesize.md +20 -0
- package/.claude/commands/aide/update-playbook.md +18 -0
- package/.claude/commands/aide/upgrade.md +91 -0
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/dist/cli/App/index.d.ts +14 -0
- package/dist/cli/App/index.js +282 -0
- package/dist/cli/DetailPanel/index.d.ts +24 -0
- package/dist/cli/DetailPanel/index.js +57 -0
- package/dist/cli/TreePanel/index.d.ts +24 -0
- package/dist/cli/TreePanel/index.js +65 -0
- package/dist/cli/buildTreeData/index.d.ts +7 -0
- package/dist/cli/buildTreeData/index.js +51 -0
- package/dist/cli/findPrimaryIntent/index.d.ts +12 -0
- package/dist/cli/findPrimaryIntent/index.js +20 -0
- package/dist/cli/flattenTree/index.d.ts +16 -0
- package/dist/cli/flattenTree/index.js +20 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +15 -0
- package/dist/cli/init/index.d.ts +2 -0
- package/dist/cli/init/index.js +33 -0
- package/dist/cli/init/writeInitCommand/index.d.ts +13 -0
- package/dist/cli/init/writeInitCommand/index.js +25 -0
- package/dist/cli/init/writeMcpEntry/index.d.ts +12 -0
- package/dist/cli/init/writeMcpEntry/index.js +51 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +228 -0
- package/dist/tools/discover/buildAncestorChain/index.d.ts +17 -0
- package/dist/tools/discover/buildAncestorChain/index.js +98 -0
- package/dist/tools/discover/buildTree/index.d.ts +9 -0
- package/dist/tools/discover/buildTree/index.js +57 -0
- package/dist/tools/discover/index.d.ts +20 -0
- package/dist/tools/discover/index.js +49 -0
- package/dist/tools/init/applySteps/index.d.ts +30 -0
- package/dist/tools/init/applySteps/index.js +76 -0
- package/dist/tools/init/configureIde/index.d.ts +21 -0
- package/dist/tools/init/configureIde/index.js +135 -0
- package/dist/tools/init/detectFramework/index.d.ts +11 -0
- package/dist/tools/init/detectFramework/index.js +53 -0
- package/dist/tools/init/index.d.ts +46 -0
- package/dist/tools/init/index.js +99 -0
- package/dist/tools/init/initContent/index.d.ts +99 -0
- package/dist/tools/init/initContent/index.js +162 -0
- package/dist/tools/init/installAgents/index.d.ts +12 -0
- package/dist/tools/init/installAgents/index.js +60 -0
- package/dist/tools/init/installMethodologyDocs/index.d.ts +14 -0
- package/dist/tools/init/installMethodologyDocs/index.js +62 -0
- package/dist/tools/init/installSkills/index.d.ts +12 -0
- package/dist/tools/init/installSkills/index.js +60 -0
- package/dist/tools/init/provisionBrain/index.d.ts +23 -0
- package/dist/tools/init/provisionBrain/index.js +239 -0
- package/dist/tools/init/resolveBrainHints/index.d.ts +17 -0
- package/dist/tools/init/resolveBrainHints/index.js +44 -0
- package/dist/tools/init/scaffoldCommands/index.d.ts +38 -0
- package/dist/tools/init/scaffoldCommands/index.js +94 -0
- package/dist/tools/init/wireMcp/index.d.ts +16 -0
- package/dist/tools/init/wireMcp/index.js +72 -0
- package/dist/tools/init/writeMethodology/index.d.ts +20 -0
- package/dist/tools/init/writeMethodology/index.js +94 -0
- package/dist/tools/read/index.d.ts +15 -0
- package/dist/tools/read/index.js +79 -0
- package/dist/tools/scaffold/index.d.ts +22 -0
- package/dist/tools/scaffold/index.js +128 -0
- package/dist/tools/upgrade/applyFiles/index.d.ts +33 -0
- package/dist/tools/upgrade/applyFiles/index.js +65 -0
- package/dist/tools/upgrade/buildVersionsMeta/index.d.ts +20 -0
- package/dist/tools/upgrade/buildVersionsMeta/index.js +51 -0
- package/dist/tools/upgrade/checkIdeConfig/index.d.ts +24 -0
- package/dist/tools/upgrade/checkIdeConfig/index.js +134 -0
- package/dist/tools/upgrade/checkMcpConfig/index.d.ts +17 -0
- package/dist/tools/upgrade/checkMcpConfig/index.js +81 -0
- package/dist/tools/upgrade/compareFile/index.d.ts +12 -0
- package/dist/tools/upgrade/compareFile/index.js +24 -0
- package/dist/tools/upgrade/index.d.ts +24 -0
- package/dist/tools/upgrade/index.js +139 -0
- package/dist/tools/upgrade/spliceStub/index.d.ts +13 -0
- package/dist/tools/upgrade/spliceStub/index.js +91 -0
- package/dist/tools/validate/index.d.ts +18 -0
- package/dist/tools/validate/index.js +65 -0
- package/dist/types/index.d.ts +277 -0
- package/dist/types/index.js +10 -0
- package/dist/util/classify/index.d.ts +17 -0
- package/dist/util/classify/index.js +134 -0
- package/dist/util/parseBody/index.d.ts +7 -0
- package/dist/util/parseBody/index.js +43 -0
- package/dist/util/parseFrontmatter/index.d.ts +12 -0
- package/dist/util/parseFrontmatter/index.js +64 -0
- package/dist/util/scan/index.d.ts +7 -0
- package/dist/util/scan/index.js +82 -0
- package/package.json +59 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
3
|
+
import { join, dirname, basename, isAbsolute, relative } from "node:path";
|
|
4
|
+
import { classifyFile } from "../../util/classify/index.js";
|
|
5
|
+
export const ReadInput = z.object({
|
|
6
|
+
path: z.string().describe("Path to the .aide file to read"),
|
|
7
|
+
});
|
|
8
|
+
/** Extract links from .aide content: [[wikilinks]], relative paths, URLs. */
|
|
9
|
+
function extractLinks(content) {
|
|
10
|
+
const links = [];
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
const add = (link) => {
|
|
13
|
+
if (!seen.has(link)) {
|
|
14
|
+
seen.add(link);
|
|
15
|
+
links.push(link);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
// [[wikilinks]]
|
|
19
|
+
for (const match of content.matchAll(/\[\[([^\]]+)\]\]/g))
|
|
20
|
+
add(`[[${match[1]}]]`);
|
|
21
|
+
// Relative paths: ./something or ../something
|
|
22
|
+
for (const match of content.matchAll(/(?:^|\s)(\.\.?\/[^\s),\]]+)/gm))
|
|
23
|
+
add(match[1]);
|
|
24
|
+
// URLs
|
|
25
|
+
for (const match of content.matchAll(/https?:\/\/[^\s),\]>]+/g))
|
|
26
|
+
add(match[0]);
|
|
27
|
+
return links;
|
|
28
|
+
}
|
|
29
|
+
/** Find sibling .aide files in the same directory. */
|
|
30
|
+
async function findSiblings(filePath, root) {
|
|
31
|
+
const dir = dirname(filePath);
|
|
32
|
+
const currentName = basename(filePath);
|
|
33
|
+
const siblings = [];
|
|
34
|
+
try {
|
|
35
|
+
const entries = await readdir(dir);
|
|
36
|
+
for (const name of entries) {
|
|
37
|
+
if (name === currentName)
|
|
38
|
+
continue;
|
|
39
|
+
if (!name.endsWith(".aide"))
|
|
40
|
+
continue;
|
|
41
|
+
const sibPath = join(dir, name);
|
|
42
|
+
const relativePath = relative(root, sibPath).split("\\").join("/");
|
|
43
|
+
siblings.push({
|
|
44
|
+
path: sibPath,
|
|
45
|
+
relativePath,
|
|
46
|
+
type: classifyFile(name),
|
|
47
|
+
summary: "",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// skip unreadable dirs
|
|
53
|
+
}
|
|
54
|
+
return siblings;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read an .aide file with context awareness.
|
|
58
|
+
* Returns the file content, classified type, sibling specs in the same
|
|
59
|
+
* directory, and links found in the content.
|
|
60
|
+
*/
|
|
61
|
+
export default async function read(root, filePath) {
|
|
62
|
+
const resolved = isAbsolute(filePath) ? filePath : join(root, filePath);
|
|
63
|
+
let content;
|
|
64
|
+
try {
|
|
65
|
+
content = await readFile(resolved, "utf-8");
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return {
|
|
69
|
+
content: `Path not found: ${filePath}`,
|
|
70
|
+
type: "intent",
|
|
71
|
+
siblings: [],
|
|
72
|
+
links: [],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const type = classifyFile(basename(resolved));
|
|
76
|
+
const siblings = await findSiblings(resolved, root);
|
|
77
|
+
const links = extractLinks(content);
|
|
78
|
+
return { content, type, siblings, links };
|
|
79
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const ScaffoldInput: z.ZodObject<{
|
|
3
|
+
directory: z.ZodString;
|
|
4
|
+
type: z.ZodEnum<["intent", "research", "both", "todo", "plan"]>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
type: "intent" | "research" | "todo" | "plan" | "both";
|
|
7
|
+
directory: string;
|
|
8
|
+
}, {
|
|
9
|
+
type: "intent" | "research" | "todo" | "plan" | "both";
|
|
10
|
+
directory: string;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Create new .aide files with correct naming conventions.
|
|
14
|
+
* Handles auto-rename logic:
|
|
15
|
+
* - type=intent + no research.aide → creates .aide
|
|
16
|
+
* - type=intent + research.aide exists → creates intent.aide
|
|
17
|
+
* - type=research → creates research.aide; renames existing .aide to intent.aide
|
|
18
|
+
* - type=both → creates research.aide + intent.aide
|
|
19
|
+
* - type=todo → creates todo.aide
|
|
20
|
+
* - type=plan → creates plan.aide
|
|
21
|
+
*/
|
|
22
|
+
export default function scaffold(root: string, directory: string, type: "intent" | "research" | "both" | "todo" | "plan"): Promise<string>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readdir, writeFile, rename, mkdir } from "node:fs/promises";
|
|
3
|
+
import { join, isAbsolute } from "node:path";
|
|
4
|
+
export const ScaffoldInput = z.object({
|
|
5
|
+
directory: z.string().describe("Directory where the .aide file(s) will be created"),
|
|
6
|
+
type: z
|
|
7
|
+
.enum(["intent", "research", "both", "todo", "plan"])
|
|
8
|
+
.describe("Type of .aide file to create (intent, research, both, todo, or plan)"),
|
|
9
|
+
});
|
|
10
|
+
const INTENT_TEMPLATE = `# Intent Spec
|
|
11
|
+
|
|
12
|
+
## Strategy
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Implementation Contract
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## Anti-Patterns
|
|
21
|
+
|
|
22
|
+
`;
|
|
23
|
+
const RESEARCH_TEMPLATE = `# Research
|
|
24
|
+
|
|
25
|
+
## Sources
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Data Points
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Patterns
|
|
34
|
+
|
|
35
|
+
`;
|
|
36
|
+
const TODO_TEMPLATE = `# QA Re-alignment Document
|
|
37
|
+
|
|
38
|
+
- [ ]
|
|
39
|
+
`;
|
|
40
|
+
const PLAN_TEMPLATE = `---
|
|
41
|
+
intent: >
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Plan
|
|
46
|
+
|
|
47
|
+
- [ ]
|
|
48
|
+
|
|
49
|
+
## Decisions
|
|
50
|
+
|
|
51
|
+
`;
|
|
52
|
+
/** List existing .aide files in a directory. */
|
|
53
|
+
async function existingAideFiles(dir) {
|
|
54
|
+
try {
|
|
55
|
+
const entries = await readdir(dir);
|
|
56
|
+
return entries.filter((name) => name.endsWith(".aide"));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create new .aide files with correct naming conventions.
|
|
64
|
+
* Handles auto-rename logic:
|
|
65
|
+
* - type=intent + no research.aide → creates .aide
|
|
66
|
+
* - type=intent + research.aide exists → creates intent.aide
|
|
67
|
+
* - type=research → creates research.aide; renames existing .aide to intent.aide
|
|
68
|
+
* - type=both → creates research.aide + intent.aide
|
|
69
|
+
* - type=todo → creates todo.aide
|
|
70
|
+
* - type=plan → creates plan.aide
|
|
71
|
+
*/
|
|
72
|
+
export default async function scaffold(root, directory, type) {
|
|
73
|
+
const dir = isAbsolute(directory) ? directory : join(root, directory);
|
|
74
|
+
// Ensure directory exists
|
|
75
|
+
await mkdir(dir, { recursive: true });
|
|
76
|
+
const existing = await existingAideFiles(dir);
|
|
77
|
+
const actions = [];
|
|
78
|
+
if (type === "todo") {
|
|
79
|
+
const target = join(dir, "todo.aide");
|
|
80
|
+
await writeFile(target, TODO_TEMPLATE, "utf-8");
|
|
81
|
+
actions.push("Created todo.aide");
|
|
82
|
+
}
|
|
83
|
+
else if (type === "plan") {
|
|
84
|
+
const target = join(dir, "plan.aide");
|
|
85
|
+
await writeFile(target, PLAN_TEMPLATE, "utf-8");
|
|
86
|
+
actions.push("Created plan.aide");
|
|
87
|
+
}
|
|
88
|
+
else if (type === "intent") {
|
|
89
|
+
if (existing.includes("research.aide")) {
|
|
90
|
+
const target = join(dir, "intent.aide");
|
|
91
|
+
await writeFile(target, INTENT_TEMPLATE, "utf-8");
|
|
92
|
+
actions.push("Created intent.aide (research.aide exists, so using explicit name)");
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const target = join(dir, ".aide");
|
|
96
|
+
await writeFile(target, INTENT_TEMPLATE, "utf-8");
|
|
97
|
+
actions.push("Created .aide");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (type === "research") {
|
|
101
|
+
// If .aide exists, rename to intent.aide first
|
|
102
|
+
if (existing.includes(".aide") && !existing.includes("intent.aide")) {
|
|
103
|
+
await rename(join(dir, ".aide"), join(dir, "intent.aide"));
|
|
104
|
+
actions.push("Renamed .aide → intent.aide");
|
|
105
|
+
}
|
|
106
|
+
const target = join(dir, "research.aide");
|
|
107
|
+
await writeFile(target, RESEARCH_TEMPLATE, "utf-8");
|
|
108
|
+
actions.push("Created research.aide");
|
|
109
|
+
}
|
|
110
|
+
else if (type === "both") {
|
|
111
|
+
// If .aide exists, remove it since we're creating intent.aide
|
|
112
|
+
if (existing.includes(".aide") && !existing.includes("intent.aide")) {
|
|
113
|
+
await rename(join(dir, ".aide"), join(dir, "intent.aide"));
|
|
114
|
+
actions.push("Renamed .aide → intent.aide");
|
|
115
|
+
}
|
|
116
|
+
else if (!existing.includes("intent.aide")) {
|
|
117
|
+
await writeFile(join(dir, "intent.aide"), INTENT_TEMPLATE, "utf-8");
|
|
118
|
+
actions.push("Created intent.aide");
|
|
119
|
+
}
|
|
120
|
+
if (!existing.includes("research.aide")) {
|
|
121
|
+
await writeFile(join(dir, "research.aide"), RESEARCH_TEMPLATE, "utf-8");
|
|
122
|
+
actions.push("Created research.aide");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (actions.length === 0)
|
|
126
|
+
return "No changes needed — files already exist.";
|
|
127
|
+
return actions.join("\n");
|
|
128
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { UpgradeFileResult } from "../../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Apply mode writer for aide_upgrade — turns comparison results into disk state
|
|
4
|
+
* and returns the manifest the agent reports to the user.
|
|
5
|
+
*
|
|
6
|
+
* Dispatch logic per file:
|
|
7
|
+
*
|
|
8
|
+
* - `"differs"` or `"missing"`, NOT mcp category, NOT VS Code IDE:
|
|
9
|
+
* Creates parent directories and writes `canonicalContent` to `filePath`.
|
|
10
|
+
* Returns the result with `status` changed to `"updated"` (was differs) or
|
|
11
|
+
* `"created"` (was missing), and `canonicalContent` stripped.
|
|
12
|
+
*
|
|
13
|
+
* - `"differs"` or `"missing"`, mcp category:
|
|
14
|
+
* Passed through unchanged with `prescription` intact — the agent merges
|
|
15
|
+
* prescriptions into the existing MCP config itself. Never written here.
|
|
16
|
+
*
|
|
17
|
+
* - `"differs"` IDE VS Code step (name contains "VS Code"):
|
|
18
|
+
* Passed through with `instructions` field set to
|
|
19
|
+
* `code --install-extension <filePath>`. The VS Code CLI is required for
|
|
20
|
+
* extension installs — the MCP server cannot invoke it. No disk write.
|
|
21
|
+
*
|
|
22
|
+
* - `"differs"` or `"missing"` IDE Zed step:
|
|
23
|
+
* Has `canonicalContent` — written to disk normally via the regular file path.
|
|
24
|
+
*
|
|
25
|
+
* - `"matches"`:
|
|
26
|
+
* Returned as `"unchanged"` — already current, no write needed. This maps
|
|
27
|
+
* the dry-run vocabulary to the apply-mode terminal vocabulary defined in
|
|
28
|
+
* the spec (`"updated"`, `"created"`, `"unchanged"`).
|
|
29
|
+
*
|
|
30
|
+
* - `"malformed"`, `"updated"`, `"created"`, `"unchanged"`:
|
|
31
|
+
* Passed through unchanged — no action available or already applied.
|
|
32
|
+
*/
|
|
33
|
+
export default function applyFiles(files: UpgradeFileResult[]): Promise<UpgradeFileResult[]>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Apply mode writer for aide_upgrade — turns comparison results into disk state
|
|
5
|
+
* and returns the manifest the agent reports to the user.
|
|
6
|
+
*
|
|
7
|
+
* Dispatch logic per file:
|
|
8
|
+
*
|
|
9
|
+
* - `"differs"` or `"missing"`, NOT mcp category, NOT VS Code IDE:
|
|
10
|
+
* Creates parent directories and writes `canonicalContent` to `filePath`.
|
|
11
|
+
* Returns the result with `status` changed to `"updated"` (was differs) or
|
|
12
|
+
* `"created"` (was missing), and `canonicalContent` stripped.
|
|
13
|
+
*
|
|
14
|
+
* - `"differs"` or `"missing"`, mcp category:
|
|
15
|
+
* Passed through unchanged with `prescription` intact — the agent merges
|
|
16
|
+
* prescriptions into the existing MCP config itself. Never written here.
|
|
17
|
+
*
|
|
18
|
+
* - `"differs"` IDE VS Code step (name contains "VS Code"):
|
|
19
|
+
* Passed through with `instructions` field set to
|
|
20
|
+
* `code --install-extension <filePath>`. The VS Code CLI is required for
|
|
21
|
+
* extension installs — the MCP server cannot invoke it. No disk write.
|
|
22
|
+
*
|
|
23
|
+
* - `"differs"` or `"missing"` IDE Zed step:
|
|
24
|
+
* Has `canonicalContent` — written to disk normally via the regular file path.
|
|
25
|
+
*
|
|
26
|
+
* - `"matches"`:
|
|
27
|
+
* Returned as `"unchanged"` — already current, no write needed. This maps
|
|
28
|
+
* the dry-run vocabulary to the apply-mode terminal vocabulary defined in
|
|
29
|
+
* the spec (`"updated"`, `"created"`, `"unchanged"`).
|
|
30
|
+
*
|
|
31
|
+
* - `"malformed"`, `"updated"`, `"created"`, `"unchanged"`:
|
|
32
|
+
* Passed through unchanged — no action available or already applied.
|
|
33
|
+
*/
|
|
34
|
+
export default async function applyFiles(files) {
|
|
35
|
+
return Promise.all(files.map(applyFile));
|
|
36
|
+
}
|
|
37
|
+
async function applyFile(file) {
|
|
38
|
+
// Files that already match canonical are reported as "unchanged" in apply output
|
|
39
|
+
if (file.status === "matches") {
|
|
40
|
+
return { ...file, status: "unchanged" };
|
|
41
|
+
}
|
|
42
|
+
// Only act on files that need writing — differs or missing
|
|
43
|
+
if (file.status !== "differs" && file.status !== "missing") {
|
|
44
|
+
return file;
|
|
45
|
+
}
|
|
46
|
+
// MCP files carry a prescription — the agent merges them; never written here
|
|
47
|
+
if (file.category === "mcp") {
|
|
48
|
+
return file;
|
|
49
|
+
}
|
|
50
|
+
// IDE VS Code steps require the external `code` CLI — pass through with
|
|
51
|
+
// instructions so the agent knows what command to run
|
|
52
|
+
if (file.category === "ide" && file.name.includes("VS Code")) {
|
|
53
|
+
return { ...file, instructions: `code --install-extension ${file.filePath}` };
|
|
54
|
+
}
|
|
55
|
+
// Regular file step (including IDE Zed) — write canonicalContent to filePath
|
|
56
|
+
if (file.canonicalContent !== undefined) {
|
|
57
|
+
await mkdir(dirname(file.filePath), { recursive: true });
|
|
58
|
+
await writeFile(file.filePath, file.canonicalContent, "utf-8");
|
|
59
|
+
// Strip canonicalContent; map comparison status to execution status
|
|
60
|
+
const { canonicalContent: _content, ...rest } = file;
|
|
61
|
+
return { ...rest, status: file.status === "differs" ? "updated" : "created" };
|
|
62
|
+
}
|
|
63
|
+
// No content and not a special case — pass through unchanged
|
|
64
|
+
return file;
|
|
65
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Git-derived version metadata for a single methodology doc. */
|
|
2
|
+
export interface VersionMeta {
|
|
3
|
+
/** ISO 8601 author timestamp of the last commit touching this doc. */
|
|
4
|
+
publishedAt: string;
|
|
5
|
+
/** 7-char short SHA of the last commit touching this doc. */
|
|
6
|
+
sourceCommit: string;
|
|
7
|
+
/** 7-char short SHA of the prior commit touching this doc, if one exists. */
|
|
8
|
+
previousCommit?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Map of doc slug → version metadata. */
|
|
11
|
+
export type VersionsMap = Record<string, VersionMeta>;
|
|
12
|
+
/**
|
|
13
|
+
* Extract git commit metadata for each methodology doc from this repo's
|
|
14
|
+
* own history. Returns a map keyed by doc slug (filename without .md).
|
|
15
|
+
*
|
|
16
|
+
* Uses `git log --follow` so history survives file renames. Gracefully
|
|
17
|
+
* returns an empty object if git is unavailable, and omits individual
|
|
18
|
+
* slugs whose files have no commit history (e.g. newly added, untracked).
|
|
19
|
+
*/
|
|
20
|
+
export default function buildVersionsMeta(): Promise<VersionsMap>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { listMethodologyDocs } from "../../../tools/init/initContent/index.js";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
/**
|
|
8
|
+
* Repo root resolved from this module's location. Same 4-hop walk as
|
|
9
|
+
* initContent — src/ and dist/ are siblings under the repo root at the
|
|
10
|
+
* same depth (src/tools/upgrade/buildVersionsMeta or
|
|
11
|
+
* dist/tools/upgrade/buildVersionsMeta).
|
|
12
|
+
*/
|
|
13
|
+
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const REPO_ROOT = join(MODULE_DIR, "..", "..", "..", "..");
|
|
15
|
+
/**
|
|
16
|
+
* Extract git commit metadata for each methodology doc from this repo's
|
|
17
|
+
* own history. Returns a map keyed by doc slug (filename without .md).
|
|
18
|
+
*
|
|
19
|
+
* Uses `git log --follow` so history survives file renames. Gracefully
|
|
20
|
+
* returns an empty object if git is unavailable, and omits individual
|
|
21
|
+
* slugs whose files have no commit history (e.g. newly added, untracked).
|
|
22
|
+
*/
|
|
23
|
+
export default async function buildVersionsMeta() {
|
|
24
|
+
const docs = listMethodologyDocs();
|
|
25
|
+
const result = {};
|
|
26
|
+
for (const entry of docs) {
|
|
27
|
+
const slug = entry.hostFilename.replace(/\.md$/, "");
|
|
28
|
+
const docPath = `.aide/docs/${entry.hostFilename}`;
|
|
29
|
+
try {
|
|
30
|
+
const { stdout } = await execFileAsync("git", ["log", "--follow", "--format=%H %aI", "-n", "2", "--", docPath], { cwd: REPO_ROOT });
|
|
31
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
32
|
+
if (lines.length === 0)
|
|
33
|
+
continue;
|
|
34
|
+
const [latestSha, latestDate] = lines[0].split(" ", 2);
|
|
35
|
+
const meta = {
|
|
36
|
+
publishedAt: latestDate,
|
|
37
|
+
sourceCommit: latestSha.slice(0, 7),
|
|
38
|
+
};
|
|
39
|
+
if (lines.length > 1) {
|
|
40
|
+
const [priorSha] = lines[1].split(" ", 2);
|
|
41
|
+
meta.previousCommit = priorSha.slice(0, 7);
|
|
42
|
+
}
|
|
43
|
+
result[slug] = meta;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Git unavailable or command failed for this file — skip slug.
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { UpgradeFileResult } from "../../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compare the Zed `*.aide` file-type association against canonical.
|
|
4
|
+
* Read-only — never writes.
|
|
5
|
+
*
|
|
6
|
+
* Returns an `UpgradeFileResult` with category `"ide"`:
|
|
7
|
+
* - `"malformed"` when `.zed/settings.json` exists but is not valid JSON.
|
|
8
|
+
* - `"missing"` when the settings file does not exist.
|
|
9
|
+
* - `"matches"` when `*.aide` is already in `file_types.Markdown`.
|
|
10
|
+
* - `"differs"` when the file exists but the association is absent.
|
|
11
|
+
* `canonicalContent` is the full settings file with the association merged in.
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkZedConfig(projectRoot: string): Promise<UpgradeFileResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Check whether the VS Code aide-markdown extension is installed.
|
|
16
|
+
* Read-only — never installs.
|
|
17
|
+
*
|
|
18
|
+
* Returns an `UpgradeFileResult` with category `"ide"`:
|
|
19
|
+
* - `"matches"` when the extension is already installed or the `code` CLI is
|
|
20
|
+
* unavailable (cannot determine state).
|
|
21
|
+
* - `"differs"` when the extension is not installed and can be installed.
|
|
22
|
+
* `canonicalContent` is the absolute path to the bundled `.vsix` file.
|
|
23
|
+
*/
|
|
24
|
+
export declare function checkVscodeExtension(): Promise<UpgradeFileResult>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
/** Read a file, returning undefined if it does not exist. */
|
|
8
|
+
async function safeReadFile(path) {
|
|
9
|
+
try {
|
|
10
|
+
return await readFile(path, "utf-8");
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compare the Zed `*.aide` file-type association against canonical.
|
|
18
|
+
* Read-only — never writes.
|
|
19
|
+
*
|
|
20
|
+
* Returns an `UpgradeFileResult` with category `"ide"`:
|
|
21
|
+
* - `"malformed"` when `.zed/settings.json` exists but is not valid JSON.
|
|
22
|
+
* - `"missing"` when the settings file does not exist.
|
|
23
|
+
* - `"matches"` when `*.aide` is already in `file_types.Markdown`.
|
|
24
|
+
* - `"differs"` when the file exists but the association is absent.
|
|
25
|
+
* `canonicalContent` is the full settings file with the association merged in.
|
|
26
|
+
*/
|
|
27
|
+
export async function checkZedConfig(projectRoot) {
|
|
28
|
+
const name = "Zed config";
|
|
29
|
+
const settingsPath = join(projectRoot, ".zed", "settings.json");
|
|
30
|
+
const existing = await safeReadFile(settingsPath);
|
|
31
|
+
if (existing !== undefined) {
|
|
32
|
+
let settings;
|
|
33
|
+
try {
|
|
34
|
+
settings = JSON.parse(existing);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return {
|
|
38
|
+
name,
|
|
39
|
+
filePath: settingsPath,
|
|
40
|
+
status: "malformed",
|
|
41
|
+
category: "ide",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const mdTypes = settings.file_types?.Markdown ?? [];
|
|
45
|
+
if (mdTypes.includes("*.aide")) {
|
|
46
|
+
return {
|
|
47
|
+
name,
|
|
48
|
+
filePath: settingsPath,
|
|
49
|
+
status: "matches",
|
|
50
|
+
category: "ide",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Merge the association and return the full content for the agent.
|
|
54
|
+
const merged = {
|
|
55
|
+
...settings,
|
|
56
|
+
file_types: {
|
|
57
|
+
...(settings.file_types ?? {}),
|
|
58
|
+
Markdown: [...mdTypes, "*.aide"],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
name,
|
|
63
|
+
filePath: settingsPath,
|
|
64
|
+
status: "differs",
|
|
65
|
+
category: "ide",
|
|
66
|
+
canonicalContent: JSON.stringify(merged, null, 2) + "\n",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Settings file absent — canonical is a fresh file with the association.
|
|
70
|
+
const canonical = { file_types: { Markdown: ["*.aide"] } };
|
|
71
|
+
return {
|
|
72
|
+
name,
|
|
73
|
+
filePath: settingsPath,
|
|
74
|
+
status: "missing",
|
|
75
|
+
category: "ide",
|
|
76
|
+
canonicalContent: JSON.stringify(canonical, null, 2) + "\n",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check whether the VS Code aide-markdown extension is installed.
|
|
81
|
+
* Read-only — never installs.
|
|
82
|
+
*
|
|
83
|
+
* Returns an `UpgradeFileResult` with category `"ide"`:
|
|
84
|
+
* - `"matches"` when the extension is already installed or the `code` CLI is
|
|
85
|
+
* unavailable (cannot determine state).
|
|
86
|
+
* - `"differs"` when the extension is not installed and can be installed.
|
|
87
|
+
* `canonicalContent` is the absolute path to the bundled `.vsix` file.
|
|
88
|
+
*/
|
|
89
|
+
export async function checkVscodeExtension() {
|
|
90
|
+
const name = "VS Code extension";
|
|
91
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
92
|
+
const vsixPath = join(moduleDir, "..", "..", "..", "..", "extensions", "vscode", "aide-markdown-0.0.1.vsix");
|
|
93
|
+
// Ensure `code` CLI is available.
|
|
94
|
+
try {
|
|
95
|
+
await execFileAsync("code", ["--version"]);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return {
|
|
99
|
+
name,
|
|
100
|
+
filePath: vsixPath,
|
|
101
|
+
status: "matches",
|
|
102
|
+
category: "ide",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Check installation state.
|
|
106
|
+
let installed;
|
|
107
|
+
try {
|
|
108
|
+
const { stdout } = await execFileAsync("code", ["--list-extensions"]);
|
|
109
|
+
installed = stdout.toLowerCase().includes("aide-markdown");
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return {
|
|
113
|
+
name,
|
|
114
|
+
filePath: vsixPath,
|
|
115
|
+
status: "matches",
|
|
116
|
+
category: "ide",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (installed) {
|
|
120
|
+
return {
|
|
121
|
+
name,
|
|
122
|
+
filePath: vsixPath,
|
|
123
|
+
status: "matches",
|
|
124
|
+
category: "ide",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
name,
|
|
129
|
+
filePath: vsixPath,
|
|
130
|
+
status: "differs",
|
|
131
|
+
category: "ide",
|
|
132
|
+
canonicalContent: vsixPath,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { UpgradeFileResult } from "../../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compare the `aide` / `aidemd-mcp` key in an MCP config file against the
|
|
4
|
+
* canonical shape. Read-only — never writes.
|
|
5
|
+
*
|
|
6
|
+
* Returns an `UpgradeFileResult` with category `"mcp"`:
|
|
7
|
+
* - `"malformed"` when the file exists but is not valid JSON.
|
|
8
|
+
* - `"missing"` when the file does not exist. `prescription` carries the
|
|
9
|
+
* canonical server entry for the agent to merge.
|
|
10
|
+
* - `"matches"` when the aide entry already matches canonical.
|
|
11
|
+
* - `"differs"` when the aide entry is absent or differs. `prescription`
|
|
12
|
+
* carries the canonical server entry for the agent to merge.
|
|
13
|
+
*
|
|
14
|
+
* The legacy `"aidemd-mcp"` key (old unscoped package) is detected as
|
|
15
|
+
* `"differs"` so the agent can migrate it to the canonical `"aide"` key.
|
|
16
|
+
*/
|
|
17
|
+
export default function checkMcpConfig(mcpConfigPath: string): Promise<UpgradeFileResult>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
/** Canonical aide server entry that upgrade checks and reports. */
|
|
3
|
+
const CANONICAL_AIDE_SERVER = {
|
|
4
|
+
command: "npx",
|
|
5
|
+
args: ["@aidemd-mcp/server"],
|
|
6
|
+
};
|
|
7
|
+
/** Read a file, returning undefined if it does not exist. */
|
|
8
|
+
async function safeReadFile(path) {
|
|
9
|
+
try {
|
|
10
|
+
return await readFile(path, "utf-8");
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compare the `aide` / `aidemd-mcp` key in an MCP config file against the
|
|
18
|
+
* canonical shape. Read-only — never writes.
|
|
19
|
+
*
|
|
20
|
+
* Returns an `UpgradeFileResult` with category `"mcp"`:
|
|
21
|
+
* - `"malformed"` when the file exists but is not valid JSON.
|
|
22
|
+
* - `"missing"` when the file does not exist. `prescription` carries the
|
|
23
|
+
* canonical server entry for the agent to merge.
|
|
24
|
+
* - `"matches"` when the aide entry already matches canonical.
|
|
25
|
+
* - `"differs"` when the aide entry is absent or differs. `prescription`
|
|
26
|
+
* carries the canonical server entry for the agent to merge.
|
|
27
|
+
*
|
|
28
|
+
* The legacy `"aidemd-mcp"` key (old unscoped package) is detected as
|
|
29
|
+
* `"differs"` so the agent can migrate it to the canonical `"aide"` key.
|
|
30
|
+
*/
|
|
31
|
+
export default async function checkMcpConfig(mcpConfigPath) {
|
|
32
|
+
const name = "MCP config";
|
|
33
|
+
const prescription = { key: "aide", entry: CANONICAL_AIDE_SERVER };
|
|
34
|
+
const existing = await safeReadFile(mcpConfigPath);
|
|
35
|
+
if (existing === undefined) {
|
|
36
|
+
return {
|
|
37
|
+
name,
|
|
38
|
+
filePath: mcpConfigPath,
|
|
39
|
+
status: "missing",
|
|
40
|
+
category: "mcp",
|
|
41
|
+
prescription,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
let config;
|
|
45
|
+
try {
|
|
46
|
+
config = JSON.parse(existing);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return {
|
|
50
|
+
name,
|
|
51
|
+
filePath: mcpConfigPath,
|
|
52
|
+
status: "malformed",
|
|
53
|
+
category: "mcp",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const servers = (config.mcpServers ?? {});
|
|
57
|
+
// Check both key names init may have used.
|
|
58
|
+
const hasLegacyKey = "aidemd-mcp" in servers;
|
|
59
|
+
const aideEntry = ("aide" in servers ? servers["aide"] : servers["aidemd-mcp"]);
|
|
60
|
+
const isCanonical = !hasLegacyKey &&
|
|
61
|
+
aideEntry !== undefined &&
|
|
62
|
+
aideEntry.command === CANONICAL_AIDE_SERVER.command &&
|
|
63
|
+
Array.isArray(aideEntry.args) &&
|
|
64
|
+
aideEntry.args.length === CANONICAL_AIDE_SERVER.args.length &&
|
|
65
|
+
CANONICAL_AIDE_SERVER.args.every((a, i) => aideEntry.args[i] === a);
|
|
66
|
+
if (isCanonical) {
|
|
67
|
+
return {
|
|
68
|
+
name,
|
|
69
|
+
filePath: mcpConfigPath,
|
|
70
|
+
status: "matches",
|
|
71
|
+
category: "mcp",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
filePath: mcpConfigPath,
|
|
77
|
+
status: "differs",
|
|
78
|
+
category: "mcp",
|
|
79
|
+
prescription,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { UpgradeFileStatus } from "../../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compare a host file against canonical content.
|
|
4
|
+
*
|
|
5
|
+
* Read-only — never writes. Returns:
|
|
6
|
+
* - `"missing"` when the file does not exist
|
|
7
|
+
* - `"matches"` when the file content is byte-identical to canonical
|
|
8
|
+
* - `"differs"` when the file exists but content differs
|
|
9
|
+
*
|
|
10
|
+
* Non-ENOENT read errors are re-thrown.
|
|
11
|
+
*/
|
|
12
|
+
export default function compareFile(hostPath: string, canonicalContent: string): Promise<UpgradeFileStatus>;
|