@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.
Files changed (114) hide show
  1. package/.aide/docs/.aide +128 -0
  2. package/.aide/docs/agent-readable-code.md +74 -0
  3. package/.aide/docs/aide-spec.md +201 -0
  4. package/.aide/docs/aide-template.md +110 -0
  5. package/.aide/docs/automated-qa.md +111 -0
  6. package/.aide/docs/cascading-alignment.md +107 -0
  7. package/.aide/docs/index.md +38 -0
  8. package/.aide/docs/plan-aide.md +77 -0
  9. package/.aide/docs/plan.aide +60 -0
  10. package/.aide/docs/progressive-disclosure.md +72 -0
  11. package/.aide/docs/todo-aide.md +77 -0
  12. package/.aide/intent.aide +256 -0
  13. package/.aide/plan.aide +169 -0
  14. package/.aide/todo.aide +47 -0
  15. package/.claude/.aide +246 -0
  16. package/.claude/commands/aide/align.md +15 -0
  17. package/.claude/commands/aide/build.md +17 -0
  18. package/.claude/commands/aide/fix.md +20 -0
  19. package/.claude/commands/aide/init.md +171 -0
  20. package/.claude/commands/aide/plan.md +25 -0
  21. package/.claude/commands/aide/qa.md +25 -0
  22. package/.claude/commands/aide/refactor.md +29 -0
  23. package/.claude/commands/aide/research.md +21 -0
  24. package/.claude/commands/aide/spec.md +24 -0
  25. package/.claude/commands/aide/synthesize.md +20 -0
  26. package/.claude/commands/aide/update-playbook.md +18 -0
  27. package/.claude/commands/aide/upgrade.md +91 -0
  28. package/LICENSE +21 -0
  29. package/README.md +88 -0
  30. package/dist/cli/App/index.d.ts +14 -0
  31. package/dist/cli/App/index.js +282 -0
  32. package/dist/cli/DetailPanel/index.d.ts +24 -0
  33. package/dist/cli/DetailPanel/index.js +57 -0
  34. package/dist/cli/TreePanel/index.d.ts +24 -0
  35. package/dist/cli/TreePanel/index.js +65 -0
  36. package/dist/cli/buildTreeData/index.d.ts +7 -0
  37. package/dist/cli/buildTreeData/index.js +51 -0
  38. package/dist/cli/findPrimaryIntent/index.d.ts +12 -0
  39. package/dist/cli/findPrimaryIntent/index.js +20 -0
  40. package/dist/cli/flattenTree/index.d.ts +16 -0
  41. package/dist/cli/flattenTree/index.js +20 -0
  42. package/dist/cli/index.d.ts +2 -0
  43. package/dist/cli/index.js +15 -0
  44. package/dist/cli/init/index.d.ts +2 -0
  45. package/dist/cli/init/index.js +33 -0
  46. package/dist/cli/init/writeInitCommand/index.d.ts +13 -0
  47. package/dist/cli/init/writeInitCommand/index.js +25 -0
  48. package/dist/cli/init/writeMcpEntry/index.d.ts +12 -0
  49. package/dist/cli/init/writeMcpEntry/index.js +51 -0
  50. package/dist/index.d.ts +2 -0
  51. package/dist/index.js +228 -0
  52. package/dist/tools/discover/buildAncestorChain/index.d.ts +17 -0
  53. package/dist/tools/discover/buildAncestorChain/index.js +98 -0
  54. package/dist/tools/discover/buildTree/index.d.ts +9 -0
  55. package/dist/tools/discover/buildTree/index.js +57 -0
  56. package/dist/tools/discover/index.d.ts +20 -0
  57. package/dist/tools/discover/index.js +49 -0
  58. package/dist/tools/init/applySteps/index.d.ts +30 -0
  59. package/dist/tools/init/applySteps/index.js +76 -0
  60. package/dist/tools/init/configureIde/index.d.ts +21 -0
  61. package/dist/tools/init/configureIde/index.js +135 -0
  62. package/dist/tools/init/detectFramework/index.d.ts +11 -0
  63. package/dist/tools/init/detectFramework/index.js +53 -0
  64. package/dist/tools/init/index.d.ts +46 -0
  65. package/dist/tools/init/index.js +99 -0
  66. package/dist/tools/init/initContent/index.d.ts +99 -0
  67. package/dist/tools/init/initContent/index.js +162 -0
  68. package/dist/tools/init/installAgents/index.d.ts +12 -0
  69. package/dist/tools/init/installAgents/index.js +60 -0
  70. package/dist/tools/init/installMethodologyDocs/index.d.ts +14 -0
  71. package/dist/tools/init/installMethodologyDocs/index.js +62 -0
  72. package/dist/tools/init/installSkills/index.d.ts +12 -0
  73. package/dist/tools/init/installSkills/index.js +60 -0
  74. package/dist/tools/init/provisionBrain/index.d.ts +23 -0
  75. package/dist/tools/init/provisionBrain/index.js +239 -0
  76. package/dist/tools/init/resolveBrainHints/index.d.ts +17 -0
  77. package/dist/tools/init/resolveBrainHints/index.js +44 -0
  78. package/dist/tools/init/scaffoldCommands/index.d.ts +38 -0
  79. package/dist/tools/init/scaffoldCommands/index.js +94 -0
  80. package/dist/tools/init/wireMcp/index.d.ts +16 -0
  81. package/dist/tools/init/wireMcp/index.js +72 -0
  82. package/dist/tools/init/writeMethodology/index.d.ts +20 -0
  83. package/dist/tools/init/writeMethodology/index.js +94 -0
  84. package/dist/tools/read/index.d.ts +15 -0
  85. package/dist/tools/read/index.js +79 -0
  86. package/dist/tools/scaffold/index.d.ts +22 -0
  87. package/dist/tools/scaffold/index.js +128 -0
  88. package/dist/tools/upgrade/applyFiles/index.d.ts +33 -0
  89. package/dist/tools/upgrade/applyFiles/index.js +65 -0
  90. package/dist/tools/upgrade/buildVersionsMeta/index.d.ts +20 -0
  91. package/dist/tools/upgrade/buildVersionsMeta/index.js +51 -0
  92. package/dist/tools/upgrade/checkIdeConfig/index.d.ts +24 -0
  93. package/dist/tools/upgrade/checkIdeConfig/index.js +134 -0
  94. package/dist/tools/upgrade/checkMcpConfig/index.d.ts +17 -0
  95. package/dist/tools/upgrade/checkMcpConfig/index.js +81 -0
  96. package/dist/tools/upgrade/compareFile/index.d.ts +12 -0
  97. package/dist/tools/upgrade/compareFile/index.js +24 -0
  98. package/dist/tools/upgrade/index.d.ts +24 -0
  99. package/dist/tools/upgrade/index.js +139 -0
  100. package/dist/tools/upgrade/spliceStub/index.d.ts +13 -0
  101. package/dist/tools/upgrade/spliceStub/index.js +91 -0
  102. package/dist/tools/validate/index.d.ts +18 -0
  103. package/dist/tools/validate/index.js +65 -0
  104. package/dist/types/index.d.ts +277 -0
  105. package/dist/types/index.js +10 -0
  106. package/dist/util/classify/index.d.ts +17 -0
  107. package/dist/util/classify/index.js +134 -0
  108. package/dist/util/parseBody/index.d.ts +7 -0
  109. package/dist/util/parseBody/index.js +43 -0
  110. package/dist/util/parseFrontmatter/index.d.ts +12 -0
  111. package/dist/util/parseFrontmatter/index.js +64 -0
  112. package/dist/util/scan/index.d.ts +7 -0
  113. package/dist/util/scan/index.js +82 -0
  114. 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>;